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