Home | History | Annotate | Download | only in cts
      1 /*
      2  * Copyright (C) 2015 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.assertNotNull;
     22 import static org.junit.Assert.assertNull;
     23 import static org.junit.Assert.assertSame;
     24 import static org.junit.Assert.assertTrue;
     25 import static org.junit.Assert.fail;
     26 import static org.mockito.Mockito.any;
     27 import static org.mockito.Mockito.doCallRealMethod;
     28 import static org.mockito.Mockito.eq;
     29 import static org.mockito.Mockito.mock;
     30 import static org.mockito.Mockito.never;
     31 import static org.mockito.Mockito.spy;
     32 import static org.mockito.Mockito.times;
     33 import static org.mockito.Mockito.verify;
     34 import static org.mockito.Mockito.verifyNoMoreInteractions;
     35 
     36 import android.app.Activity;
     37 import android.app.Instrumentation;
     38 import android.content.Context;
     39 import android.graphics.Rect;
     40 import android.graphics.drawable.ColorDrawable;
     41 import android.graphics.drawable.Drawable;
     42 import android.platform.test.annotations.Presubmit;
     43 import android.support.test.InstrumentationRegistry;
     44 import android.support.test.filters.LargeTest;
     45 import android.support.test.rule.ActivityTestRule;
     46 import android.support.test.runner.AndroidJUnit4;
     47 import android.view.Display;
     48 import android.view.Gravity;
     49 import android.view.KeyEvent;
     50 import android.view.LayoutInflater;
     51 import android.view.View;
     52 import android.view.ViewGroup;
     53 import android.view.WindowManager;
     54 import android.widget.AdapterView;
     55 import android.widget.BaseAdapter;
     56 import android.widget.ListAdapter;
     57 import android.widget.ListPopupWindow;
     58 import android.widget.ListView;
     59 import android.widget.PopupWindow;
     60 import android.widget.TextView;
     61 
     62 import com.android.compatibility.common.util.CtsKeyEventUtil;
     63 import com.android.compatibility.common.util.CtsTouchUtils;
     64 import com.android.compatibility.common.util.WidgetTestUtils;
     65 
     66 import org.junit.After;
     67 import org.junit.Before;
     68 import org.junit.Rule;
     69 import org.junit.Test;
     70 import org.junit.runner.RunWith;
     71 
     72 @LargeTest
     73 @RunWith(AndroidJUnit4.class)
     74 public class ListPopupWindowTest {
     75     private Instrumentation mInstrumentation;
     76     private Activity mActivity;
     77     private Builder mPopupWindowBuilder;
     78     private View promptView;
     79 
     80     /** The list popup window. */
     81     private ListPopupWindow mPopupWindow;
     82 
     83     private AdapterView.OnItemClickListener mItemClickListener;
     84 
     85     /**
     86      * Item click listener that dismisses our <code>ListPopupWindow</code> when any item
     87      * is clicked. Note that this needs to be a separate class that is also protected (not
     88      * private) so that Mockito can "spy" on it.
     89      */
     90     protected class PopupItemClickListener implements AdapterView.OnItemClickListener {
     91         @Override
     92         public void onItemClick(AdapterView<?> parent, View view, int position,
     93                 long id) {
     94             mPopupWindow.dismiss();
     95         }
     96     }
     97 
     98     @Rule
     99     public ActivityTestRule<ListPopupWindowCtsActivity> mActivityRule
    100             = new ActivityTestRule<>(ListPopupWindowCtsActivity.class);
    101 
    102     @Before
    103     public void setup() {
    104         mInstrumentation = InstrumentationRegistry.getInstrumentation();
    105         mActivity = mActivityRule.getActivity();
    106         mItemClickListener = new PopupItemClickListener();
    107     }
    108 
    109     @After
    110     public void teardown() throws Throwable {
    111         if ((mPopupWindowBuilder != null) && (mPopupWindow != null)) {
    112             mActivityRule.runOnUiThread(mPopupWindowBuilder::dismiss);
    113             mInstrumentation.waitForIdleSync();
    114         }
    115     }
    116 
    117     @Test
    118     public void testConstructor() {
    119         new ListPopupWindow(mActivity);
    120 
    121         new ListPopupWindow(mActivity, null);
    122 
    123         new ListPopupWindow(mActivity, null, android.R.attr.popupWindowStyle);
    124 
    125         new ListPopupWindow(mActivity, null, 0,
    126                 android.R.style.Widget_DeviceDefault_ListPopupWindow);
    127 
    128         new ListPopupWindow(mActivity, null, 0,
    129                 android.R.style.Widget_DeviceDefault_Light_ListPopupWindow);
    130 
    131         new ListPopupWindow(mActivity, null, 0, android.R.style.Widget_Material_ListPopupWindow);
    132 
    133         new ListPopupWindow(mActivity, null, 0,
    134                 android.R.style.Widget_Material_Light_ListPopupWindow);
    135     }
    136 
    137     @Test
    138     public void testNoDefaultVisibility() {
    139         mPopupWindow = new ListPopupWindow(mActivity);
    140         assertFalse(mPopupWindow.isShowing());
    141     }
    142 
    143     @Test
    144     public void testAccessBackground() throws Throwable {
    145         mPopupWindowBuilder = new Builder();
    146         mActivityRule.runOnUiThread(mPopupWindowBuilder::show);
    147         mInstrumentation.waitForIdleSync();
    148 
    149         Drawable drawable = new ColorDrawable();
    150         mPopupWindow.setBackgroundDrawable(drawable);
    151         assertSame(drawable, mPopupWindow.getBackground());
    152 
    153         mPopupWindow.setBackgroundDrawable(null);
    154         assertNull(mPopupWindow.getBackground());
    155     }
    156 
    157     @Test
    158     public void testAccessAnimationStyle() throws Throwable {
    159         mPopupWindowBuilder = new Builder();
    160         mActivityRule.runOnUiThread(mPopupWindowBuilder::show);
    161         mInstrumentation.waitForIdleSync();
    162         assertEquals(0, mPopupWindow.getAnimationStyle());
    163 
    164         mPopupWindow.setAnimationStyle(android.R.style.Animation_Toast);
    165         assertEquals(android.R.style.Animation_Toast, mPopupWindow.getAnimationStyle());
    166 
    167         // abnormal values
    168         mPopupWindow.setAnimationStyle(-100);
    169         assertEquals(-100, mPopupWindow.getAnimationStyle());
    170     }
    171 
    172     @Test
    173     public void testAccessHeight() throws Throwable {
    174         mPopupWindowBuilder = new Builder();
    175         mActivityRule.runOnUiThread(mPopupWindowBuilder::show);
    176         mInstrumentation.waitForIdleSync();
    177 
    178         assertEquals(WindowManager.LayoutParams.WRAP_CONTENT, mPopupWindow.getHeight());
    179 
    180         int height = getDisplay().getHeight() / 2;
    181         mPopupWindow.setHeight(height);
    182         assertEquals(height, mPopupWindow.getHeight());
    183 
    184         height = getDisplay().getHeight();
    185         mPopupWindow.setHeight(height);
    186         assertEquals(height, mPopupWindow.getHeight());
    187 
    188         mPopupWindow.setHeight(0);
    189         assertEquals(0, mPopupWindow.getHeight());
    190 
    191         height = getDisplay().getHeight() * 2;
    192         mPopupWindow.setHeight(height);
    193         assertEquals(height, mPopupWindow.getHeight());
    194 
    195         height = -getDisplay().getHeight() / 2;
    196         try {
    197             mPopupWindow.setHeight(height);
    198             fail("should throw IllegalArgumentException for negative height.");
    199         } catch (IllegalArgumentException e) {
    200             // expected exception.
    201         }
    202     }
    203 
    204     /**
    205      * Gets the display.
    206      *
    207      * @return the display
    208      */
    209     private Display getDisplay() {
    210         WindowManager wm = (WindowManager) mActivity.getSystemService(Context.WINDOW_SERVICE);
    211         return wm.getDefaultDisplay();
    212     }
    213 
    214     @Test
    215     public void testAccessWidth() throws Throwable {
    216         mPopupWindowBuilder = new Builder().ignoreContentWidth();
    217         mActivityRule.runOnUiThread(mPopupWindowBuilder::show);
    218         mInstrumentation.waitForIdleSync();
    219 
    220         assertEquals(WindowManager.LayoutParams.WRAP_CONTENT, mPopupWindow.getWidth());
    221 
    222         int width = getDisplay().getWidth() / 2;
    223         mPopupWindow.setWidth(width);
    224         assertEquals(width, mPopupWindow.getWidth());
    225 
    226         width = getDisplay().getWidth();
    227         mPopupWindow.setWidth(width);
    228         assertEquals(width, mPopupWindow.getWidth());
    229 
    230         mPopupWindow.setWidth(0);
    231         assertEquals(0, mPopupWindow.getWidth());
    232 
    233         width = getDisplay().getWidth() * 2;
    234         mPopupWindow.setWidth(width);
    235         assertEquals(width, mPopupWindow.getWidth());
    236 
    237         width = - getDisplay().getWidth() / 2;
    238         mPopupWindow.setWidth(width);
    239         assertEquals(width, mPopupWindow.getWidth());
    240     }
    241 
    242     private void verifyAnchoring(int horizontalOffset, int verticalOffset, int gravity) {
    243         final View upperAnchor = mActivity.findViewById(R.id.anchor_upper);
    244         final ListView listView = mPopupWindow.getListView();
    245         int[] anchorXY = new int[2];
    246         int[] listViewOnScreenXY = new int[2];
    247         int[] listViewInWindowXY = new int[2];
    248 
    249         assertTrue(mPopupWindow.isShowing());
    250         assertEquals(upperAnchor, mPopupWindow.getAnchorView());
    251 
    252         listView.getLocationOnScreen(listViewOnScreenXY);
    253         upperAnchor.getLocationOnScreen(anchorXY);
    254         listView.getLocationInWindow(listViewInWindowXY);
    255 
    256         int expectedListViewOnScreenX = anchorXY[0] + listViewInWindowXY[0] + horizontalOffset;
    257         final int absoluteGravity =
    258                 Gravity.getAbsoluteGravity(gravity, upperAnchor.getLayoutDirection());
    259         if (absoluteGravity == Gravity.RIGHT) {
    260             expectedListViewOnScreenX -= (listView.getWidth() - upperAnchor.getWidth());
    261         } else {
    262             // On narrow screens, it's possible for the popup to reach the edge
    263             // of the screen.
    264             int rightmostX =
    265                     getDisplay().getWidth() - mPopupWindow.getWidth() + listViewInWindowXY[0];
    266             if (expectedListViewOnScreenX > rightmostX) {
    267                 expectedListViewOnScreenX = rightmostX;
    268             }
    269         }
    270         int expectedListViewOnScreenY = anchorXY[1] + listViewInWindowXY[1]
    271                 + upperAnchor.getHeight() + verticalOffset;
    272         assertEquals(expectedListViewOnScreenX, listViewOnScreenXY[0]);
    273         assertEquals(expectedListViewOnScreenY, listViewOnScreenXY[1]);
    274     }
    275 
    276     @Test
    277     public void testAnchoring() throws Throwable {
    278         mPopupWindowBuilder = new Builder();
    279         mActivityRule.runOnUiThread(mPopupWindowBuilder::show);
    280         mInstrumentation.waitForIdleSync();
    281 
    282         assertEquals(0, mPopupWindow.getHorizontalOffset());
    283         assertEquals(0, mPopupWindow.getVerticalOffset());
    284 
    285         verifyAnchoring(0, 0, Gravity.NO_GRAVITY);
    286     }
    287 
    288     @Test
    289     public void testAnchoringWithHorizontalOffset() throws Throwable {
    290         mPopupWindowBuilder = new Builder().withHorizontalOffset(50);
    291         mActivityRule.runOnUiThread(mPopupWindowBuilder::show);
    292         mInstrumentation.waitForIdleSync();
    293 
    294         assertEquals(50, mPopupWindow.getHorizontalOffset());
    295         assertEquals(0, mPopupWindow.getVerticalOffset());
    296 
    297         verifyAnchoring(50, 0, Gravity.NO_GRAVITY);
    298     }
    299 
    300     @Test
    301     public void testAnchoringWithVerticalOffset() throws Throwable {
    302         mPopupWindowBuilder = new Builder().withVerticalOffset(60);
    303         mActivityRule.runOnUiThread(mPopupWindowBuilder::show);
    304         mInstrumentation.waitForIdleSync();
    305 
    306         assertEquals(0, mPopupWindow.getHorizontalOffset());
    307         assertEquals(60, mPopupWindow.getVerticalOffset());
    308 
    309         verifyAnchoring(0, 60, Gravity.NO_GRAVITY);
    310     }
    311 
    312     @Test
    313     public void testAnchoringWithRightGravity() throws Throwable {
    314         mPopupWindowBuilder = new Builder().withDropDownGravity(Gravity.RIGHT);
    315         mActivityRule.runOnUiThread(mPopupWindowBuilder::show);
    316         mInstrumentation.waitForIdleSync();
    317 
    318         assertEquals(0, mPopupWindow.getHorizontalOffset());
    319         assertEquals(0, mPopupWindow.getVerticalOffset());
    320 
    321         verifyAnchoring(0, 0, Gravity.RIGHT);
    322     }
    323 
    324     @Test
    325     public void testAnchoringWithEndGravity() throws Throwable {
    326         mPopupWindowBuilder = new Builder().withDropDownGravity(Gravity.END);
    327         mActivityRule.runOnUiThread(mPopupWindowBuilder::show);
    328         mInstrumentation.waitForIdleSync();
    329 
    330         assertEquals(0, mPopupWindow.getHorizontalOffset());
    331         assertEquals(0, mPopupWindow.getVerticalOffset());
    332 
    333         verifyAnchoring(0, 0, Gravity.END);
    334     }
    335 
    336     @Test
    337     public void testSetWindowLayoutType() throws Throwable {
    338         mPopupWindowBuilder = new Builder().withWindowLayoutType(
    339                 WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
    340         mActivityRule.runOnUiThread(mPopupWindowBuilder::show);
    341         mInstrumentation.waitForIdleSync();
    342         assertTrue(mPopupWindow.isShowing());
    343 
    344         WindowManager.LayoutParams p = (WindowManager.LayoutParams)
    345                 mPopupWindow.getListView().getRootView().getLayoutParams();
    346         assertEquals(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL, p.type);
    347     }
    348 
    349     @Test
    350     public void testDismiss() throws Throwable {
    351         mPopupWindowBuilder = new Builder();
    352         mActivityRule.runOnUiThread(mPopupWindowBuilder::show);
    353         mInstrumentation.waitForIdleSync();
    354         assertTrue(mPopupWindow.isShowing());
    355 
    356         mActivityRule.runOnUiThread(mPopupWindowBuilder::dismiss);
    357         mInstrumentation.waitForIdleSync();
    358         assertFalse(mPopupWindow.isShowing());
    359 
    360         mActivityRule.runOnUiThread(mPopupWindowBuilder::dismiss);
    361         mInstrumentation.waitForIdleSync();
    362         assertFalse(mPopupWindow.isShowing());
    363     }
    364 
    365     @Test
    366     public void testSetOnDismissListener() throws Throwable {
    367         mPopupWindowBuilder = new Builder().withDismissListener();
    368         mActivityRule.runOnUiThread(mPopupWindowBuilder::show);
    369         mInstrumentation.waitForIdleSync();
    370         mActivityRule.runOnUiThread(mPopupWindowBuilder::dismiss);
    371         mInstrumentation.waitForIdleSync();
    372         verify(mPopupWindowBuilder.mOnDismissListener, times(1)).onDismiss();
    373 
    374         mActivityRule.runOnUiThread(mPopupWindowBuilder::showAgain);
    375         mInstrumentation.waitForIdleSync();
    376         mActivityRule.runOnUiThread(mPopupWindowBuilder::dismiss);
    377         mInstrumentation.waitForIdleSync();
    378         verify(mPopupWindowBuilder.mOnDismissListener, times(2)).onDismiss();
    379 
    380         mPopupWindow.setOnDismissListener(null);
    381         mActivityRule.runOnUiThread(mPopupWindowBuilder::showAgain);
    382         mInstrumentation.waitForIdleSync();
    383         mActivityRule.runOnUiThread(mPopupWindowBuilder::dismiss);
    384         mInstrumentation.waitForIdleSync();
    385         // Since we've reset the listener to null, we are not expecting any more interactions
    386         // on the previously registered listener.
    387         verifyNoMoreInteractions(mPopupWindowBuilder.mOnDismissListener);
    388     }
    389 
    390     @Test
    391     public void testAccessInputMethodMode() throws Throwable {
    392         mPopupWindowBuilder = new Builder().withDismissListener();
    393         mActivityRule.runOnUiThread(mPopupWindowBuilder::show);
    394         mInstrumentation.waitForIdleSync();
    395 
    396         assertEquals(PopupWindow.INPUT_METHOD_NEEDED, mPopupWindow.getInputMethodMode());
    397         assertFalse(mPopupWindow.isInputMethodNotNeeded());
    398 
    399         mPopupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_FROM_FOCUSABLE);
    400         assertEquals(PopupWindow.INPUT_METHOD_FROM_FOCUSABLE, mPopupWindow.getInputMethodMode());
    401         assertFalse(mPopupWindow.isInputMethodNotNeeded());
    402 
    403         mPopupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
    404         assertEquals(PopupWindow.INPUT_METHOD_NEEDED, mPopupWindow.getInputMethodMode());
    405         assertFalse(mPopupWindow.isInputMethodNotNeeded());
    406 
    407         mPopupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
    408         assertEquals(PopupWindow.INPUT_METHOD_NOT_NEEDED, mPopupWindow.getInputMethodMode());
    409         assertTrue(mPopupWindow.isInputMethodNotNeeded());
    410 
    411         mPopupWindow.setInputMethodMode(-1);
    412         assertEquals(-1, mPopupWindow.getInputMethodMode());
    413         assertFalse(mPopupWindow.isInputMethodNotNeeded());
    414     }
    415 
    416     @Test
    417     public void testAccessSoftInputMethodMode() throws Throwable {
    418         mPopupWindowBuilder = new Builder().withDismissListener();
    419         mActivityRule.runOnUiThread(mPopupWindowBuilder::show);
    420         mInstrumentation.waitForIdleSync();
    421 
    422         mPopupWindow = new ListPopupWindow(mActivity);
    423         assertEquals(WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED,
    424                 mPopupWindow.getSoftInputMode());
    425 
    426         mPopupWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
    427         assertEquals(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE,
    428                 mPopupWindow.getSoftInputMode());
    429 
    430         mPopupWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
    431         assertEquals(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE,
    432                 mPopupWindow.getSoftInputMode());
    433     }
    434 
    435     private void verifyDismissalViaTouch(boolean setupAsModal) throws Throwable {
    436         // Register a click listener on the top-level container
    437         final View mainContainer = mActivity.findViewById(R.id.main_container);
    438         final View.OnClickListener mockContainerClickListener = mock(View.OnClickListener.class);
    439         mActivityRule.runOnUiThread(() ->
    440                 mainContainer.setOnClickListener(mockContainerClickListener));
    441 
    442         // Configure a list popup window with requested modality
    443         mPopupWindowBuilder = new Builder().setModal(setupAsModal).withDismissListener();
    444         mActivityRule.runOnUiThread(mPopupWindowBuilder::show);
    445         mInstrumentation.waitForIdleSync();
    446 
    447         assertTrue("Popup window showing", mPopupWindow.isShowing());
    448         // Make sure that the modality of the popup window is set up correctly
    449         assertEquals("Popup window modality", setupAsModal, mPopupWindow.isModal());
    450 
    451         // The logic below uses Instrumentation to emulate a tap outside the bounds of the
    452         // displayed list popup window. This tap is then treated by the framework to be "split" as
    453         // the ACTION_OUTSIDE for the popup itself, as well as DOWN / MOVE / UP for the underlying
    454         // view root if the popup is not modal.
    455         // It is not correct to emulate these two sequences separately in the test, as it
    456         // wouldn't emulate the user-facing interaction for this test. Also, we don't want to use
    457         // View.dispatchTouchEvent directly as that would require emulation of two separate
    458         // sequences as well.
    459         final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
    460         final ListView popupListView = mPopupWindow.getListView();
    461         final Rect rect = new Rect();
    462         mPopupWindow.getBackground().getPadding(rect);
    463         CtsTouchUtils.emulateTapOnView(instrumentation, popupListView,
    464                 -rect.left - 20, popupListView.getHeight() + rect.top + rect.bottom + 20);
    465 
    466         // At this point our popup should not be showing and should have notified its
    467         // dismiss listener
    468         verify(mPopupWindowBuilder.mOnDismissListener, times(1)).onDismiss();
    469         assertFalse("Popup window not showing after outside click", mPopupWindow.isShowing());
    470 
    471         // Also test that the click outside the popup bounds has been "delivered" to the main
    472         // container only if the popup is not modal
    473         verify(mockContainerClickListener, times(setupAsModal ? 0 : 1)).onClick(mainContainer);
    474     }
    475 
    476     @Test
    477     public void testDismissalOutsideNonModal() throws Throwable {
    478         verifyDismissalViaTouch(false);
    479     }
    480 
    481     @Test
    482     public void testDismissalOutsideModal() throws Throwable {
    483         verifyDismissalViaTouch(true);
    484     }
    485 
    486     @Test
    487     public void testItemClicks() throws Throwable {
    488         mPopupWindowBuilder = new Builder().withItemClickListener().withDismissListener();
    489         mActivityRule.runOnUiThread(mPopupWindowBuilder::show);
    490         mInstrumentation.waitForIdleSync();
    491         mActivityRule.runOnUiThread(() -> mPopupWindow.performItemClick(2));
    492         mInstrumentation.waitForIdleSync();
    493 
    494         verify(mPopupWindowBuilder.mOnItemClickListener, times(1)).onItemClick(
    495                 any(AdapterView.class), any(View.class), eq(2), eq(2L));
    496         // Also verify that the popup window has been dismissed
    497         assertFalse(mPopupWindow.isShowing());
    498         verify(mPopupWindowBuilder.mOnDismissListener, times(1)).onDismiss();
    499 
    500         mActivityRule.runOnUiThread(mPopupWindowBuilder::showAgain);
    501         mInstrumentation.waitForIdleSync();
    502         mActivityRule.runOnUiThread(
    503                 () -> mPopupWindow.getListView().performItemClick(null, 1, 1));
    504         mInstrumentation.waitForIdleSync();
    505 
    506         verify(mPopupWindowBuilder.mOnItemClickListener, times(1)).onItemClick(
    507                 any(AdapterView.class), any(), eq(1), eq(1L));
    508         // Also verify that the popup window has been dismissed
    509         assertFalse(mPopupWindow.isShowing());
    510         verify(mPopupWindowBuilder.mOnDismissListener, times(2)).onDismiss();
    511 
    512         // Finally verify that our item click listener has only been called twice
    513         verifyNoMoreInteractions(mPopupWindowBuilder.mOnItemClickListener);
    514     }
    515 
    516     @Test
    517     public void testPromptViewAbove() throws Throwable {
    518         mActivityRule.runOnUiThread(() -> {
    519             promptView = LayoutInflater.from(mActivity).inflate(R.layout.popupwindow_prompt, null);
    520             mPopupWindowBuilder = new Builder().withPrompt(
    521                     promptView, ListPopupWindow.POSITION_PROMPT_ABOVE);
    522             mPopupWindowBuilder.show();
    523         });
    524         mInstrumentation.waitForIdleSync();
    525 
    526         // Verify that our prompt is displayed on the screen and is above the first list item
    527         assertTrue(promptView.isAttachedToWindow());
    528         assertTrue(promptView.isShown());
    529         assertEquals(ListPopupWindow.POSITION_PROMPT_ABOVE, mPopupWindow.getPromptPosition());
    530 
    531         final ListView listView = mPopupWindow.getListView();
    532         WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, listView, null);
    533 
    534         final int[] promptViewOnScreenXY = new int[2];
    535         final int[] firstChildOnScreenXY = new int[2];
    536 
    537         mActivityRule.runOnUiThread(()-> {
    538             promptView.getLocationOnScreen(promptViewOnScreenXY);
    539 
    540             final View firstListChild = listView.getChildAt(0);
    541             firstListChild.getLocationOnScreen(firstChildOnScreenXY);
    542         });
    543         mInstrumentation.waitForIdleSync();
    544 
    545         assertTrue(promptViewOnScreenXY[1] + promptView.getHeight() <= firstChildOnScreenXY[1]);
    546     }
    547 
    548     @Test
    549     public void testPromptViewBelow() throws Throwable {
    550         mActivityRule.runOnUiThread(() -> {
    551             promptView = LayoutInflater.from(mActivity).inflate(R.layout.popupwindow_prompt, null);
    552             mPopupWindowBuilder = new Builder().withPrompt(
    553                     promptView, ListPopupWindow.POSITION_PROMPT_BELOW);
    554             mPopupWindowBuilder.show();
    555         });
    556         mInstrumentation.waitForIdleSync();
    557 
    558         // Verify that our prompt is displayed on the screen and is below the last list item
    559         assertTrue(promptView.isAttachedToWindow());
    560         assertTrue(promptView.isShown());
    561         assertEquals(ListPopupWindow.POSITION_PROMPT_BELOW, mPopupWindow.getPromptPosition());
    562 
    563         final ListView listView = mPopupWindow.getListView();
    564         WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, listView, null);
    565 
    566         final int[] promptViewOnScreenXY = new int[2];
    567         final int[] lastChildOnScreenXY = new int[2];
    568 
    569         mActivityRule.runOnUiThread(()-> {
    570             promptView.getLocationOnScreen(promptViewOnScreenXY);
    571 
    572             final View lastListChild = listView.getChildAt(listView.getChildCount() - 1);
    573             lastListChild.getLocationOnScreen(lastChildOnScreenXY);
    574         });
    575         mInstrumentation.waitForIdleSync();
    576 
    577         // The child is above the prompt. They may overlap, as in the case
    578         // when the list items do not all fit on screen, but this is still
    579         // correct.
    580         assertTrue(lastChildOnScreenXY[1] <= promptViewOnScreenXY[1]);
    581     }
    582 
    583     @Presubmit
    584     @Test
    585     public void testAccessSelection() throws Throwable {
    586         mPopupWindowBuilder = new Builder().withItemSelectedListener();
    587         mActivityRule.runOnUiThread(mPopupWindowBuilder::show);
    588         mInstrumentation.waitForIdleSync();
    589 
    590         final ListView listView = mPopupWindow.getListView();
    591 
    592         // Select an item
    593         WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, listView,
    594                 () -> mPopupWindow.setSelection(1));
    595 
    596         // And verify the current selection state + selection listener invocation
    597         verify(mPopupWindowBuilder.mOnItemSelectedListener, times(1)).onItemSelected(
    598                 any(AdapterView.class), any(View.class), eq(1), eq(1L));
    599         assertEquals(1, mPopupWindow.getSelectedItemId());
    600         assertEquals(1, mPopupWindow.getSelectedItemPosition());
    601         assertEquals("Bob", mPopupWindow.getSelectedItem());
    602         View selectedView = mPopupWindow.getSelectedView();
    603         assertNotNull(selectedView);
    604         assertEquals("Bob",
    605                 ((TextView) selectedView.findViewById(android.R.id.text1)).getText());
    606 
    607         // Select another item
    608         WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, listView,
    609                 () -> mPopupWindow.setSelection(3));
    610 
    611         // And verify the new selection state + selection listener invocation
    612         verify(mPopupWindowBuilder.mOnItemSelectedListener, times(1)).onItemSelected(
    613                 any(AdapterView.class), any(View.class), eq(3), eq(3L));
    614         assertEquals(3, mPopupWindow.getSelectedItemId());
    615         assertEquals(3, mPopupWindow.getSelectedItemPosition());
    616         assertEquals("Deirdre", mPopupWindow.getSelectedItem());
    617         selectedView = mPopupWindow.getSelectedView();
    618         assertNotNull(selectedView);
    619         assertEquals("Deirdre",
    620                 ((TextView) selectedView.findViewById(android.R.id.text1)).getText());
    621 
    622         // Clear selection
    623         WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, listView,
    624                 mPopupWindow::clearListSelection);
    625 
    626         // And verify empty selection state + no more selection listener invocation
    627         verify(mPopupWindowBuilder.mOnItemSelectedListener, times(1)).onNothingSelected(
    628                 any(AdapterView.class));
    629         assertEquals(AdapterView.INVALID_ROW_ID, mPopupWindow.getSelectedItemId());
    630         assertEquals(AdapterView.INVALID_POSITION, mPopupWindow.getSelectedItemPosition());
    631         assertEquals(null, mPopupWindow.getSelectedItem());
    632         assertEquals(null, mPopupWindow.getSelectedView());
    633         verifyNoMoreInteractions(mPopupWindowBuilder.mOnItemSelectedListener);
    634     }
    635 
    636     @Test
    637     public void testNoDefaultDismissalWithBackButton() throws Throwable {
    638         mPopupWindowBuilder = new Builder().withDismissListener();
    639         mActivityRule.runOnUiThread(mPopupWindowBuilder::show);
    640         mInstrumentation.waitForIdleSync();
    641 
    642         // Send BACK key event. As we don't have any custom code that dismisses ListPopupWindow,
    643         // and ListPopupWindow doesn't track that system-level key event on its own, ListPopupWindow
    644         // should stay visible
    645         mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
    646         verify(mPopupWindowBuilder.mOnDismissListener, never()).onDismiss();
    647         assertTrue(mPopupWindow.isShowing());
    648     }
    649 
    650     @Test
    651     public void testCustomDismissalWithBackButton() throws Throwable {
    652         mActivityRule.runOnUiThread(() -> {
    653             mPopupWindowBuilder = new Builder().withAnchor(R.id.anchor_upper_left)
    654                     .withDismissListener();
    655             mPopupWindowBuilder.show();
    656         });
    657         mInstrumentation.waitForIdleSync();
    658 
    659         // "Point" our custom extension of EditText to our ListPopupWindow
    660         final MockViewForListPopupWindow anchor =
    661                 (MockViewForListPopupWindow) mPopupWindow.getAnchorView();
    662         anchor.wireTo(mPopupWindow);
    663         // Request focus on our EditText
    664         mActivityRule.runOnUiThread(anchor::requestFocus);
    665         mInstrumentation.waitForIdleSync();
    666         assertTrue(anchor.isFocused());
    667 
    668         // Send BACK key event. As our custom extension of EditText calls
    669         // ListPopupWindow.onKeyPreIme, the end result should be the dismissal of the
    670         // ListPopupWindow
    671         mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
    672         verify(mPopupWindowBuilder.mOnDismissListener, times(1)).onDismiss();
    673         assertFalse(mPopupWindow.isShowing());
    674     }
    675 
    676     @Test
    677     public void testListSelectionWithDPad() throws Throwable {
    678         mPopupWindowBuilder = new Builder().withAnchor(R.id.anchor_upper_left)
    679                 .withDismissListener().withItemSelectedListener();
    680         mActivityRule.runOnUiThread(mPopupWindowBuilder::show);
    681         mInstrumentation.waitForIdleSync();
    682 
    683         final View root = mPopupWindow.getListView().getRootView();
    684 
    685         // "Point" our custom extension of EditText to our ListPopupWindow
    686         final MockViewForListPopupWindow anchor =
    687                 (MockViewForListPopupWindow) mPopupWindow.getAnchorView();
    688         anchor.wireTo(mPopupWindow);
    689         // Request focus on our EditText
    690         mActivityRule.runOnUiThread(anchor::requestFocus);
    691         mInstrumentation.waitForIdleSync();
    692         assertTrue(anchor.isFocused());
    693 
    694         // Select entry #1 in the popup list
    695         final ListView listView = mPopupWindow.getListView();
    696         WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, listView,
    697                 () -> mPopupWindow.setSelection(1));
    698         verify(mPopupWindowBuilder.mOnItemSelectedListener, times(1)).onItemSelected(
    699                 any(AdapterView.class), any(View.class), eq(1), eq(1L));
    700 
    701         // Send DPAD_DOWN key event. As our custom extension of EditText calls
    702         // ListPopupWindow.onKeyDown and onKeyUp, the end result should be transfer of selection
    703         // down one row
    704         CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, listView, KeyEvent.KEYCODE_DPAD_DOWN);
    705         mInstrumentation.waitForIdleSync();
    706 
    707         WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, root, null);
    708 
    709         // At this point we expect that item #2 was selected
    710         verify(mPopupWindowBuilder.mOnItemSelectedListener, times(1)).onItemSelected(
    711                 any(AdapterView.class), any(View.class), eq(2), eq(2L));
    712 
    713         // Send a DPAD_UP key event. As our custom extension of EditText calls
    714         // ListPopupWindow.onKeyDown and onKeyUp, the end result should be transfer of selection
    715         // up one row
    716         CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, listView, KeyEvent.KEYCODE_DPAD_UP);
    717         mInstrumentation.waitForIdleSync();
    718 
    719         WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, root, null);
    720 
    721         // At this point we expect that item #1 was selected
    722         verify(mPopupWindowBuilder.mOnItemSelectedListener, times(2)).onItemSelected(
    723                 any(AdapterView.class), any(View.class), eq(1), eq(1L));
    724 
    725         // Send one more DPAD_UP key event. As our custom extension of EditText calls
    726         // ListPopupWindow.onKeyDown and onKeyUp, the end result should be transfer of selection
    727         // up one more row
    728         CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, listView, KeyEvent.KEYCODE_DPAD_UP);
    729         mInstrumentation.waitForIdleSync();
    730 
    731         WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, root, null);
    732 
    733         // At this point we expect that item #0 was selected
    734         verify(mPopupWindowBuilder.mOnItemSelectedListener, times(1)).onItemSelected(
    735                 any(AdapterView.class), any(View.class), eq(0), eq(0L));
    736 
    737         // Send ENTER key event. As our custom extension of EditText calls
    738         // ListPopupWindow.onKeyDown and onKeyUp, the end result should be dismissal of
    739         // the popup window
    740         CtsKeyEventUtil.sendKeyDownUp(mInstrumentation,listView, KeyEvent.KEYCODE_ENTER);
    741         mInstrumentation.waitForIdleSync();
    742 
    743         verify(mPopupWindowBuilder.mOnDismissListener, times(1)).onDismiss();
    744         assertFalse(mPopupWindow.isShowing());
    745 
    746         verifyNoMoreInteractions(mPopupWindowBuilder.mOnItemSelectedListener);
    747         verifyNoMoreInteractions(mPopupWindowBuilder.mOnDismissListener);
    748     }
    749 
    750     @Test
    751     public void testCreateOnDragListener() throws Throwable {
    752         // In this test we want precise control over the height of the popup content since
    753         // we need to know by how much to swipe down to end the emulated gesture over the
    754         // specific item in the popup. This is why we're using a popup style that removes
    755         // all decoration around the popup content, as well as our own row layout with known
    756         // height.
    757         mPopupWindowBuilder = new Builder()
    758                 .withPopupStyleAttr(R.style.PopupEmptyStyle)
    759                 .withContentRowLayoutId(R.layout.popup_window_item)
    760                 .withItemClickListener().withDismissListener();
    761 
    762         // Configure ListPopupWindow without showing it
    763         mActivityRule.runOnUiThread(mPopupWindowBuilder::configure);
    764         mInstrumentation.waitForIdleSync();
    765 
    766         // Get the anchor view and configure it with ListPopupWindow's drag-to-open listener
    767         final View anchor = mActivity.findViewById(mPopupWindowBuilder.mAnchorId);
    768         final View.OnTouchListener dragListener = mPopupWindow.createDragToOpenListener(anchor);
    769         mActivityRule.runOnUiThread(() -> {
    770             anchor.setOnTouchListener(dragListener);
    771             // And also configure it to show the popup window on click
    772             anchor.setOnClickListener((View view) -> mPopupWindow.show());
    773         });
    774         mInstrumentation.waitForIdleSync();
    775 
    776         // Get the height of a row item in our popup window
    777         final int popupRowHeight = mActivity.getResources().getDimensionPixelSize(
    778                 R.dimen.popup_row_height);
    779 
    780         final int[] anchorOnScreenXY = new int[2];
    781         anchor.getLocationOnScreen(anchorOnScreenXY);
    782 
    783         // Compute the start coordinates of a downward swipe and the amount of swipe. We'll
    784         // be swiping by twice the row height. That, combined with the swipe originating in the
    785         // center of the anchor should result in clicking the second row in the popup.
    786         int emulatedX = anchorOnScreenXY[0] + anchor.getWidth() / 2;
    787         int emulatedStartY = anchorOnScreenXY[1] + anchor.getHeight() / 2;
    788         int swipeAmount = 2 * popupRowHeight;
    789 
    790         // Emulate drag-down gesture with a sequence of motion events
    791         CtsTouchUtils.emulateDragGesture(mInstrumentation, emulatedX, emulatedStartY,
    792                 0, swipeAmount);
    793 
    794         // We expect the swipe / drag gesture to result in clicking the second item in our list.
    795         verify(mPopupWindowBuilder.mOnItemClickListener, times(1)).onItemClick(
    796                 any(AdapterView.class), any(View.class), eq(1), eq(1L));
    797         // Since our item click listener calls dismiss() on the popup, we expect the popup to not
    798         // be showing
    799         assertFalse(mPopupWindow.isShowing());
    800         // At this point our popup should have notified its dismiss listener
    801         verify(mPopupWindowBuilder.mOnDismissListener, times(1)).onDismiss();
    802     }
    803 
    804     /**
    805      * Inner helper class to configure an instance of <code>ListPopupWindow</code> for the
    806      * specific test. The main reason for its existence is that once a popup window is shown
    807      * with the show() method, most of its configuration APIs are no-ops. This means that
    808      * we can't add logic that is specific to a certain test (such as dismissing a non-modal
    809      * popup window) once it's shown and we have a reference to a displayed ListPopupWindow.
    810      */
    811     public class Builder {
    812         private boolean mIsModal;
    813         private boolean mHasDismissListener;
    814         private boolean mHasItemClickListener;
    815         private boolean mHasItemSelectedListener;
    816         private boolean mIgnoreContentWidth;
    817         private int mHorizontalOffset;
    818         private int mVerticalOffset;
    819         private int mDropDownGravity;
    820         private int mAnchorId = R.id.anchor_upper;
    821         private int mContentRowLayoutId = android.R.layout.simple_list_item_1;
    822 
    823         private boolean mHasWindowLayoutType;
    824         private int mWindowLayoutType;
    825 
    826         private boolean mUseCustomPopupStyle;
    827         private int mPopupStyleAttr;
    828 
    829         private View mPromptView;
    830         private int mPromptPosition;
    831 
    832         private AdapterView.OnItemClickListener mOnItemClickListener;
    833         private AdapterView.OnItemSelectedListener mOnItemSelectedListener;
    834         private PopupWindow.OnDismissListener mOnDismissListener;
    835 
    836         public Builder() {
    837         }
    838 
    839         public Builder withAnchor(int anchorId) {
    840             mAnchorId = anchorId;
    841             return this;
    842         }
    843 
    844         public Builder withContentRowLayoutId(int contentRowLayoutId) {
    845             mContentRowLayoutId = contentRowLayoutId;
    846             return this;
    847         }
    848 
    849         public Builder withPopupStyleAttr(int popupStyleAttr) {
    850             mUseCustomPopupStyle = true;
    851             mPopupStyleAttr = popupStyleAttr;
    852             return this;
    853         }
    854 
    855         public Builder ignoreContentWidth() {
    856             mIgnoreContentWidth = true;
    857             return this;
    858         }
    859 
    860         public Builder setModal(boolean isModal) {
    861             mIsModal = isModal;
    862             return this;
    863         }
    864 
    865         public Builder withItemClickListener() {
    866             mHasItemClickListener = true;
    867             return this;
    868         }
    869 
    870         public Builder withItemSelectedListener() {
    871             mHasItemSelectedListener = true;
    872             return this;
    873         }
    874 
    875         public Builder withDismissListener() {
    876             mHasDismissListener = true;
    877             return this;
    878         }
    879 
    880         public Builder withWindowLayoutType(int windowLayoutType) {
    881             mHasWindowLayoutType = true;
    882             mWindowLayoutType = windowLayoutType;
    883             return this;
    884         }
    885 
    886         public Builder withHorizontalOffset(int horizontalOffset) {
    887             mHorizontalOffset = horizontalOffset;
    888             return this;
    889         }
    890 
    891         public Builder withVerticalOffset(int verticalOffset) {
    892             mVerticalOffset = verticalOffset;
    893             return this;
    894         }
    895 
    896         public Builder withDropDownGravity(int dropDownGravity) {
    897             mDropDownGravity = dropDownGravity;
    898             return this;
    899         }
    900 
    901         public Builder withPrompt(View promptView, int promptPosition) {
    902             mPromptView = promptView;
    903             mPromptPosition = promptPosition;
    904             return this;
    905         }
    906 
    907         private int getContentWidth(ListAdapter listAdapter, Drawable background) {
    908             if (listAdapter == null) {
    909                 return 0;
    910             }
    911 
    912             int width = 0;
    913             View itemView = null;
    914             int itemType = 0;
    915 
    916             for (int i = 0; i < listAdapter.getCount(); i++) {
    917                 final int positionType = listAdapter.getItemViewType(i);
    918                 if (positionType != itemType) {
    919                     itemType = positionType;
    920                     itemView = null;
    921                 }
    922                 itemView = listAdapter.getView(i, itemView, null);
    923                 if (itemView.getLayoutParams() == null) {
    924                     itemView.setLayoutParams(new ViewGroup.LayoutParams(
    925                             ViewGroup.LayoutParams.WRAP_CONTENT,
    926                             ViewGroup.LayoutParams.WRAP_CONTENT));
    927                 }
    928                 itemView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
    929                 width = Math.max(width, itemView.getMeasuredWidth());
    930             }
    931 
    932             // Add background padding to measured width
    933             if (background != null) {
    934                 final Rect rect = new Rect();
    935                 background.getPadding(rect);
    936                 width += rect.left + rect.right;
    937             }
    938 
    939             return width;
    940         }
    941 
    942         private void configure() {
    943             if (mUseCustomPopupStyle) {
    944                 mPopupWindow = new ListPopupWindow(mActivity, null, mPopupStyleAttr, 0);
    945             } else {
    946                 mPopupWindow = new ListPopupWindow(mActivity);
    947             }
    948             final String[] POPUP_CONTENT =
    949                     new String[]{"Alice", "Bob", "Charlie", "Deirdre", "El"};
    950             final BaseAdapter listPopupAdapter = new BaseAdapter() {
    951                 class ViewHolder {
    952                     private TextView title;
    953                 }
    954 
    955                 @Override
    956                 public int getCount() {
    957                     return POPUP_CONTENT.length;
    958                 }
    959 
    960                 @Override
    961                 public Object getItem(int position) {
    962                     return POPUP_CONTENT[position];
    963                 }
    964 
    965                 @Override
    966                 public long getItemId(int position) {
    967                     return position;
    968                 }
    969 
    970                 @Override
    971                 public View getView(int position, View convertView, ViewGroup parent) {
    972                     if (convertView == null) {
    973                         convertView = LayoutInflater.from(mActivity).inflate(
    974                                 mContentRowLayoutId, parent, false);
    975                         ViewHolder viewHolder = new ViewHolder();
    976                         viewHolder.title = (TextView) convertView.findViewById(android.R.id.text1);
    977                         convertView.setTag(viewHolder);
    978                     }
    979 
    980                     ViewHolder viewHolder = (ViewHolder) convertView.getTag();
    981                     viewHolder.title.setText(POPUP_CONTENT[position]);
    982                     return convertView;
    983                 }
    984             };
    985 
    986             mPopupWindow.setAdapter(listPopupAdapter);
    987             mPopupWindow.setAnchorView(mActivity.findViewById(mAnchorId));
    988 
    989             // The following mock listeners have to be set before the call to show() as
    990             // they are set on the internally constructed drop down.
    991             if (mHasItemClickListener) {
    992                 // Wrap our item click listener with a Mockito spy
    993                 mOnItemClickListener = spy(mItemClickListener);
    994                 // Register that spy as the item click listener on the ListPopupWindow
    995                 mPopupWindow.setOnItemClickListener(mOnItemClickListener);
    996                 // And configure Mockito to call our original listener with onItemClick.
    997                 // This way we can have both our item click listener running to dismiss the popup
    998                 // window, and track the invocations of onItemClick with Mockito APIs.
    999                 doCallRealMethod().when(mOnItemClickListener).onItemClick(
   1000                         any(AdapterView.class), any(View.class), any(int.class), any(int.class));
   1001             }
   1002 
   1003             if (mHasItemSelectedListener) {
   1004                 mOnItemSelectedListener = mock(AdapterView.OnItemSelectedListener.class);
   1005                 mPopupWindow.setOnItemSelectedListener(mOnItemSelectedListener);
   1006                 mPopupWindow.setListSelector(
   1007                         mActivity.getDrawable(R.drawable.red_translucent_fill));
   1008             }
   1009 
   1010             if (mHasDismissListener) {
   1011                 mOnDismissListener = mock(PopupWindow.OnDismissListener.class);
   1012                 mPopupWindow.setOnDismissListener(mOnDismissListener);
   1013             }
   1014 
   1015             mPopupWindow.setModal(mIsModal);
   1016             if (mHasWindowLayoutType) {
   1017                 mPopupWindow.setWindowLayoutType(mWindowLayoutType);
   1018             }
   1019 
   1020             if (!mIgnoreContentWidth) {
   1021                 mPopupWindow.setContentWidth(
   1022                         getContentWidth(listPopupAdapter, mPopupWindow.getBackground()));
   1023             }
   1024 
   1025             if (mHorizontalOffset != 0) {
   1026                 mPopupWindow.setHorizontalOffset(mHorizontalOffset);
   1027             }
   1028 
   1029             if (mVerticalOffset != 0) {
   1030                 mPopupWindow.setVerticalOffset(mVerticalOffset);
   1031             }
   1032 
   1033             if (mDropDownGravity != Gravity.NO_GRAVITY) {
   1034                 mPopupWindow.setDropDownGravity(mDropDownGravity);
   1035             }
   1036 
   1037             if (mPromptView != null) {
   1038                 mPopupWindow.setPromptPosition(mPromptPosition);
   1039                 mPopupWindow.setPromptView(mPromptView);
   1040             }
   1041         }
   1042 
   1043         private void show() {
   1044             configure();
   1045             mPopupWindow.show();
   1046             assertTrue(mPopupWindow.isShowing());
   1047         }
   1048 
   1049         private void showAgain() {
   1050             if (mPopupWindow == null || mPopupWindow.isShowing()) {
   1051                 return;
   1052             }
   1053             mPopupWindow.show();
   1054             assertTrue(mPopupWindow.isShowing());
   1055         }
   1056 
   1057         private void dismiss() {
   1058             if (mPopupWindow == null || !mPopupWindow.isShowing())
   1059                 return;
   1060             mPopupWindow.dismiss();
   1061         }
   1062     }
   1063 }
   1064