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