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