1 /* 2 * Copyright (C) 2007 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; 18 19 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; 20 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; 21 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME; 22 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH; 23 24 import android.annotation.NonNull; 25 import android.annotation.Nullable; 26 import android.compat.annotation.UnsupportedAppUsage; 27 import android.content.Context; 28 import android.content.res.TypedArray; 29 import android.graphics.PixelFormat; 30 import android.graphics.Rect; 31 import android.graphics.drawable.Drawable; 32 import android.graphics.drawable.StateListDrawable; 33 import android.os.Build; 34 import android.os.IBinder; 35 import android.transition.Transition; 36 import android.transition.Transition.EpicenterCallback; 37 import android.transition.Transition.TransitionListener; 38 import android.transition.TransitionInflater; 39 import android.transition.TransitionListenerAdapter; 40 import android.transition.TransitionManager; 41 import android.transition.TransitionSet; 42 import android.util.AttributeSet; 43 import android.view.Gravity; 44 import android.view.KeyEvent; 45 import android.view.KeyboardShortcutGroup; 46 import android.view.MotionEvent; 47 import android.view.View; 48 import android.view.View.OnAttachStateChangeListener; 49 import android.view.View.OnTouchListener; 50 import android.view.ViewGroup; 51 import android.view.ViewParent; 52 import android.view.ViewTreeObserver; 53 import android.view.ViewTreeObserver.OnGlobalLayoutListener; 54 import android.view.ViewTreeObserver.OnScrollChangedListener; 55 import android.view.WindowManager; 56 import android.view.WindowManager.LayoutParams; 57 import android.view.WindowManager.LayoutParams.SoftInputModeFlags; 58 import android.view.WindowManagerGlobal; 59 import android.window.OnBackInvokedCallback; 60 import android.window.OnBackInvokedDispatcher; 61 import android.window.WindowOnBackInvokedDispatcher; 62 63 import com.android.internal.R; 64 65 import java.lang.ref.WeakReference; 66 import java.util.List; 67 68 /** 69 * <p> 70 * This class represents a popup window that can be used to display an 71 * arbitrary view. The popup window is a floating container that appears on top 72 * of the current activity. 73 * </p> 74 * <a name="Animation"></a> 75 * <h3>Animation</h3> 76 * <p> 77 * On all versions of Android, popup window enter and exit animations may be 78 * specified by calling {@link #setAnimationStyle(int)} and passing the 79 * resource ID for an animation style that defines {@code windowEnterAnimation} 80 * and {@code windowExitAnimation}. For example, passing 81 * {@link android.R.style#Animation_Dialog} will give a scale and alpha 82 * animation. 83 * </br> 84 * A window animation style may also be specified in the popup window's style 85 * XML via the {@link android.R.styleable#PopupWindow_popupAnimationStyle popupAnimationStyle} 86 * attribute. 87 * </p> 88 * <p> 89 * Starting with API 23, more complex popup window enter and exit transitions 90 * may be specified by calling either {@link #setEnterTransition(Transition)} 91 * or {@link #setExitTransition(Transition)} and passing a {@link Transition}. 92 * </br> 93 * Popup enter and exit transitions may also be specified in the popup window's 94 * style XML via the {@link android.R.styleable#PopupWindow_popupEnterTransition popupEnterTransition} 95 * and {@link android.R.styleable#PopupWindow_popupExitTransition popupExitTransition} 96 * attributes, respectively. 97 * </p> 98 * 99 * @attr ref android.R.styleable#PopupWindow_overlapAnchor 100 * @attr ref android.R.styleable#PopupWindow_popupAnimationStyle 101 * @attr ref android.R.styleable#PopupWindow_popupBackground 102 * @attr ref android.R.styleable#PopupWindow_popupElevation 103 * @attr ref android.R.styleable#PopupWindow_popupEnterTransition 104 * @attr ref android.R.styleable#PopupWindow_popupExitTransition 105 * 106 * @see android.widget.AutoCompleteTextView 107 * @see android.widget.Spinner 108 */ 109 public class PopupWindow { 110 /** 111 * Mode for {@link #setInputMethodMode(int)}: the requirements for the 112 * input method should be based on the focusability of the popup. That is 113 * if it is focusable than it needs to work with the input method, else 114 * it doesn't. 115 */ 116 public static final int INPUT_METHOD_FROM_FOCUSABLE = 0; 117 118 /** 119 * Mode for {@link #setInputMethodMode(int)}: this popup always needs to 120 * work with an input method, regardless of whether it is focusable. This 121 * means that it will always be displayed so that the user can also operate 122 * the input method while it is shown. 123 */ 124 public static final int INPUT_METHOD_NEEDED = 1; 125 126 /** 127 * Mode for {@link #setInputMethodMode(int)}: this popup never needs to 128 * work with an input method, regardless of whether it is focusable. This 129 * means that it will always be displayed to use as much space on the 130 * screen as needed, regardless of whether this covers the input method. 131 */ 132 public static final int INPUT_METHOD_NOT_NEEDED = 2; 133 134 private static final int DEFAULT_ANCHORED_GRAVITY = Gravity.TOP | Gravity.START; 135 136 /** 137 * Default animation style indicating that separate animations should be 138 * used for top/bottom anchoring states. 139 */ 140 private static final int ANIMATION_STYLE_DEFAULT = -1; 141 142 private final int[] mTmpDrawingLocation = new int[2]; 143 private final int[] mTmpScreenLocation = new int[2]; 144 private final int[] mTmpAppLocation = new int[2]; 145 private final Rect mTempRect = new Rect(); 146 147 @UnsupportedAppUsage 148 private Context mContext; 149 @UnsupportedAppUsage 150 private WindowManager mWindowManager; 151 152 /** 153 * Keeps track of popup's parent's decor view. This is needed to dispatch 154 * requestKeyboardShortcuts to the owning Activity. 155 */ 156 private WeakReference<View> mParentRootView; 157 158 @UnsupportedAppUsage 159 private boolean mIsShowing; 160 private boolean mIsTransitioningToDismiss; 161 @UnsupportedAppUsage 162 private boolean mIsDropdown; 163 164 /** View that handles event dispatch and content transitions. */ 165 @UnsupportedAppUsage 166 private PopupDecorView mDecorView; 167 168 /** View that holds the background and may animate during a transition. */ 169 @UnsupportedAppUsage 170 private View mBackgroundView; 171 172 /** The contents of the popup. May be identical to the background view. */ 173 @UnsupportedAppUsage 174 private View mContentView; 175 176 private boolean mFocusable; 177 private int mInputMethodMode = INPUT_METHOD_FROM_FOCUSABLE; 178 @SoftInputModeFlags 179 private int mSoftInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED; 180 private boolean mTouchable = true; 181 private boolean mOutsideTouchable = false; 182 private boolean mClippingEnabled = true; 183 private int mSplitTouchEnabled = -1; 184 @UnsupportedAppUsage 185 private boolean mLayoutInScreen; 186 private boolean mClipToScreen; 187 private boolean mAllowScrollingAnchorParent = true; 188 private boolean mLayoutInsetDecor = false; 189 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 190 private boolean mNotTouchModal; 191 private boolean mAttachedInDecor = true; 192 private boolean mAttachedInDecorSet = false; 193 194 @UnsupportedAppUsage 195 private OnTouchListener mTouchInterceptor; 196 197 @UnsupportedAppUsage 198 private int mWidthMode; 199 private int mWidth = LayoutParams.WRAP_CONTENT; 200 @UnsupportedAppUsage 201 private int mLastWidth; 202 @UnsupportedAppUsage 203 private int mHeightMode; 204 private int mHeight = LayoutParams.WRAP_CONTENT; 205 @UnsupportedAppUsage 206 private int mLastHeight; 207 208 private float mElevation; 209 210 private Drawable mBackground; 211 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) 212 private Drawable mAboveAnchorBackgroundDrawable; 213 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) 214 private Drawable mBelowAnchorBackgroundDrawable; 215 216 private Transition mEnterTransition; 217 private Transition mExitTransition; 218 private Rect mEpicenterBounds; 219 220 @UnsupportedAppUsage 221 private boolean mAboveAnchor; 222 @UnsupportedAppUsage 223 private int mWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL; 224 225 @UnsupportedAppUsage 226 private OnDismissListener mOnDismissListener; 227 private boolean mIgnoreCheekPress = false; 228 229 @UnsupportedAppUsage 230 private int mAnimationStyle = ANIMATION_STYLE_DEFAULT; 231 232 private int mGravity = Gravity.NO_GRAVITY; 233 234 private static final int[] ABOVE_ANCHOR_STATE_SET = new int[] { 235 com.android.internal.R.attr.state_above_anchor 236 }; 237 238 private final OnAttachStateChangeListener mOnAnchorDetachedListener = 239 new OnAttachStateChangeListener() { 240 @Override 241 public void onViewAttachedToWindow(View v) { 242 // Anchor might have been reattached in a different position. 243 alignToAnchor(); 244 } 245 246 @Override 247 public void onViewDetachedFromWindow(View v) { 248 // Leave the popup in its current position. 249 // The anchor might become attached again. 250 } 251 }; 252 253 private final OnAttachStateChangeListener mOnAnchorRootDetachedListener = 254 new OnAttachStateChangeListener() { 255 @Override 256 public void onViewAttachedToWindow(View v) {} 257 258 @Override 259 public void onViewDetachedFromWindow(View v) { 260 mIsAnchorRootAttached = false; 261 } 262 }; 263 264 @UnsupportedAppUsage 265 private WeakReference<View> mAnchor; 266 private WeakReference<View> mAnchorRoot; 267 private boolean mIsAnchorRootAttached; 268 269 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 270 private final OnScrollChangedListener mOnScrollChangedListener = this::alignToAnchor; 271 272 private final View.OnLayoutChangeListener mOnLayoutChangeListener = 273 (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> alignToAnchor(); 274 275 private int mAnchorXoff; 276 private int mAnchorYoff; 277 private int mAnchoredGravity; 278 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 279 private boolean mOverlapAnchor; 280 281 private boolean mPopupViewInitialLayoutDirectionInherited; 282 283 private OnBackInvokedCallback mBackCallback; 284 285 /** 286 * <p>Create a new empty, non focusable popup window of dimension (0,0).</p> 287 * 288 * <p>The popup does provide a background.</p> 289 */ PopupWindow(Context context)290 public PopupWindow(Context context) { 291 this(context, null); 292 } 293 294 /** 295 * <p>Create a new empty, non focusable popup window of dimension (0,0).</p> 296 * 297 * <p>The popup does provide a background.</p> 298 */ PopupWindow(Context context, AttributeSet attrs)299 public PopupWindow(Context context, AttributeSet attrs) { 300 this(context, attrs, com.android.internal.R.attr.popupWindowStyle); 301 } 302 303 /** 304 * <p>Create a new empty, non focusable popup window of dimension (0,0).</p> 305 * 306 * <p>The popup does provide a background.</p> 307 */ PopupWindow(Context context, AttributeSet attrs, int defStyleAttr)308 public PopupWindow(Context context, AttributeSet attrs, int defStyleAttr) { 309 this(context, attrs, defStyleAttr, 0); 310 } 311 312 /** 313 * <p>Create a new, empty, non focusable popup window of dimension (0,0).</p> 314 * 315 * <p>The popup does not provide a background.</p> 316 */ PopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)317 public PopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 318 mContext = context; 319 mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 320 321 final TypedArray a = context.obtainStyledAttributes( 322 attrs, R.styleable.PopupWindow, defStyleAttr, defStyleRes); 323 final Drawable bg = a.getDrawable(R.styleable.PopupWindow_popupBackground); 324 mElevation = a.getDimension(R.styleable.PopupWindow_popupElevation, 0); 325 mOverlapAnchor = a.getBoolean(R.styleable.PopupWindow_overlapAnchor, false); 326 327 // Preserve default behavior from Gingerbread. If the animation is 328 // undefined or explicitly specifies the Gingerbread animation style, 329 // use a sentinel value. 330 if (a.hasValueOrEmpty(R.styleable.PopupWindow_popupAnimationStyle)) { 331 final int animStyle = a.getResourceId(R.styleable.PopupWindow_popupAnimationStyle, 0); 332 if (animStyle == R.style.Animation_PopupWindow) { 333 mAnimationStyle = ANIMATION_STYLE_DEFAULT; 334 } else { 335 mAnimationStyle = animStyle; 336 } 337 } else { 338 mAnimationStyle = ANIMATION_STYLE_DEFAULT; 339 } 340 341 final Transition enterTransition = getTransition(a.getResourceId( 342 R.styleable.PopupWindow_popupEnterTransition, 0)); 343 final Transition exitTransition; 344 if (a.hasValueOrEmpty(R.styleable.PopupWindow_popupExitTransition)) { 345 exitTransition = getTransition(a.getResourceId( 346 R.styleable.PopupWindow_popupExitTransition, 0)); 347 } else { 348 exitTransition = enterTransition == null ? null : enterTransition.clone(); 349 } 350 351 a.recycle(); 352 353 setEnterTransition(enterTransition); 354 setExitTransition(exitTransition); 355 setBackgroundDrawable(bg); 356 } 357 358 /** 359 * <p>Create a new empty, non focusable popup window of dimension (0,0).</p> 360 * 361 * <p>The popup does not provide any background. This should be handled 362 * by the content view.</p> 363 */ PopupWindow()364 public PopupWindow() { 365 this(null, 0, 0); 366 } 367 368 /** 369 * <p>Create a new non focusable popup window which can display the 370 * <tt>contentView</tt>. The dimension of the window are (0,0).</p> 371 * 372 * <p>The popup does not provide any background. This should be handled 373 * by the content view.</p> 374 * 375 * @param contentView the popup's content 376 */ PopupWindow(View contentView)377 public PopupWindow(View contentView) { 378 this(contentView, 0, 0); 379 } 380 381 /** 382 * <p>Create a new empty, non focusable popup window. The dimension of the 383 * window must be passed to this constructor.</p> 384 * 385 * <p>The popup does not provide any background. This should be handled 386 * by the content view.</p> 387 * 388 * @param width the popup's width 389 * @param height the popup's height 390 */ PopupWindow(int width, int height)391 public PopupWindow(int width, int height) { 392 this(null, width, height); 393 } 394 395 /** 396 * <p>Create a new non focusable popup window which can display the 397 * <tt>contentView</tt>. The dimension of the window must be passed to 398 * this constructor.</p> 399 * 400 * <p>The popup does not provide any background. This should be handled 401 * by the content view.</p> 402 * 403 * @param contentView the popup's content 404 * @param width the popup's width 405 * @param height the popup's height 406 */ PopupWindow(View contentView, int width, int height)407 public PopupWindow(View contentView, int width, int height) { 408 this(contentView, width, height, false); 409 } 410 411 /** 412 * <p>Create a new popup window which can display the <tt>contentView</tt>. 413 * The dimension of the window must be passed to this constructor.</p> 414 * 415 * <p>The popup does not provide any background. This should be handled 416 * by the content view.</p> 417 * 418 * @param contentView the popup's content 419 * @param width the popup's width 420 * @param height the popup's height 421 * @param focusable true if the popup can be focused, false otherwise 422 */ PopupWindow(View contentView, int width, int height, boolean focusable)423 public PopupWindow(View contentView, int width, int height, boolean focusable) { 424 if (contentView != null) { 425 mContext = contentView.getContext(); 426 mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); 427 } 428 429 setContentView(contentView); 430 setWidth(width); 431 setHeight(height); 432 setFocusable(focusable); 433 } 434 435 /** 436 * Sets the enter transition to be used when the popup window is shown. 437 * 438 * @param enterTransition the enter transition, or {@code null} to clear 439 * @see #getEnterTransition() 440 * @attr ref android.R.styleable#PopupWindow_popupEnterTransition 441 */ setEnterTransition(@ullable Transition enterTransition)442 public void setEnterTransition(@Nullable Transition enterTransition) { 443 mEnterTransition = enterTransition; 444 } 445 446 /** 447 * Returns the enter transition to be used when the popup window is shown. 448 * 449 * @return the enter transition, or {@code null} if not set 450 * @see #setEnterTransition(Transition) 451 * @attr ref android.R.styleable#PopupWindow_popupEnterTransition 452 */ 453 @Nullable getEnterTransition()454 public Transition getEnterTransition() { 455 return mEnterTransition; 456 } 457 458 /** 459 * Sets the exit transition to be used when the popup window is dismissed. 460 * 461 * @param exitTransition the exit transition, or {@code null} to clear 462 * @see #getExitTransition() 463 * @attr ref android.R.styleable#PopupWindow_popupExitTransition 464 */ setExitTransition(@ullable Transition exitTransition)465 public void setExitTransition(@Nullable Transition exitTransition) { 466 mExitTransition = exitTransition; 467 } 468 469 /** 470 * Returns the exit transition to be used when the popup window is 471 * dismissed. 472 * 473 * @return the exit transition, or {@code null} if not set 474 * @see #setExitTransition(Transition) 475 * @attr ref android.R.styleable#PopupWindow_popupExitTransition 476 */ 477 @Nullable getExitTransition()478 public Transition getExitTransition() { 479 return mExitTransition; 480 } 481 482 /** 483 * <p>Returns bounds which are used as a center of the enter and exit transitions.<p/> 484 * 485 * <p>Transitions use Rect, referred to as the epicenter, to orient 486 * the direction of travel. For popup windows, the anchor view bounds are 487 * used as the default epicenter.</p> 488 * 489 * <p>See {@link Transition#setEpicenterCallback(EpicenterCallback)} for more 490 * information about how transition epicenters work.</p> 491 * 492 * @return bounds relative to anchor view, or {@code null} if not set 493 * @see #setEpicenterBounds(Rect) 494 */ 495 @Nullable getEpicenterBounds()496 public Rect getEpicenterBounds() { 497 return mEpicenterBounds != null ? new Rect(mEpicenterBounds) : null; 498 } 499 500 /** 501 * <p>Sets the bounds used as the epicenter of the enter and exit transitions.</p> 502 * 503 * <p>Transitions use Rect, referred to as the epicenter, to orient 504 * the direction of travel. For popup windows, the anchor view bounds are 505 * used as the default epicenter.</p> 506 * 507 * <p>See {@link Transition#setEpicenterCallback(EpicenterCallback)} for more 508 * information about how transition epicenters work.</p> 509 * 510 * @param bounds the epicenter bounds relative to the anchor view, or 511 * {@code null} to use the default epicenter 512 * 513 * @see #getEpicenterBounds() 514 */ setEpicenterBounds(@ullable Rect bounds)515 public void setEpicenterBounds(@Nullable Rect bounds) { 516 mEpicenterBounds = bounds != null ? new Rect(bounds) : null; 517 } 518 getTransition(int resId)519 private Transition getTransition(int resId) { 520 if (resId != 0 && resId != R.transition.no_transition) { 521 final TransitionInflater inflater = TransitionInflater.from(mContext); 522 final Transition transition = inflater.inflateTransition(resId); 523 if (transition != null) { 524 final boolean isEmpty = transition instanceof TransitionSet 525 && ((TransitionSet) transition).getTransitionCount() == 0; 526 if (!isEmpty) { 527 return transition; 528 } 529 } 530 } 531 return null; 532 } 533 534 /** 535 * Return the drawable used as the popup window's background. 536 * 537 * @return the background drawable or {@code null} if not set 538 * @see #setBackgroundDrawable(Drawable) 539 * @attr ref android.R.styleable#PopupWindow_popupBackground 540 */ getBackground()541 public Drawable getBackground() { 542 return mBackground; 543 } 544 545 /** 546 * Specifies the background drawable for this popup window. The background 547 * can be set to {@code null}. 548 * 549 * @param background the popup's background 550 * @see #getBackground() 551 * @attr ref android.R.styleable#PopupWindow_popupBackground 552 */ setBackgroundDrawable(Drawable background)553 public void setBackgroundDrawable(Drawable background) { 554 mBackground = background; 555 556 // If this is a StateListDrawable, try to find and store the drawable to be 557 // used when the drop-down is placed above its anchor view, and the one to be 558 // used when the drop-down is placed below its anchor view. We extract 559 // the drawables ourselves to work around a problem with using refreshDrawableState 560 // that it will take into account the padding of all drawables specified in a 561 // StateListDrawable, thus adding superfluous padding to drop-down views. 562 // 563 // We assume a StateListDrawable will have a drawable for ABOVE_ANCHOR_STATE_SET and 564 // at least one other drawable, intended for the 'below-anchor state'. 565 if (mBackground instanceof StateListDrawable) { 566 StateListDrawable stateList = (StateListDrawable) mBackground; 567 568 // Find the above-anchor view - this one's easy, it should be labeled as such. 569 int aboveAnchorStateIndex = stateList.findStateDrawableIndex(ABOVE_ANCHOR_STATE_SET); 570 571 // Now, for the below-anchor view, look for any other drawable specified in the 572 // StateListDrawable which is not for the above-anchor state and use that. 573 int count = stateList.getStateCount(); 574 int belowAnchorStateIndex = -1; 575 for (int i = 0; i < count; i++) { 576 if (i != aboveAnchorStateIndex) { 577 belowAnchorStateIndex = i; 578 break; 579 } 580 } 581 582 // Store the drawables we found, if we found them. Otherwise, set them both 583 // to null so that we'll just use refreshDrawableState. 584 if (aboveAnchorStateIndex != -1 && belowAnchorStateIndex != -1) { 585 mAboveAnchorBackgroundDrawable = stateList.getStateDrawable(aboveAnchorStateIndex); 586 mBelowAnchorBackgroundDrawable = stateList.getStateDrawable(belowAnchorStateIndex); 587 } else { 588 mBelowAnchorBackgroundDrawable = null; 589 mAboveAnchorBackgroundDrawable = null; 590 } 591 } 592 } 593 594 /** 595 * @return the elevation for this popup window in pixels 596 * @see #setElevation(float) 597 * @attr ref android.R.styleable#PopupWindow_popupElevation 598 */ getElevation()599 public float getElevation() { 600 return mElevation; 601 } 602 603 /** 604 * Specifies the elevation for this popup window. 605 * 606 * @param elevation the popup's elevation in pixels 607 * @see #getElevation() 608 * @attr ref android.R.styleable#PopupWindow_popupElevation 609 */ setElevation(float elevation)610 public void setElevation(float elevation) { 611 mElevation = elevation; 612 } 613 614 /** 615 * <p>Return the animation style to use the popup appears and disappears</p> 616 * 617 * @return the animation style to use the popup appears and disappears 618 */ getAnimationStyle()619 public int getAnimationStyle() { 620 return mAnimationStyle; 621 } 622 623 /** 624 * Set the flag on popup to ignore cheek press events; by default this flag 625 * is set to false 626 * which means the popup will not ignore cheek press dispatch events. 627 * 628 * <p>If the popup is showing, calling this method will take effect only 629 * the next time the popup is shown or through a manual call to one of 630 * the {@link #update()} methods.</p> 631 * 632 * @see #update() 633 */ setIgnoreCheekPress()634 public void setIgnoreCheekPress() { 635 mIgnoreCheekPress = true; 636 } 637 638 /** 639 * <p>Change the animation style resource for this popup.</p> 640 * 641 * <p>If the popup is showing, calling this method will take effect only 642 * the next time the popup is shown or through a manual call to one of 643 * the {@link #update()} methods.</p> 644 * 645 * @param animationStyle animation style to use when the popup appears 646 * and disappears. Set to -1 for the default animation, 0 for no 647 * animation, or a resource identifier for an explicit animation. 648 * 649 * @see #update() 650 */ setAnimationStyle(int animationStyle)651 public void setAnimationStyle(int animationStyle) { 652 mAnimationStyle = animationStyle; 653 } 654 655 /** 656 * <p>Return the view used as the content of the popup window.</p> 657 * 658 * @return a {@link android.view.View} representing the popup's content 659 * 660 * @see #setContentView(android.view.View) 661 */ getContentView()662 public View getContentView() { 663 return mContentView; 664 } 665 666 /** 667 * <p>Change the popup's content. The content is represented by an instance 668 * of {@link android.view.View}.</p> 669 * 670 * <p>This method has no effect if called when the popup is showing.</p> 671 * 672 * @param contentView the new content for the popup 673 * 674 * @see #getContentView() 675 * @see #isShowing() 676 */ setContentView(View contentView)677 public void setContentView(View contentView) { 678 if (isShowing()) { 679 return; 680 } 681 682 mContentView = contentView; 683 684 if (mContext == null && mContentView != null) { 685 mContext = mContentView.getContext(); 686 } 687 688 if (mWindowManager == null && mContentView != null) { 689 mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); 690 } 691 692 // Setting the default for attachedInDecor based on SDK version here 693 // instead of in the constructor since we might not have the context 694 // object in the constructor. We only want to set default here if the 695 // app hasn't already set the attachedInDecor. 696 if (mContext != null && !mAttachedInDecorSet) { 697 // Attach popup window in decor frame of parent window by default for 698 // {@link Build.VERSION_CODES.LOLLIPOP_MR1} or greater. Keep current 699 // behavior of not attaching to decor frame for older SDKs. 700 setAttachedInDecor(mContext.getApplicationInfo().targetSdkVersion 701 >= Build.VERSION_CODES.LOLLIPOP_MR1); 702 } 703 704 } 705 706 /** 707 * Set a callback for all touch events being dispatched to the popup 708 * window. 709 */ setTouchInterceptor(OnTouchListener l)710 public void setTouchInterceptor(OnTouchListener l) { 711 mTouchInterceptor = l; 712 } 713 714 /** 715 * <p>Indicate whether the popup window can grab the focus.</p> 716 * 717 * @return true if the popup is focusable, false otherwise 718 * 719 * @see #setFocusable(boolean) 720 */ isFocusable()721 public boolean isFocusable() { 722 return mFocusable; 723 } 724 725 /** 726 * <p>Changes the focusability of the popup window. When focusable, the 727 * window will grab the focus from the current focused widget if the popup 728 * contains a focusable {@link android.view.View}. By default a popup 729 * window is not focusable.</p> 730 * 731 * <p>If the popup is showing, calling this method will take effect only 732 * the next time the popup is shown or through a manual call to one of 733 * the {@link #update()} methods.</p> 734 * 735 * @param focusable true if the popup should grab focus, false otherwise. 736 * 737 * @see #isFocusable() 738 * @see #isShowing() 739 * @see #update() 740 */ setFocusable(boolean focusable)741 public void setFocusable(boolean focusable) { 742 mFocusable = focusable; 743 } 744 745 /** 746 * Return the current value in {@link #setInputMethodMode(int)}. 747 * 748 * @see #setInputMethodMode(int) 749 */ getInputMethodMode()750 public int getInputMethodMode() { 751 return mInputMethodMode; 752 753 } 754 755 /** 756 * Control how the popup operates with an input method: one of 757 * {@link #INPUT_METHOD_FROM_FOCUSABLE}, {@link #INPUT_METHOD_NEEDED}, 758 * or {@link #INPUT_METHOD_NOT_NEEDED}. 759 * 760 * <p>If the popup is showing, calling this method will take effect only 761 * the next time the popup is shown or through a manual call to one of 762 * the {@link #update()} methods.</p> 763 * 764 * @see #getInputMethodMode() 765 * @see #update() 766 */ setInputMethodMode(int mode)767 public void setInputMethodMode(int mode) { 768 mInputMethodMode = mode; 769 } 770 771 /** 772 * Sets the operating mode for the soft input area. 773 * 774 * @param mode The desired mode, see 775 * {@link android.view.WindowManager.LayoutParams#softInputMode} 776 * for the full list 777 * 778 * @see android.view.WindowManager.LayoutParams#softInputMode 779 * @see #getSoftInputMode() 780 */ setSoftInputMode(@oftInputModeFlags int mode)781 public void setSoftInputMode(@SoftInputModeFlags int mode) { 782 mSoftInputMode = mode; 783 } 784 785 /** 786 * Returns the current value in {@link #setSoftInputMode(int)}. 787 * 788 * @see #setSoftInputMode(int) 789 * @see android.view.WindowManager.LayoutParams#softInputMode 790 */ 791 @SoftInputModeFlags getSoftInputMode()792 public int getSoftInputMode() { 793 return mSoftInputMode; 794 } 795 796 /** 797 * <p>Indicates whether the popup window receives touch events.</p> 798 * 799 * @return true if the popup is touchable, false otherwise 800 * 801 * @see #setTouchable(boolean) 802 */ isTouchable()803 public boolean isTouchable() { 804 return mTouchable; 805 } 806 807 /** 808 * <p>Changes the touchability of the popup window. When touchable, the 809 * window will receive touch events, otherwise touch events will go to the 810 * window below it. By default the window is touchable.</p> 811 * 812 * <p>If the popup is showing, calling this method will take effect only 813 * the next time the popup is shown or through a manual call to one of 814 * the {@link #update()} methods.</p> 815 * 816 * @param touchable true if the popup should receive touch events, false otherwise 817 * 818 * @see #isTouchable() 819 * @see #isShowing() 820 * @see #update() 821 */ setTouchable(boolean touchable)822 public void setTouchable(boolean touchable) { 823 mTouchable = touchable; 824 } 825 826 /** 827 * <p>Indicates whether the popup window will be informed of touch events 828 * outside of its window.</p> 829 * 830 * @return true if the popup is outside touchable, false otherwise 831 * 832 * @see #setOutsideTouchable(boolean) 833 */ isOutsideTouchable()834 public boolean isOutsideTouchable() { 835 return mOutsideTouchable; 836 } 837 838 /** 839 * <p>Controls whether the pop-up will be informed of touch events outside 840 * of its window. This only makes sense for pop-ups that are touchable 841 * but not focusable, which means touches outside of the window will 842 * be delivered to the window behind. The default is false.</p> 843 * 844 * <p>If the popup is showing, calling this method will take effect only 845 * the next time the popup is shown or through a manual call to one of 846 * the {@link #update()} methods.</p> 847 * 848 * @param touchable true if the popup should receive outside 849 * touch events, false otherwise 850 * 851 * @see #isOutsideTouchable() 852 * @see #isShowing() 853 * @see #update() 854 */ setOutsideTouchable(boolean touchable)855 public void setOutsideTouchable(boolean touchable) { 856 mOutsideTouchable = touchable; 857 } 858 859 /** 860 * <p>Indicates whether clipping of the popup window is enabled.</p> 861 * 862 * @return true if the clipping is enabled, false otherwise 863 * 864 * @see #setClippingEnabled(boolean) 865 */ isClippingEnabled()866 public boolean isClippingEnabled() { 867 return mClippingEnabled; 868 } 869 870 /** 871 * <p>Allows the popup window to extend beyond the bounds of the screen. By default the 872 * window is clipped to the screen boundaries. Setting this to false will allow windows to be 873 * accurately positioned.</p> 874 * 875 * <p>If the popup is showing, calling this method will take effect only 876 * the next time the popup is shown or through a manual call to one of 877 * the {@link #update()} methods.</p> 878 * 879 * @param enabled false if the window should be allowed to extend outside of the screen 880 * @see #isShowing() 881 * @see #isClippingEnabled() 882 * @see #update() 883 */ setClippingEnabled(boolean enabled)884 public void setClippingEnabled(boolean enabled) { 885 mClippingEnabled = enabled; 886 } 887 888 /** 889 * <p>Indicates whether this popup will be clipped to the screen and not to the 890 * containing window<p/> 891 * 892 * @return true if popup will be clipped to the screen instead of the window, false otherwise 893 * @deprecated Use {@link #isClippedToScreen()} instead 894 * @removed 895 */ 896 @Deprecated isClipToScreenEnabled()897 public boolean isClipToScreenEnabled() { 898 return mClipToScreen; 899 } 900 901 /** 902 * <p>Clip this popup window to the screen, but not to the containing window.</p> 903 * 904 * <p>If the popup is showing, calling this method will take effect only 905 * the next time the popup is shown or through a manual call to one of 906 * the {@link #update()} methods.</p> 907 * 908 * @deprecated Use {@link #setIsClippedToScreen(boolean)} instead 909 * @removed 910 */ 911 @Deprecated setClipToScreenEnabled(boolean enabled)912 public void setClipToScreenEnabled(boolean enabled) { 913 mClipToScreen = enabled; 914 } 915 916 /** 917 * <p>Indicates whether this popup will be clipped to the screen and not to the 918 * containing window<p/> 919 * 920 * @return true if popup will be clipped to the screen instead of the window, false otherwise 921 * 922 * @see #setIsClippedToScreen(boolean) 923 */ isClippedToScreen()924 public boolean isClippedToScreen() { 925 return mClipToScreen; 926 } 927 928 /** 929 * <p>Clip this popup window to the screen, but not to the containing window.</p> 930 * 931 * <p>If the popup is showing, calling this method will take effect only 932 * the next time the popup is shown or through a manual call to one of 933 * the {@link #update()} methods.</p> 934 * 935 * @param enabled true to clip to the screen. 936 * 937 * @see #isClippedToScreen() 938 */ setIsClippedToScreen(boolean enabled)939 public void setIsClippedToScreen(boolean enabled) { 940 mClipToScreen = enabled; 941 } 942 943 /** 944 * Allow PopupWindow to scroll the anchor's parent to provide more room 945 * for the popup. Enabled by default. 946 * 947 * @param enabled True to scroll the anchor's parent when more room is desired by the popup. 948 */ 949 @UnsupportedAppUsage setAllowScrollingAnchorParent(boolean enabled)950 void setAllowScrollingAnchorParent(boolean enabled) { 951 mAllowScrollingAnchorParent = enabled; 952 } 953 954 /** @hide */ getAllowScrollingAnchorParent()955 protected final boolean getAllowScrollingAnchorParent() { 956 return mAllowScrollingAnchorParent; 957 } 958 959 /** 960 * <p>Indicates whether the popup window supports splitting touches.</p> 961 * 962 * @return true if the touch splitting is enabled, false otherwise 963 * 964 * @see #setSplitTouchEnabled(boolean) 965 */ isSplitTouchEnabled()966 public boolean isSplitTouchEnabled() { 967 if (mSplitTouchEnabled < 0 && mContext != null) { 968 return mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB; 969 } 970 return mSplitTouchEnabled == 1; 971 } 972 973 /** 974 * <p>Allows the popup window to split touches across other windows that also 975 * support split touch. When this flag is false, the first pointer 976 * that goes down determines the window to which all subsequent touches 977 * go until all pointers go up. When this flag is true, each pointer 978 * (not necessarily the first) that goes down determines the window 979 * to which all subsequent touches of that pointer will go until that 980 * pointer goes up thereby enabling touches with multiple pointers 981 * to be split across multiple windows.</p> 982 * 983 * @param enabled true if the split touches should be enabled, false otherwise 984 * @see #isSplitTouchEnabled() 985 */ setSplitTouchEnabled(boolean enabled)986 public void setSplitTouchEnabled(boolean enabled) { 987 mSplitTouchEnabled = enabled ? 1 : 0; 988 } 989 990 /** 991 * <p>Indicates whether the popup window will be forced into using absolute screen coordinates 992 * for positioning.</p> 993 * 994 * @return true if the window will always be positioned in screen coordinates. 995 * 996 * @deprecated Use {@link #isLaidOutInScreen()} instead 997 * @removed 998 */ 999 @Deprecated isLayoutInScreenEnabled()1000 public boolean isLayoutInScreenEnabled() { 1001 return mLayoutInScreen; 1002 } 1003 1004 /** 1005 * <p>Allows the popup window to force the flag 1006 * {@link WindowManager.LayoutParams#FLAG_LAYOUT_IN_SCREEN}, overriding default behavior. 1007 * This will cause the popup to be positioned in absolute screen coordinates.</p> 1008 * 1009 * @param enabled true if the popup should always be positioned in screen coordinates 1010 * @deprecated Use {@link #setIsLaidOutInScreen(boolean)} instead 1011 * @removed 1012 */ 1013 @Deprecated setLayoutInScreenEnabled(boolean enabled)1014 public void setLayoutInScreenEnabled(boolean enabled) { 1015 mLayoutInScreen = enabled; 1016 } 1017 1018 /** 1019 * <p>Indicates whether the popup window will be forced into using absolute screen coordinates 1020 * for positioning.</p> 1021 * 1022 * @return true if the window will always be positioned in screen coordinates. 1023 * 1024 * @see #setIsLaidOutInScreen(boolean) 1025 */ isLaidOutInScreen()1026 public boolean isLaidOutInScreen() { 1027 return mLayoutInScreen; 1028 } 1029 1030 /** 1031 * <p>Allows the popup window to force the flag 1032 * {@link WindowManager.LayoutParams#FLAG_LAYOUT_IN_SCREEN}, overriding default behavior. 1033 * This will cause the popup to be positioned in absolute screen coordinates.</p> 1034 * 1035 * @param enabled true if the popup should always be positioned in screen coordinates 1036 * 1037 * @see #isLaidOutInScreen() 1038 */ setIsLaidOutInScreen(boolean enabled)1039 public void setIsLaidOutInScreen(boolean enabled) { 1040 mLayoutInScreen = enabled; 1041 } 1042 1043 /** 1044 * <p>Indicates whether the popup window will be attached in the decor frame of its parent 1045 * window. 1046 * 1047 * @return true if the window will be attached to the decor frame of its parent window. 1048 * 1049 * @see #setAttachedInDecor(boolean) 1050 * @see WindowManager.LayoutParams#FLAG_LAYOUT_ATTACHED_IN_DECOR 1051 */ isAttachedInDecor()1052 public boolean isAttachedInDecor() { 1053 return mAttachedInDecor; 1054 } 1055 1056 /** 1057 * <p>This will attach the popup window to the decor frame of the parent window to avoid 1058 * overlaping with screen decorations like the navigation bar. Overrides the default behavior of 1059 * the flag {@link WindowManager.LayoutParams#FLAG_LAYOUT_ATTACHED_IN_DECOR}. 1060 * 1061 * <p>By default the flag is set on SDK version {@link Build.VERSION_CODES#LOLLIPOP_MR1} or 1062 * greater and cleared on lesser SDK versions. 1063 * 1064 * @param enabled true if the popup should be attached to the decor frame of its parent window. 1065 * 1066 * @see WindowManager.LayoutParams#FLAG_LAYOUT_ATTACHED_IN_DECOR 1067 */ setAttachedInDecor(boolean enabled)1068 public void setAttachedInDecor(boolean enabled) { 1069 mAttachedInDecor = enabled; 1070 mAttachedInDecorSet = true; 1071 } 1072 1073 /** 1074 * Allows the popup window to force the flag 1075 * {@link WindowManager.LayoutParams#FLAG_LAYOUT_INSET_DECOR}, overriding default behavior. 1076 * This will cause the popup to inset its content to account for system windows overlaying 1077 * the screen, such as the status bar. 1078 * 1079 * <p>This will often be combined with {@link #setIsLaidOutInScreen(boolean)}. 1080 * 1081 * @param enabled true if the popup's views should inset content to account for system windows, 1082 * the way that decor views behave for full-screen windows. 1083 * @hide 1084 */ 1085 @UnsupportedAppUsage setLayoutInsetDecor(boolean enabled)1086 public void setLayoutInsetDecor(boolean enabled) { 1087 mLayoutInsetDecor = enabled; 1088 } 1089 1090 /** @hide */ isLayoutInsetDecor()1091 protected final boolean isLayoutInsetDecor() { 1092 return mLayoutInsetDecor; 1093 } 1094 1095 /** 1096 * Set the layout type for this window. 1097 * <p> 1098 * See {@link WindowManager.LayoutParams#type} for possible values. 1099 * 1100 * @param layoutType Layout type for this window. 1101 * 1102 * @see WindowManager.LayoutParams#type 1103 */ setWindowLayoutType(int layoutType)1104 public void setWindowLayoutType(int layoutType) { 1105 mWindowLayoutType = layoutType; 1106 } 1107 1108 /** 1109 * Returns the layout type for this window. 1110 * 1111 * @see #setWindowLayoutType(int) 1112 */ getWindowLayoutType()1113 public int getWindowLayoutType() { 1114 return mWindowLayoutType; 1115 } 1116 1117 /** 1118 * <p>Indicates whether outside touches will be sent to this window 1119 * or other windows behind it<p/> 1120 * 1121 * @return true if touches will be sent to this window, false otherwise 1122 * 1123 * @see #setTouchModal(boolean) 1124 */ isTouchModal()1125 public boolean isTouchModal() { 1126 return !mNotTouchModal; 1127 } 1128 1129 /** 1130 * <p>Set whether this window is touch modal or if outside touches will be sent to 1131 * other windows behind it.<p/> 1132 * 1133 * <p>If the popup is showing, calling this method will take effect only 1134 * the next time the popup is shown or through a manual call to one of 1135 * the {@link #update()} methods.</p> 1136 * 1137 * @param touchModal true to sent all outside touches to this window, 1138 * false to other windows behind it 1139 * 1140 * @see #isTouchModal() 1141 */ setTouchModal(boolean touchModal)1142 public void setTouchModal(boolean touchModal) { 1143 mNotTouchModal = !touchModal; 1144 } 1145 1146 /** 1147 * <p>Change the width and height measure specs that are given to the 1148 * window manager by the popup. By default these are 0, meaning that 1149 * the current width or height is requested as an explicit size from 1150 * the window manager. You can supply 1151 * {@link ViewGroup.LayoutParams#WRAP_CONTENT} or 1152 * {@link ViewGroup.LayoutParams#MATCH_PARENT} to have that measure 1153 * spec supplied instead, replacing the absolute width and height that 1154 * has been set in the popup.</p> 1155 * 1156 * <p>If the popup is showing, calling this method will take effect only 1157 * the next time the popup is shown.</p> 1158 * 1159 * @param widthSpec an explicit width measure spec mode, either 1160 * {@link ViewGroup.LayoutParams#WRAP_CONTENT}, 1161 * {@link ViewGroup.LayoutParams#MATCH_PARENT}, or 0 to use the absolute 1162 * width. 1163 * @param heightSpec an explicit height measure spec mode, either 1164 * {@link ViewGroup.LayoutParams#WRAP_CONTENT}, 1165 * {@link ViewGroup.LayoutParams#MATCH_PARENT}, or 0 to use the absolute 1166 * height. 1167 * 1168 * @deprecated Use {@link #setWidth(int)} and {@link #setHeight(int)}. 1169 */ 1170 @Deprecated setWindowLayoutMode(int widthSpec, int heightSpec)1171 public void setWindowLayoutMode(int widthSpec, int heightSpec) { 1172 mWidthMode = widthSpec; 1173 mHeightMode = heightSpec; 1174 } 1175 1176 /** 1177 * Returns the popup's requested height. May be a layout constant such as 1178 * {@link LayoutParams#WRAP_CONTENT} or {@link LayoutParams#MATCH_PARENT}. 1179 * <p> 1180 * The actual size of the popup may depend on other factors such as 1181 * clipping and window layout. 1182 * 1183 * @return the popup height in pixels or a layout constant 1184 * @see #setHeight(int) 1185 */ getHeight()1186 public int getHeight() { 1187 return mHeight; 1188 } 1189 1190 /** 1191 * Sets the popup's requested height. May be a layout constant such as 1192 * {@link LayoutParams#WRAP_CONTENT} or {@link LayoutParams#MATCH_PARENT}. 1193 * <p> 1194 * The actual size of the popup may depend on other factors such as 1195 * clipping and window layout. 1196 * <p> 1197 * If the popup is showing, calling this method will take effect the next 1198 * time the popup is shown. 1199 * 1200 * @param height the popup height in pixels or a layout constant 1201 * @see #getHeight() 1202 * @see #isShowing() 1203 */ setHeight(int height)1204 public void setHeight(int height) { 1205 mHeight = height; 1206 } 1207 1208 /** 1209 * Returns the popup's requested width. May be a layout constant such as 1210 * {@link LayoutParams#WRAP_CONTENT} or {@link LayoutParams#MATCH_PARENT}. 1211 * <p> 1212 * The actual size of the popup may depend on other factors such as 1213 * clipping and window layout. 1214 * 1215 * @return the popup width in pixels or a layout constant 1216 * @see #setWidth(int) 1217 */ getWidth()1218 public int getWidth() { 1219 return mWidth; 1220 } 1221 1222 /** 1223 * Sets the popup's requested width. May be a layout constant such as 1224 * {@link LayoutParams#WRAP_CONTENT} or {@link LayoutParams#MATCH_PARENT}. 1225 * <p> 1226 * The actual size of the popup may depend on other factors such as 1227 * clipping and window layout. 1228 * <p> 1229 * If the popup is showing, calling this method will take effect the next 1230 * time the popup is shown. 1231 * 1232 * @param width the popup width in pixels or a layout constant 1233 * @see #getWidth() 1234 * @see #isShowing() 1235 */ setWidth(int width)1236 public void setWidth(int width) { 1237 mWidth = width; 1238 } 1239 1240 /** 1241 * Sets whether the popup window should overlap its anchor view when 1242 * displayed as a drop-down. 1243 * <p> 1244 * If the popup is showing, calling this method will take effect only 1245 * the next time the popup is shown. 1246 * 1247 * @param overlapAnchor Whether the popup should overlap its anchor. 1248 * 1249 * @see #getOverlapAnchor() 1250 * @see #isShowing() 1251 */ setOverlapAnchor(boolean overlapAnchor)1252 public void setOverlapAnchor(boolean overlapAnchor) { 1253 mOverlapAnchor = overlapAnchor; 1254 } 1255 1256 /** 1257 * Returns whether the popup window should overlap its anchor view when 1258 * displayed as a drop-down. 1259 * 1260 * @return Whether the popup should overlap its anchor. 1261 * 1262 * @see #setOverlapAnchor(boolean) 1263 */ getOverlapAnchor()1264 public boolean getOverlapAnchor() { 1265 return mOverlapAnchor; 1266 } 1267 1268 /** 1269 * <p>Indicate whether this popup window is showing on screen.</p> 1270 * 1271 * @return true if the popup is showing, false otherwise 1272 */ isShowing()1273 public boolean isShowing() { 1274 return mIsShowing; 1275 } 1276 1277 /** @hide */ setShowing(boolean isShowing)1278 protected final void setShowing(boolean isShowing) { 1279 mIsShowing = isShowing; 1280 } 1281 1282 /** @hide */ setDropDown(boolean isDropDown)1283 protected final void setDropDown(boolean isDropDown) { 1284 mIsDropdown = isDropDown; 1285 } 1286 1287 /** @hide */ setTransitioningToDismiss(boolean transitioningToDismiss)1288 protected final void setTransitioningToDismiss(boolean transitioningToDismiss) { 1289 mIsTransitioningToDismiss = transitioningToDismiss; 1290 } 1291 1292 /** @hide */ isTransitioningToDismiss()1293 protected final boolean isTransitioningToDismiss() { 1294 return mIsTransitioningToDismiss; 1295 } 1296 1297 /** 1298 * <p> 1299 * Display the content view in a popup window at the specified location. If the popup window 1300 * cannot fit on screen, it will be clipped. See {@link android.view.WindowManager.LayoutParams} 1301 * for more information on how gravity and the x and y parameters are related. Specifying 1302 * a gravity of {@link android.view.Gravity#NO_GRAVITY} is similar to specifying 1303 * <code>Gravity.LEFT | Gravity.TOP</code>. 1304 * </p> 1305 * 1306 * @param parent a parent view to get the {@link android.view.View#getWindowToken()} token from 1307 * @param gravity the gravity which controls the placement of the popup window 1308 * @param x the popup's x location offset 1309 * @param y the popup's y location offset 1310 */ showAtLocation(View parent, int gravity, int x, int y)1311 public void showAtLocation(View parent, int gravity, int x, int y) { 1312 mParentRootView = new WeakReference<>(parent.getRootView()); 1313 showAtLocation(parent.getWindowToken(), gravity, x, y); 1314 } 1315 1316 /** 1317 * Display the content view in a popup window at the specified location. 1318 * 1319 * @param token Window token to use for creating the new window 1320 * @param gravity the gravity which controls the placement of the popup window 1321 * @param x the popup's x location offset 1322 * @param y the popup's y location offset 1323 * 1324 * @hide Internal use only. Applications should use 1325 * {@link #showAtLocation(View, int, int, int)} instead. 1326 */ 1327 @UnsupportedAppUsage showAtLocation(IBinder token, int gravity, int x, int y)1328 public void showAtLocation(IBinder token, int gravity, int x, int y) { 1329 if (isShowing() || mContentView == null) { 1330 return; 1331 } 1332 1333 TransitionManager.endTransitions(mDecorView); 1334 1335 detachFromAnchor(); 1336 1337 mIsShowing = true; 1338 mIsDropdown = false; 1339 mGravity = gravity; 1340 1341 final WindowManager.LayoutParams p = createPopupLayoutParams(token); 1342 preparePopup(p); 1343 1344 p.x = x; 1345 p.y = y; 1346 1347 invokePopup(p); 1348 } 1349 1350 /** 1351 * Display the content view in a popup window anchored to the bottom-left 1352 * corner of the anchor view. If there is not enough room on screen to show 1353 * the popup in its entirety, this method tries to find a parent scroll 1354 * view to scroll. If no parent scroll view can be scrolled, the 1355 * bottom-left corner of the popup is pinned at the top left corner of the 1356 * anchor view. 1357 * 1358 * @param anchor the view on which to pin the popup window 1359 * 1360 * @see #dismiss() 1361 */ showAsDropDown(View anchor)1362 public void showAsDropDown(View anchor) { 1363 showAsDropDown(anchor, 0, 0); 1364 } 1365 1366 /** 1367 * Display the content view in a popup window anchored to the bottom-left 1368 * corner of the anchor view offset by the specified x and y coordinates. 1369 * If there is not enough room on screen to show the popup in its entirety, 1370 * this method tries to find a parent scroll view to scroll. If no parent 1371 * scroll view can be scrolled, the bottom-left corner of the popup is 1372 * pinned at the top left corner of the anchor view. 1373 * <p> 1374 * If the view later scrolls to move <code>anchor</code> to a different 1375 * location, the popup will be moved correspondingly. 1376 * 1377 * @param anchor the view on which to pin the popup window 1378 * @param xoff A horizontal offset from the anchor in pixels 1379 * @param yoff A vertical offset from the anchor in pixels 1380 * 1381 * @see #dismiss() 1382 */ showAsDropDown(View anchor, int xoff, int yoff)1383 public void showAsDropDown(View anchor, int xoff, int yoff) { 1384 showAsDropDown(anchor, xoff, yoff, DEFAULT_ANCHORED_GRAVITY); 1385 } 1386 1387 /** 1388 * Displays the content view in a popup window anchored to the corner of 1389 * another view. The window is positioned according to the specified 1390 * gravity and offset by the specified x and y coordinates. 1391 * <p> 1392 * If there is not enough room on screen to show the popup in its entirety, 1393 * this method tries to find a parent scroll view to scroll. If no parent 1394 * view can be scrolled, the specified vertical gravity will be ignored and 1395 * the popup will anchor itself such that it is visible. 1396 * <p> 1397 * If the view later scrolls to move <code>anchor</code> to a different 1398 * location, the popup will be moved correspondingly. 1399 * 1400 * @param anchor the view on which to pin the popup window 1401 * @param xoff A horizontal offset from the anchor in pixels 1402 * @param yoff A vertical offset from the anchor in pixels 1403 * @param gravity Alignment of the popup relative to the anchor 1404 * 1405 * @see #dismiss() 1406 */ showAsDropDown(View anchor, int xoff, int yoff, int gravity)1407 public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) { 1408 if (isShowing() || !hasContentView()) { 1409 return; 1410 } 1411 1412 TransitionManager.endTransitions(mDecorView); 1413 1414 attachToAnchor(anchor, xoff, yoff, gravity); 1415 1416 mIsShowing = true; 1417 mIsDropdown = true; 1418 1419 final WindowManager.LayoutParams p = 1420 createPopupLayoutParams(anchor.getApplicationWindowToken()); 1421 preparePopup(p); 1422 1423 final boolean aboveAnchor = findDropDownPosition(anchor, p, xoff, yoff, 1424 p.width, p.height, gravity, mAllowScrollingAnchorParent); 1425 updateAboveAnchor(aboveAnchor); 1426 p.accessibilityIdOfAnchor = (anchor != null) ? anchor.getAccessibilityViewId() : -1; 1427 1428 invokePopup(p); 1429 } 1430 1431 /** @hide */ 1432 @UnsupportedAppUsage updateAboveAnchor(boolean aboveAnchor)1433 protected final void updateAboveAnchor(boolean aboveAnchor) { 1434 if (aboveAnchor != mAboveAnchor) { 1435 mAboveAnchor = aboveAnchor; 1436 1437 if (mBackground != null && mBackgroundView != null) { 1438 // If the background drawable provided was a StateListDrawable 1439 // with above-anchor and below-anchor states, use those. 1440 // Otherwise, rely on refreshDrawableState to do the job. 1441 if (mAboveAnchorBackgroundDrawable != null) { 1442 if (mAboveAnchor) { 1443 mBackgroundView.setBackground(mAboveAnchorBackgroundDrawable); 1444 } else { 1445 mBackgroundView.setBackground(mBelowAnchorBackgroundDrawable); 1446 } 1447 } else { 1448 mBackgroundView.refreshDrawableState(); 1449 } 1450 } 1451 } 1452 } 1453 1454 /** 1455 * Indicates whether the popup is showing above (the y coordinate of the popup's bottom 1456 * is less than the y coordinate of the anchor) or below the anchor view (the y coordinate 1457 * of the popup is greater than y coordinate of the anchor's bottom). 1458 * 1459 * The value returned 1460 * by this method is meaningful only after {@link #showAsDropDown(android.view.View)} 1461 * or {@link #showAsDropDown(android.view.View, int, int)} was invoked. 1462 * 1463 * @return True if this popup is showing above the anchor view, false otherwise. 1464 */ isAboveAnchor()1465 public boolean isAboveAnchor() { 1466 return mAboveAnchor; 1467 } 1468 1469 /** 1470 * Prepare the popup by embedding it into a new ViewGroup if the background 1471 * drawable is not null. If embedding is required, the layout parameters' 1472 * height is modified to take into account the background's padding. 1473 * 1474 * @param p the layout parameters of the popup's content view 1475 */ 1476 @UnsupportedAppUsage preparePopup(WindowManager.LayoutParams p)1477 private void preparePopup(WindowManager.LayoutParams p) { 1478 if (mContentView == null || mContext == null || mWindowManager == null) { 1479 throw new IllegalStateException("You must specify a valid content view by " 1480 + "calling setContentView() before attempting to show the popup."); 1481 } 1482 1483 if (p.accessibilityTitle == null) { 1484 p.accessibilityTitle = mContext.getString(R.string.popup_window_default_title); 1485 } 1486 1487 // The old decor view may be transitioning out. Make sure it finishes 1488 // and cleans up before we try to create another one. 1489 if (mDecorView != null) { 1490 mDecorView.cancelTransitions(); 1491 } 1492 1493 // When a background is available, we embed the content view within 1494 // another view that owns the background drawable. 1495 if (mBackground != null) { 1496 mBackgroundView = createBackgroundView(mContentView); 1497 mBackgroundView.setBackground(mBackground); 1498 } else { 1499 mBackgroundView = mContentView; 1500 } 1501 1502 mDecorView = createDecorView(mBackgroundView); 1503 mDecorView.setIsRootNamespace(true); 1504 1505 // The background owner should be elevated so that it casts a shadow. 1506 mBackgroundView.setElevation(mElevation); 1507 1508 // We may wrap that in another view, so we'll need to manually specify 1509 // the surface insets. 1510 p.setSurfaceInsets(mBackgroundView, true /*manual*/, true /*preservePrevious*/); 1511 1512 mPopupViewInitialLayoutDirectionInherited = 1513 (mContentView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT); 1514 } 1515 1516 /** 1517 * Wraps a content view in a PopupViewContainer. 1518 * 1519 * @param contentView the content view to wrap 1520 * @return a PopupViewContainer that wraps the content view 1521 */ createBackgroundView(View contentView)1522 private PopupBackgroundView createBackgroundView(View contentView) { 1523 final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams(); 1524 final int height; 1525 if (layoutParams != null && layoutParams.height == WRAP_CONTENT) { 1526 height = WRAP_CONTENT; 1527 } else { 1528 height = MATCH_PARENT; 1529 } 1530 1531 final PopupBackgroundView backgroundView = new PopupBackgroundView(mContext); 1532 final PopupBackgroundView.LayoutParams listParams = new PopupBackgroundView.LayoutParams( 1533 MATCH_PARENT, height); 1534 backgroundView.addView(contentView, listParams); 1535 1536 return backgroundView; 1537 } 1538 1539 /** 1540 * Wraps a content view in a FrameLayout. 1541 * 1542 * @param contentView the content view to wrap 1543 * @return a FrameLayout that wraps the content view 1544 */ createDecorView(View contentView)1545 private PopupDecorView createDecorView(View contentView) { 1546 final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams(); 1547 final int height; 1548 if (layoutParams != null && layoutParams.height == WRAP_CONTENT) { 1549 height = WRAP_CONTENT; 1550 } else { 1551 height = MATCH_PARENT; 1552 } 1553 1554 final PopupDecorView decorView = new PopupDecorView(mContext); 1555 decorView.addView(contentView, MATCH_PARENT, height); 1556 decorView.setClipChildren(false); 1557 decorView.setClipToPadding(false); 1558 1559 return decorView; 1560 } 1561 1562 /** 1563 * <p>Invoke the popup window by adding the content view to the window 1564 * manager.</p> 1565 * 1566 * <p>The content view must be non-null when this method is invoked.</p> 1567 * 1568 * @param p the layout parameters of the popup's content view 1569 */ 1570 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) invokePopup(WindowManager.LayoutParams p)1571 private void invokePopup(WindowManager.LayoutParams p) { 1572 if (mContext != null) { 1573 p.packageName = mContext.getPackageName(); 1574 } 1575 1576 final PopupDecorView decorView = mDecorView; 1577 decorView.setFitsSystemWindows(mLayoutInsetDecor); 1578 1579 setLayoutDirectionFromAnchor(); 1580 1581 mWindowManager.addView(decorView, p); 1582 1583 if (mEnterTransition != null) { 1584 decorView.requestEnterTransition(mEnterTransition); 1585 } 1586 } 1587 setLayoutDirectionFromAnchor()1588 private void setLayoutDirectionFromAnchor() { 1589 if (mAnchor != null) { 1590 View anchor = mAnchor.get(); 1591 if (anchor != null && mPopupViewInitialLayoutDirectionInherited) { 1592 mDecorView.setLayoutDirection(anchor.getLayoutDirection()); 1593 } 1594 } 1595 } 1596 computeGravity()1597 private int computeGravity() { 1598 int gravity = mGravity == Gravity.NO_GRAVITY ? Gravity.START | Gravity.TOP : mGravity; 1599 if (mIsDropdown && (mClipToScreen || mClippingEnabled)) { 1600 gravity |= Gravity.DISPLAY_CLIP_VERTICAL; 1601 } 1602 return gravity; 1603 } 1604 1605 /** 1606 * <p>Generate the layout parameters for the popup window.</p> 1607 * 1608 * @param token the window token used to bind the popup's window 1609 * 1610 * @return the layout parameters to pass to the window manager 1611 * 1612 * @hide 1613 */ 1614 @UnsupportedAppUsage createPopupLayoutParams(IBinder token)1615 protected final WindowManager.LayoutParams createPopupLayoutParams(IBinder token) { 1616 final WindowManager.LayoutParams p = new WindowManager.LayoutParams(); 1617 1618 // These gravity settings put the view at the top left corner of the 1619 // screen. The view is then positioned to the appropriate location by 1620 // setting the x and y offsets to match the anchor's bottom-left 1621 // corner. 1622 p.gravity = computeGravity(); 1623 p.flags = computeFlags(p.flags); 1624 p.type = mWindowLayoutType; 1625 p.token = token; 1626 p.softInputMode = mSoftInputMode; 1627 p.windowAnimations = computeAnimationResource(); 1628 1629 if (mBackground != null) { 1630 p.format = mBackground.getOpacity(); 1631 } else { 1632 p.format = PixelFormat.TRANSLUCENT; 1633 } 1634 1635 if (mHeightMode < 0) { 1636 p.height = mLastHeight = mHeightMode; 1637 } else { 1638 p.height = mLastHeight = mHeight; 1639 } 1640 1641 if (mWidthMode < 0) { 1642 p.width = mLastWidth = mWidthMode; 1643 } else { 1644 p.width = mLastWidth = mWidth; 1645 } 1646 1647 p.privateFlags = PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH 1648 | PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME; 1649 1650 // Used for debugging. 1651 p.setTitle("PopupWindow:" + Integer.toHexString(hashCode())); 1652 1653 return p; 1654 } 1655 computeFlags(int curFlags)1656 private int computeFlags(int curFlags) { 1657 curFlags &= ~( 1658 WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES | 1659 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | 1660 WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | 1661 WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | 1662 WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | 1663 WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM | 1664 WindowManager.LayoutParams.FLAG_SPLIT_TOUCH); 1665 if(mIgnoreCheekPress) { 1666 curFlags |= WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES; 1667 } 1668 if (!mFocusable) { 1669 curFlags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; 1670 if (mInputMethodMode == INPUT_METHOD_NEEDED) { 1671 curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; 1672 } 1673 } else if (mInputMethodMode == INPUT_METHOD_NOT_NEEDED) { 1674 curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; 1675 } 1676 if (!mTouchable) { 1677 curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; 1678 } 1679 if (mOutsideTouchable) { 1680 curFlags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; 1681 } 1682 if (!mClippingEnabled || mClipToScreen) { 1683 curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; 1684 } 1685 if (isSplitTouchEnabled()) { 1686 curFlags |= WindowManager.LayoutParams.FLAG_SPLIT_TOUCH; 1687 } 1688 if (mLayoutInScreen) { 1689 curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; 1690 } 1691 if (mLayoutInsetDecor) { 1692 curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR; 1693 } 1694 if (mNotTouchModal) { 1695 curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; 1696 } 1697 if (mAttachedInDecor) { 1698 curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_ATTACHED_IN_DECOR; 1699 } 1700 return curFlags; 1701 } 1702 1703 @UnsupportedAppUsage computeAnimationResource()1704 private int computeAnimationResource() { 1705 if (mAnimationStyle == ANIMATION_STYLE_DEFAULT) { 1706 if (mIsDropdown) { 1707 return mAboveAnchor 1708 ? com.android.internal.R.style.Animation_DropDownUp 1709 : com.android.internal.R.style.Animation_DropDownDown; 1710 } 1711 return 0; 1712 } 1713 return mAnimationStyle; 1714 } 1715 1716 /** 1717 * Positions the popup window on screen. When the popup window is too tall 1718 * to fit under the anchor, a parent scroll view is seeked and scrolled up 1719 * to reclaim space. If scrolling is not possible or not enough, the popup 1720 * window gets moved on top of the anchor. 1721 * <p> 1722 * The results of positioning are placed in {@code outParams}. 1723 * 1724 * @param anchor the view on which the popup window must be anchored 1725 * @param outParams the layout parameters used to display the drop down 1726 * @param xOffset absolute horizontal offset from the left of the anchor 1727 * @param yOffset absolute vertical offset from the top of the anchor 1728 * @param gravity horizontal gravity specifying popup alignment 1729 * @param allowScroll whether the anchor view's parent may be scrolled 1730 * when the popup window doesn't fit on screen 1731 * @return true if the popup is translated upwards to fit on screen 1732 * 1733 * @hide 1734 */ findDropDownPosition(View anchor, WindowManager.LayoutParams outParams, int xOffset, int yOffset, int width, int height, int gravity, boolean allowScroll)1735 protected boolean findDropDownPosition(View anchor, WindowManager.LayoutParams outParams, 1736 int xOffset, int yOffset, int width, int height, int gravity, boolean allowScroll) { 1737 final int anchorHeight = anchor.getHeight(); 1738 final int anchorWidth = anchor.getWidth(); 1739 if (mOverlapAnchor) { 1740 yOffset -= anchorHeight; 1741 } 1742 1743 // Initially, align to the bottom-left corner of the anchor plus offsets. 1744 final int[] appScreenLocation = mTmpAppLocation; 1745 final View appRootView = getAppRootView(anchor); 1746 appRootView.getLocationOnScreen(appScreenLocation); 1747 1748 final int[] screenLocation = mTmpScreenLocation; 1749 anchor.getLocationOnScreen(screenLocation); 1750 1751 final int[] drawingLocation = mTmpDrawingLocation; 1752 drawingLocation[0] = screenLocation[0] - appScreenLocation[0]; 1753 drawingLocation[1] = screenLocation[1] - appScreenLocation[1]; 1754 outParams.x = drawingLocation[0] + xOffset; 1755 outParams.y = drawingLocation[1] + anchorHeight + yOffset; 1756 1757 final Rect displayFrame = new Rect(); 1758 appRootView.getWindowVisibleDisplayFrame(displayFrame); 1759 if (width == MATCH_PARENT) { 1760 width = displayFrame.right - displayFrame.left; 1761 } 1762 if (height == MATCH_PARENT) { 1763 height = displayFrame.bottom - displayFrame.top; 1764 } 1765 1766 // Let the window manager know to align the top to y. 1767 outParams.gravity = computeGravity(); 1768 outParams.width = width; 1769 outParams.height = height; 1770 1771 // If we need to adjust for gravity RIGHT, align to the bottom-right 1772 // corner of the anchor (still accounting for offsets). 1773 final int hgrav = Gravity.getAbsoluteGravity(gravity, anchor.getLayoutDirection()) 1774 & Gravity.HORIZONTAL_GRAVITY_MASK; 1775 if (hgrav == Gravity.RIGHT) { 1776 outParams.x -= width - anchorWidth; 1777 } 1778 1779 // First, attempt to fit the popup vertically without resizing. 1780 final boolean fitsVertical = tryFitVertical(outParams, yOffset, height, 1781 anchorHeight, drawingLocation[1], screenLocation[1], displayFrame.top, 1782 displayFrame.bottom, false); 1783 1784 // Next, attempt to fit the popup horizontally without resizing. 1785 final boolean fitsHorizontal = tryFitHorizontal(outParams, xOffset, width, 1786 anchorWidth, drawingLocation[0], screenLocation[0], displayFrame.left, 1787 displayFrame.right, false); 1788 1789 // If the popup still doesn't fit, attempt to scroll the parent. 1790 if (!fitsVertical || !fitsHorizontal) { 1791 final int scrollX = anchor.getScrollX(); 1792 final int scrollY = anchor.getScrollY(); 1793 final Rect r = new Rect(scrollX, scrollY, scrollX + width + xOffset, 1794 scrollY + height + anchorHeight + yOffset); 1795 if (allowScroll && anchor.requestRectangleOnScreen(r, true)) { 1796 // Reset for the new anchor position. 1797 anchor.getLocationOnScreen(screenLocation); 1798 drawingLocation[0] = screenLocation[0] - appScreenLocation[0]; 1799 drawingLocation[1] = screenLocation[1] - appScreenLocation[1]; 1800 outParams.x = drawingLocation[0] + xOffset; 1801 outParams.y = drawingLocation[1] + anchorHeight + yOffset; 1802 1803 // Preserve the gravity adjustment. 1804 if (hgrav == Gravity.RIGHT) { 1805 outParams.x -= width - anchorWidth; 1806 } 1807 } 1808 1809 // Try to fit the popup again and allowing resizing. 1810 tryFitVertical(outParams, yOffset, height, anchorHeight, drawingLocation[1], 1811 screenLocation[1], displayFrame.top, displayFrame.bottom, mClipToScreen); 1812 tryFitHorizontal(outParams, xOffset, width, anchorWidth, drawingLocation[0], 1813 screenLocation[0], displayFrame.left, displayFrame.right, mClipToScreen); 1814 } 1815 1816 // Return whether the popup's top edge is above the anchor's top edge. 1817 return outParams.y < drawingLocation[1]; 1818 } 1819 tryFitVertical(@onNull LayoutParams outParams, int yOffset, int height, int anchorHeight, int drawingLocationY, int screenLocationY, int displayFrameTop, int displayFrameBottom, boolean allowResize)1820 private boolean tryFitVertical(@NonNull LayoutParams outParams, int yOffset, int height, 1821 int anchorHeight, int drawingLocationY, int screenLocationY, int displayFrameTop, 1822 int displayFrameBottom, boolean allowResize) { 1823 final int winOffsetY = screenLocationY - drawingLocationY; 1824 final int anchorTopInScreen = outParams.y + winOffsetY; 1825 final int spaceBelow = displayFrameBottom - anchorTopInScreen; 1826 if (anchorTopInScreen >= displayFrameTop && height <= spaceBelow) { 1827 return true; 1828 } 1829 1830 final int spaceAbove = anchorTopInScreen - anchorHeight - displayFrameTop; 1831 if (height <= spaceAbove) { 1832 // Move everything up. 1833 if (mOverlapAnchor) { 1834 yOffset += anchorHeight; 1835 } 1836 outParams.y = drawingLocationY - height + yOffset; 1837 1838 return true; 1839 } 1840 1841 if (positionInDisplayVertical(outParams, height, drawingLocationY, screenLocationY, 1842 displayFrameTop, displayFrameBottom, allowResize)) { 1843 return true; 1844 } 1845 1846 return false; 1847 } 1848 positionInDisplayVertical(@onNull LayoutParams outParams, int height, int drawingLocationY, int screenLocationY, int displayFrameTop, int displayFrameBottom, boolean canResize)1849 private boolean positionInDisplayVertical(@NonNull LayoutParams outParams, int height, 1850 int drawingLocationY, int screenLocationY, int displayFrameTop, int displayFrameBottom, 1851 boolean canResize) { 1852 boolean fitsInDisplay = true; 1853 1854 final int winOffsetY = screenLocationY - drawingLocationY; 1855 outParams.y += winOffsetY; 1856 outParams.height = height; 1857 1858 final int bottom = outParams.y + height; 1859 if (bottom > displayFrameBottom) { 1860 // The popup is too far down, move it back in. 1861 outParams.y -= bottom - displayFrameBottom; 1862 } 1863 1864 if (outParams.y < displayFrameTop) { 1865 // The popup is too far up, move it back in and clip if 1866 // it's still too large. 1867 outParams.y = displayFrameTop; 1868 1869 final int displayFrameHeight = displayFrameBottom - displayFrameTop; 1870 if (canResize && height > displayFrameHeight) { 1871 outParams.height = displayFrameHeight; 1872 } else { 1873 fitsInDisplay = false; 1874 } 1875 } 1876 1877 outParams.y -= winOffsetY; 1878 1879 return fitsInDisplay; 1880 } 1881 tryFitHorizontal(@onNull LayoutParams outParams, int xOffset, int width, int anchorWidth, int drawingLocationX, int screenLocationX, int displayFrameLeft, int displayFrameRight, boolean allowResize)1882 private boolean tryFitHorizontal(@NonNull LayoutParams outParams, int xOffset, int width, 1883 int anchorWidth, int drawingLocationX, int screenLocationX, int displayFrameLeft, 1884 int displayFrameRight, boolean allowResize) { 1885 final int winOffsetX = screenLocationX - drawingLocationX; 1886 final int anchorLeftInScreen = outParams.x + winOffsetX; 1887 final int spaceRight = displayFrameRight - anchorLeftInScreen; 1888 if (anchorLeftInScreen >= displayFrameLeft && width <= spaceRight) { 1889 return true; 1890 } 1891 1892 if (positionInDisplayHorizontal(outParams, width, drawingLocationX, screenLocationX, 1893 displayFrameLeft, displayFrameRight, allowResize)) { 1894 return true; 1895 } 1896 1897 return false; 1898 } 1899 positionInDisplayHorizontal(@onNull LayoutParams outParams, int width, int drawingLocationX, int screenLocationX, int displayFrameLeft, int displayFrameRight, boolean canResize)1900 private boolean positionInDisplayHorizontal(@NonNull LayoutParams outParams, int width, 1901 int drawingLocationX, int screenLocationX, int displayFrameLeft, int displayFrameRight, 1902 boolean canResize) { 1903 boolean fitsInDisplay = true; 1904 1905 // Use screen coordinates for comparison against display frame. 1906 final int winOffsetX = screenLocationX - drawingLocationX; 1907 outParams.x += winOffsetX; 1908 1909 final int right = outParams.x + width; 1910 if (right > displayFrameRight) { 1911 // The popup is too far right, move it back in. 1912 outParams.x -= right - displayFrameRight; 1913 } 1914 1915 if (outParams.x < displayFrameLeft) { 1916 // The popup is too far left, move it back in and clip if it's 1917 // still too large. 1918 outParams.x = displayFrameLeft; 1919 1920 final int displayFrameWidth = displayFrameRight - displayFrameLeft; 1921 if (canResize && width > displayFrameWidth) { 1922 outParams.width = displayFrameWidth; 1923 } else { 1924 fitsInDisplay = false; 1925 } 1926 } 1927 1928 outParams.x -= winOffsetX; 1929 1930 return fitsInDisplay; 1931 } 1932 1933 /** 1934 * Returns the maximum height that is available for the popup to be 1935 * completely shown. It is recommended that this height be the maximum for 1936 * the popup's height, otherwise it is possible that the popup will be 1937 * clipped. 1938 * 1939 * @param anchor The view on which the popup window must be anchored. 1940 * @return The maximum available height for the popup to be completely 1941 * shown. 1942 */ getMaxAvailableHeight(@onNull View anchor)1943 public int getMaxAvailableHeight(@NonNull View anchor) { 1944 return getMaxAvailableHeight(anchor, 0); 1945 } 1946 1947 /** 1948 * Returns the maximum height that is available for the popup to be 1949 * completely shown. It is recommended that this height be the maximum for 1950 * the popup's height, otherwise it is possible that the popup will be 1951 * clipped. 1952 * 1953 * @param anchor The view on which the popup window must be anchored. 1954 * @param yOffset y offset from the view's bottom edge 1955 * @return The maximum available height for the popup to be completely 1956 * shown. 1957 */ getMaxAvailableHeight(@onNull View anchor, int yOffset)1958 public int getMaxAvailableHeight(@NonNull View anchor, int yOffset) { 1959 return getMaxAvailableHeight(anchor, yOffset, false); 1960 } 1961 1962 /** 1963 * Returns the maximum height that is available for the popup to be 1964 * completely shown, optionally ignoring any bottom decorations such as 1965 * the input method. It is recommended that this height be the maximum for 1966 * the popup's height, otherwise it is possible that the popup will be 1967 * clipped. 1968 * 1969 * @param anchor The view on which the popup window must be anchored. 1970 * @param yOffset y offset from the view's bottom edge 1971 * @param ignoreBottomDecorations if true, the height returned will be 1972 * all the way to the bottom of the display, ignoring any 1973 * bottom decorations 1974 * @return The maximum available height for the popup to be completely 1975 * shown. 1976 */ getMaxAvailableHeight( @onNull View anchor, int yOffset, boolean ignoreBottomDecorations)1977 public int getMaxAvailableHeight( 1978 @NonNull View anchor, int yOffset, boolean ignoreBottomDecorations) { 1979 Rect displayFrame = null; 1980 final Rect visibleDisplayFrame = new Rect(); 1981 1982 final View appView = getAppRootView(anchor); 1983 appView.getWindowVisibleDisplayFrame(visibleDisplayFrame); 1984 if (ignoreBottomDecorations) { 1985 // In the ignore bottom decorations case we want to 1986 // still respect all other decorations so we use the inset visible 1987 // frame on the top right and left and take the bottom 1988 // value from the full frame. 1989 displayFrame = new Rect(); 1990 anchor.getWindowDisplayFrame(displayFrame); 1991 displayFrame.top = visibleDisplayFrame.top; 1992 displayFrame.right = visibleDisplayFrame.right; 1993 displayFrame.left = visibleDisplayFrame.left; 1994 } else { 1995 displayFrame = visibleDisplayFrame; 1996 } 1997 1998 final int[] anchorPos = mTmpDrawingLocation; 1999 anchor.getLocationOnScreen(anchorPos); 2000 2001 final int bottomEdge = displayFrame.bottom; 2002 2003 final int distanceToBottom; 2004 if (mOverlapAnchor) { 2005 distanceToBottom = bottomEdge - anchorPos[1] - yOffset; 2006 } else { 2007 distanceToBottom = bottomEdge - (anchorPos[1] + anchor.getHeight()) - yOffset; 2008 } 2009 final int distanceToTop = anchorPos[1] - displayFrame.top + yOffset; 2010 2011 // anchorPos[1] is distance from anchor to top of screen 2012 int returnedHeight = Math.max(distanceToBottom, distanceToTop); 2013 if (mBackground != null) { 2014 mBackground.getPadding(mTempRect); 2015 returnedHeight -= mTempRect.top + mTempRect.bottom; 2016 } 2017 2018 return returnedHeight; 2019 } 2020 2021 /** 2022 * Disposes of the popup window. This method can be invoked only after 2023 * {@link #showAsDropDown(android.view.View)} has been executed. Failing 2024 * that, calling this method will have no effect. 2025 * 2026 * @see #showAsDropDown(android.view.View) 2027 */ dismiss()2028 public void dismiss() { 2029 if (!isShowing() || isTransitioningToDismiss()) { 2030 return; 2031 } 2032 2033 final PopupDecorView decorView = mDecorView; 2034 final View contentView = mContentView; 2035 2036 unregisterBackCallback(decorView.findOnBackInvokedDispatcher()); 2037 2038 final ViewGroup contentHolder; 2039 final ViewParent contentParent = contentView.getParent(); 2040 if (contentParent instanceof ViewGroup) { 2041 contentHolder = ((ViewGroup) contentParent); 2042 } else { 2043 contentHolder = null; 2044 } 2045 2046 // Ensure any ongoing or pending transitions are canceled. 2047 decorView.cancelTransitions(); 2048 2049 mIsShowing = false; 2050 mIsTransitioningToDismiss = true; 2051 2052 // This method may be called as part of window detachment, in which 2053 // case the anchor view (and its root) will still return true from 2054 // isAttachedToWindow() during execution of this method; however, we 2055 // can expect the OnAttachStateChangeListener to have been called prior 2056 // to executing this method, so we can rely on that instead. 2057 final Transition exitTransition = mExitTransition; 2058 if (exitTransition != null && decorView.isLaidOut() 2059 && (mIsAnchorRootAttached || mAnchorRoot == null)) { 2060 // The decor view is non-interactive and non-IME-focusable during exit transitions. 2061 final LayoutParams p = (LayoutParams) decorView.getLayoutParams(); 2062 p.flags |= LayoutParams.FLAG_NOT_TOUCHABLE; 2063 p.flags |= LayoutParams.FLAG_NOT_FOCUSABLE; 2064 p.flags &= ~LayoutParams.FLAG_ALT_FOCUSABLE_IM; 2065 mWindowManager.updateViewLayout(decorView, p); 2066 2067 final View anchorRoot = mAnchorRoot != null ? mAnchorRoot.get() : null; 2068 final Rect epicenter = getTransitionEpicenter(); 2069 2070 // Once we start dismissing the decor view, all state (including 2071 // the anchor root) needs to be moved to the decor view since we 2072 // may open another popup while it's busy exiting. 2073 decorView.startExitTransition(exitTransition, anchorRoot, epicenter, 2074 new TransitionListenerAdapter() { 2075 @Override 2076 public void onTransitionEnd(Transition transition) { 2077 dismissImmediate(decorView, contentHolder, contentView); 2078 } 2079 }); 2080 } else { 2081 dismissImmediate(decorView, contentHolder, contentView); 2082 } 2083 2084 // Clears the anchor view. 2085 detachFromAnchor(); 2086 2087 if (mOnDismissListener != null) { 2088 mOnDismissListener.onDismiss(); 2089 } 2090 } 2091 unregisterBackCallback(@ullable OnBackInvokedDispatcher onBackInvokedDispatcher)2092 private void unregisterBackCallback(@Nullable OnBackInvokedDispatcher onBackInvokedDispatcher) { 2093 OnBackInvokedCallback backCallback = mBackCallback; 2094 mBackCallback = null; 2095 if (onBackInvokedDispatcher != null && backCallback != null) { 2096 onBackInvokedDispatcher.unregisterOnBackInvokedCallback( 2097 backCallback); 2098 } 2099 } 2100 2101 /** 2102 * Returns the window-relative epicenter bounds to be used by enter and 2103 * exit transitions. 2104 * <p> 2105 * <strong>Note:</strong> This is distinct from the rect passed to 2106 * {@link #setEpicenterBounds(Rect)}, which is anchor-relative. 2107 * 2108 * @return the window-relative epicenter bounds to be used by enter and 2109 * exit transitions 2110 * 2111 * @hide 2112 */ getTransitionEpicenter()2113 protected final Rect getTransitionEpicenter() { 2114 final View anchor = mAnchor != null ? mAnchor.get() : null; 2115 final View decor = mDecorView; 2116 if (anchor == null || decor == null) { 2117 return null; 2118 } 2119 2120 final int[] anchorLocation = anchor.getLocationOnScreen(); 2121 final int[] popupLocation = mDecorView.getLocationOnScreen(); 2122 2123 // Compute the position of the anchor relative to the popup. 2124 final Rect bounds = new Rect(0, 0, anchor.getWidth(), anchor.getHeight()); 2125 bounds.offset(anchorLocation[0] - popupLocation[0], anchorLocation[1] - popupLocation[1]); 2126 2127 // Use anchor-relative epicenter, if specified. 2128 if (mEpicenterBounds != null) { 2129 final int offsetX = bounds.left; 2130 final int offsetY = bounds.top; 2131 bounds.set(mEpicenterBounds); 2132 bounds.offset(offsetX, offsetY); 2133 } 2134 2135 return bounds; 2136 } 2137 2138 /** 2139 * Removes the popup from the window manager and tears down the supporting 2140 * view hierarchy, if necessary. 2141 */ dismissImmediate(View decorView, ViewGroup contentHolder, View contentView)2142 private void dismissImmediate(View decorView, ViewGroup contentHolder, View contentView) { 2143 // If this method gets called and the decor view doesn't have a parent, 2144 // then it was either never added or was already removed. That should 2145 // never happen, but it's worth checking to avoid potential crashes. 2146 if (decorView.getParent() != null) { 2147 mWindowManager.removeViewImmediate(decorView); 2148 } 2149 2150 if (contentHolder != null) { 2151 contentHolder.removeView(contentView); 2152 } 2153 2154 // This needs to stay until after all transitions have ended since we 2155 // need the reference to cancel transitions in preparePopup(). 2156 mDecorView = null; 2157 mBackgroundView = null; 2158 mIsTransitioningToDismiss = false; 2159 } 2160 2161 /** 2162 * Sets the listener to be called when the window is dismissed. 2163 * 2164 * @param onDismissListener The listener. 2165 */ setOnDismissListener(OnDismissListener onDismissListener)2166 public void setOnDismissListener(OnDismissListener onDismissListener) { 2167 mOnDismissListener = onDismissListener; 2168 } 2169 2170 /** @hide */ getOnDismissListener()2171 protected final OnDismissListener getOnDismissListener() { 2172 return mOnDismissListener; 2173 } 2174 2175 /** 2176 * Updates the state of the popup window, if it is currently being displayed, 2177 * from the currently set state. 2178 * <p> 2179 * This includes: 2180 * <ul> 2181 * <li>{@link #setClippingEnabled(boolean)}</li> 2182 * <li>{@link #setFocusable(boolean)}</li> 2183 * <li>{@link #setIgnoreCheekPress()}</li> 2184 * <li>{@link #setInputMethodMode(int)}</li> 2185 * <li>{@link #setTouchable(boolean)}</li> 2186 * <li>{@link #setAnimationStyle(int)}</li> 2187 * <li>{@link #setTouchModal(boolean)} (boolean)}</li> 2188 * <li>{@link #setIsClippedToScreen(boolean)}</li> 2189 * </ul> 2190 */ update()2191 public void update() { 2192 if (!isShowing() || !hasContentView()) { 2193 return; 2194 } 2195 2196 final WindowManager.LayoutParams p = getDecorViewLayoutParams(); 2197 2198 boolean update = false; 2199 2200 final int newAnim = computeAnimationResource(); 2201 if (newAnim != p.windowAnimations) { 2202 p.windowAnimations = newAnim; 2203 update = true; 2204 } 2205 2206 final int newFlags = computeFlags(p.flags); 2207 if (newFlags != p.flags) { 2208 p.flags = newFlags; 2209 update = true; 2210 } 2211 2212 final int newGravity = computeGravity(); 2213 if (newGravity != p.gravity) { 2214 p.gravity = newGravity; 2215 update = true; 2216 } 2217 2218 if (update) { 2219 update(mAnchor != null ? mAnchor.get() : null, p); 2220 } 2221 } 2222 2223 /** @hide */ update(View anchor, WindowManager.LayoutParams params)2224 protected void update(View anchor, WindowManager.LayoutParams params) { 2225 setLayoutDirectionFromAnchor(); 2226 mWindowManager.updateViewLayout(mDecorView, params); 2227 } 2228 2229 /** 2230 * Updates the dimension of the popup window. 2231 * <p> 2232 * Calling this function also updates the window with the current popup 2233 * state as described for {@link #update()}. 2234 * 2235 * @param width the new width in pixels, must be >= 0 or -1 to ignore 2236 * @param height the new height in pixels, must be >= 0 or -1 to ignore 2237 */ update(int width, int height)2238 public void update(int width, int height) { 2239 final WindowManager.LayoutParams p = getDecorViewLayoutParams(); 2240 update(p.x, p.y, width, height, false); 2241 } 2242 2243 /** 2244 * Updates the position and the dimension of the popup window. 2245 * <p> 2246 * Width and height can be set to -1 to update location only. Calling this 2247 * function also updates the window with the current popup state as 2248 * described for {@link #update()}. 2249 * 2250 * @param x the new x location 2251 * @param y the new y location 2252 * @param width the new width in pixels, must be >= 0 or -1 to ignore 2253 * @param height the new height in pixels, must be >= 0 or -1 to ignore 2254 */ update(int x, int y, int width, int height)2255 public void update(int x, int y, int width, int height) { 2256 update(x, y, width, height, false); 2257 } 2258 2259 /** 2260 * Updates the position and the dimension of the popup window. 2261 * <p> 2262 * Width and height can be set to -1 to update location only. Calling this 2263 * function also updates the window with the current popup state as 2264 * described for {@link #update()}. 2265 * 2266 * @param x the new x location 2267 * @param y the new y location 2268 * @param width the new width in pixels, must be >= 0 or -1 to ignore 2269 * @param height the new height in pixels, must be >= 0 or -1 to ignore 2270 * @param force {@code true} to reposition the window even if the specified 2271 * position already seems to correspond to the LayoutParams, 2272 * {@code false} to only reposition if needed 2273 */ update(int x, int y, int width, int height, boolean force)2274 public void update(int x, int y, int width, int height, boolean force) { 2275 if (width >= 0) { 2276 mLastWidth = width; 2277 setWidth(width); 2278 } 2279 2280 if (height >= 0) { 2281 mLastHeight = height; 2282 setHeight(height); 2283 } 2284 2285 if (!isShowing() || !hasContentView()) { 2286 return; 2287 } 2288 2289 final WindowManager.LayoutParams p = getDecorViewLayoutParams(); 2290 2291 boolean update = force; 2292 2293 final int finalWidth = mWidthMode < 0 ? mWidthMode : mLastWidth; 2294 if (width != -1 && p.width != finalWidth) { 2295 p.width = mLastWidth = finalWidth; 2296 update = true; 2297 } 2298 2299 final int finalHeight = mHeightMode < 0 ? mHeightMode : mLastHeight; 2300 if (height != -1 && p.height != finalHeight) { 2301 p.height = mLastHeight = finalHeight; 2302 update = true; 2303 } 2304 2305 if (p.x != x) { 2306 p.x = x; 2307 update = true; 2308 } 2309 2310 if (p.y != y) { 2311 p.y = y; 2312 update = true; 2313 } 2314 2315 final int newAnim = computeAnimationResource(); 2316 if (newAnim != p.windowAnimations) { 2317 p.windowAnimations = newAnim; 2318 update = true; 2319 } 2320 2321 final int newFlags = computeFlags(p.flags); 2322 if (newFlags != p.flags) { 2323 p.flags = newFlags; 2324 update = true; 2325 } 2326 2327 final int newGravity = computeGravity(); 2328 if (newGravity != p.gravity) { 2329 p.gravity = newGravity; 2330 update = true; 2331 } 2332 2333 View anchor = null; 2334 int newAccessibilityIdOfAnchor = -1; 2335 2336 if (mAnchor != null && mAnchor.get() != null) { 2337 anchor = mAnchor.get(); 2338 newAccessibilityIdOfAnchor = anchor.getAccessibilityViewId(); 2339 } 2340 2341 if (newAccessibilityIdOfAnchor != p.accessibilityIdOfAnchor) { 2342 p.accessibilityIdOfAnchor = newAccessibilityIdOfAnchor; 2343 update = true; 2344 } 2345 2346 if (update) { 2347 update(anchor, p); 2348 } 2349 } 2350 2351 /** @hide */ 2352 protected boolean hasContentView() { 2353 return mContentView != null; 2354 } 2355 2356 /** @hide */ 2357 protected boolean hasDecorView() { 2358 return mDecorView != null; 2359 } 2360 2361 /** @hide */ 2362 protected WindowManager.LayoutParams getDecorViewLayoutParams() { 2363 return (WindowManager.LayoutParams) mDecorView.getLayoutParams(); 2364 } 2365 2366 /** 2367 * Updates the position and the dimension of the popup window. 2368 * <p> 2369 * Calling this function also updates the window with the current popup 2370 * state as described for {@link #update()}. 2371 * 2372 * @param anchor the popup's anchor view 2373 * @param width the new width in pixels, must be >= 0 or -1 to ignore 2374 * @param height the new height in pixels, must be >= 0 or -1 to ignore 2375 */ 2376 public void update(View anchor, int width, int height) { 2377 update(anchor, false, 0, 0, width, height); 2378 } 2379 2380 /** 2381 * Updates the position and the dimension of the popup window. 2382 * <p> 2383 * Width and height can be set to -1 to update location only. Calling this 2384 * function also updates the window with the current popup state as 2385 * described for {@link #update()}. 2386 * <p> 2387 * If the view later scrolls to move {@code anchor} to a different 2388 * location, the popup will be moved correspondingly. 2389 * 2390 * @param anchor the popup's anchor view 2391 * @param xoff x offset from the view's left edge 2392 * @param yoff y offset from the view's bottom edge 2393 * @param width the new width in pixels, must be >= 0 or -1 to ignore 2394 * @param height the new height in pixels, must be >= 0 or -1 to ignore 2395 */ 2396 public void update(View anchor, int xoff, int yoff, int width, int height) { 2397 update(anchor, true, xoff, yoff, width, height); 2398 } 2399 2400 private void update(View anchor, boolean updateLocation, int xoff, int yoff, 2401 int width, int height) { 2402 2403 if (!isShowing() || !hasContentView()) { 2404 return; 2405 } 2406 2407 final WeakReference<View> oldAnchor = mAnchor; 2408 final int gravity = mAnchoredGravity; 2409 2410 final boolean needsUpdate = updateLocation && (mAnchorXoff != xoff || mAnchorYoff != yoff); 2411 if (oldAnchor == null || oldAnchor.get() != anchor || (needsUpdate && !mIsDropdown)) { 2412 attachToAnchor(anchor, xoff, yoff, gravity); 2413 } else if (needsUpdate) { 2414 // No need to register again if this is a DropDown, showAsDropDown already did. 2415 mAnchorXoff = xoff; 2416 mAnchorYoff = yoff; 2417 } 2418 2419 final WindowManager.LayoutParams p = getDecorViewLayoutParams(); 2420 final int oldGravity = p.gravity; 2421 final int oldWidth = p.width; 2422 final int oldHeight = p.height; 2423 final int oldX = p.x; 2424 final int oldY = p.y; 2425 2426 // If an explicit width/height has not specified, use the most recent 2427 // explicitly specified value (either from setWidth/Height or update). 2428 if (width < 0) { 2429 width = mWidth; 2430 } 2431 if (height < 0) { 2432 height = mHeight; 2433 } 2434 2435 final boolean aboveAnchor = findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff, 2436 width, height, gravity, mAllowScrollingAnchorParent); 2437 updateAboveAnchor(aboveAnchor); 2438 2439 final boolean paramsChanged = oldGravity != p.gravity || oldX != p.x || oldY != p.y 2440 || oldWidth != p.width || oldHeight != p.height; 2441 2442 // If width and mWidth were both < 0 then we have a MATCH_PARENT or 2443 // WRAP_CONTENT case. findDropDownPosition will have resolved this to 2444 // absolute values, but we don't want to update mWidth/mHeight to these 2445 // absolute values. 2446 final int newWidth = width < 0 ? width : p.width; 2447 final int newHeight = height < 0 ? height : p.height; 2448 update(p.x, p.y, newWidth, newHeight, paramsChanged); 2449 } 2450 2451 /** 2452 * Listener that is called when this popup window is dismissed. 2453 */ 2454 public interface OnDismissListener { 2455 /** 2456 * Called when this popup window is dismissed. 2457 */ 2458 public void onDismiss(); 2459 } 2460 2461 /** @hide */ 2462 protected void detachFromAnchor() { 2463 final View anchor = getAnchor(); 2464 if (anchor != null) { 2465 final ViewTreeObserver vto = anchor.getViewTreeObserver(); 2466 vto.removeOnScrollChangedListener(mOnScrollChangedListener); 2467 anchor.removeOnAttachStateChangeListener(mOnAnchorDetachedListener); 2468 } 2469 2470 final View anchorRoot = mAnchorRoot != null ? mAnchorRoot.get() : null; 2471 if (anchorRoot != null) { 2472 anchorRoot.removeOnAttachStateChangeListener(mOnAnchorRootDetachedListener); 2473 anchorRoot.removeOnLayoutChangeListener(mOnLayoutChangeListener); 2474 } 2475 2476 mAnchor = null; 2477 mAnchorRoot = null; 2478 mIsAnchorRootAttached = false; 2479 } 2480 2481 /** @hide */ 2482 protected void attachToAnchor(View anchor, int xoff, int yoff, int gravity) { 2483 detachFromAnchor(); 2484 2485 final ViewTreeObserver vto = anchor.getViewTreeObserver(); 2486 if (vto != null) { 2487 vto.addOnScrollChangedListener(mOnScrollChangedListener); 2488 } 2489 anchor.addOnAttachStateChangeListener(mOnAnchorDetachedListener); 2490 2491 final View anchorRoot = anchor.getRootView(); 2492 anchorRoot.addOnAttachStateChangeListener(mOnAnchorRootDetachedListener); 2493 anchorRoot.addOnLayoutChangeListener(mOnLayoutChangeListener); 2494 2495 mAnchor = new WeakReference<>(anchor); 2496 mAnchorRoot = new WeakReference<>(anchorRoot); 2497 mIsAnchorRootAttached = anchorRoot.isAttachedToWindow(); 2498 mParentRootView = mAnchorRoot; 2499 2500 mAnchorXoff = xoff; 2501 mAnchorYoff = yoff; 2502 mAnchoredGravity = gravity; 2503 } 2504 2505 /** @hide */ 2506 protected @Nullable View getAnchor() { 2507 return mAnchor != null ? mAnchor.get() : null; 2508 } 2509 2510 private void alignToAnchor() { 2511 final View anchor = mAnchor != null ? mAnchor.get() : null; 2512 if (anchor != null && anchor.isAttachedToWindow() && hasDecorView()) { 2513 final WindowManager.LayoutParams p = getDecorViewLayoutParams(); 2514 2515 updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff, 2516 p.width, p.height, mAnchoredGravity, false)); 2517 update(p.x, p.y, -1, -1, true); 2518 } 2519 } 2520 2521 private View getAppRootView(View anchor) { 2522 final View appWindowView = WindowManagerGlobal.getInstance().getWindowView( 2523 anchor.getApplicationWindowToken()); 2524 if (appWindowView != null) { 2525 return appWindowView; 2526 } 2527 return anchor.getRootView(); 2528 } 2529 2530 private class PopupDecorView extends FrameLayout { 2531 /** Runnable used to clean up listeners after exit transition. */ 2532 private Runnable mCleanupAfterExit; 2533 2534 public PopupDecorView(Context context) { 2535 super(context); 2536 } 2537 2538 @Override 2539 public boolean dispatchKeyEvent(KeyEvent event) { 2540 if (event.getKeyCode() == KeyEvent.KEYCODE_BACK 2541 || event.getKeyCode() == KeyEvent.KEYCODE_ESCAPE) { 2542 if (getKeyDispatcherState() == null) { 2543 return super.dispatchKeyEvent(event); 2544 } 2545 2546 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { 2547 final KeyEvent.DispatcherState state = getKeyDispatcherState(); 2548 if (state != null) { 2549 state.startTracking(event, this); 2550 } 2551 return true; 2552 } else if (event.getAction() == KeyEvent.ACTION_UP) { 2553 final KeyEvent.DispatcherState state = getKeyDispatcherState(); 2554 if (state != null && state.isTracking(event) && !event.isCanceled()) { 2555 dismiss(); 2556 return true; 2557 } 2558 } 2559 return super.dispatchKeyEvent(event); 2560 } else { 2561 return super.dispatchKeyEvent(event); 2562 } 2563 } 2564 2565 @Override 2566 public boolean dispatchTouchEvent(MotionEvent ev) { 2567 if (mTouchInterceptor != null && mTouchInterceptor.onTouch(this, ev)) { 2568 return true; 2569 } 2570 return super.dispatchTouchEvent(ev); 2571 } 2572 2573 @Override 2574 public boolean onTouchEvent(MotionEvent event) { 2575 final int x = (int) event.getX(); 2576 final int y = (int) event.getY(); 2577 2578 if ((event.getAction() == MotionEvent.ACTION_DOWN) 2579 && ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) { 2580 dismiss(); 2581 return true; 2582 } else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) { 2583 dismiss(); 2584 return true; 2585 } else { 2586 return super.onTouchEvent(event); 2587 } 2588 } 2589 2590 /** 2591 * Requests that an enter transition run after the next layout pass. 2592 */ 2593 public void requestEnterTransition(Transition transition) { 2594 final ViewTreeObserver observer = getViewTreeObserver(); 2595 if (observer != null && transition != null) { 2596 final Transition enterTransition = transition.clone(); 2597 2598 // Postpone the enter transition after the first layout pass. 2599 observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() { 2600 @Override 2601 public void onGlobalLayout() { 2602 final ViewTreeObserver observer = getViewTreeObserver(); 2603 if (observer != null) { 2604 observer.removeOnGlobalLayoutListener(this); 2605 } 2606 2607 final Rect epicenter = getTransitionEpicenter(); 2608 enterTransition.setEpicenterCallback(new EpicenterCallback() { 2609 @Override 2610 public Rect onGetEpicenter(Transition transition) { 2611 return epicenter; 2612 } 2613 }); 2614 startEnterTransition(enterTransition); 2615 } 2616 }); 2617 } 2618 } 2619 2620 /** 2621 * Starts the pending enter transition, if one is set. 2622 */ 2623 private void startEnterTransition(Transition enterTransition) { 2624 final int count = getChildCount(); 2625 for (int i = 0; i < count; i++) { 2626 final View child = getChildAt(i); 2627 enterTransition.addTarget(child); 2628 child.setTransitionVisibility(View.INVISIBLE); 2629 } 2630 2631 TransitionManager.beginDelayedTransition(this, enterTransition); 2632 2633 for (int i = 0; i < count; i++) { 2634 final View child = getChildAt(i); 2635 child.setTransitionVisibility(View.VISIBLE); 2636 } 2637 } 2638 2639 /** 2640 * Starts an exit transition immediately. 2641 * <p> 2642 * <strong>Note:</strong> The transition listener is guaranteed to have 2643 * its {@code onTransitionEnd} method called even if the transition 2644 * never starts. 2645 */ 2646 public void startExitTransition(@NonNull Transition transition, 2647 @Nullable final View anchorRoot, @Nullable final Rect epicenter, 2648 @NonNull final TransitionListener listener) { 2649 if (transition == null) { 2650 return; 2651 } 2652 2653 // The anchor view's window may go away while we're executing our 2654 // transition, in which case we need to end the transition 2655 // immediately and execute the listener to remove the popup. 2656 if (anchorRoot != null) { 2657 anchorRoot.addOnAttachStateChangeListener(mOnAnchorRootDetachedListener); 2658 } 2659 2660 // The cleanup runnable MUST be called even if the transition is 2661 // canceled before it starts (and thus can't call onTransitionEnd). 2662 mCleanupAfterExit = () -> { 2663 listener.onTransitionEnd(transition); 2664 2665 if (anchorRoot != null) { 2666 anchorRoot.removeOnAttachStateChangeListener(mOnAnchorRootDetachedListener); 2667 } 2668 2669 // The listener was called. Our job here is done. 2670 mCleanupAfterExit = null; 2671 }; 2672 2673 final Transition exitTransition = transition.clone(); 2674 exitTransition.addListener(new TransitionListenerAdapter() { 2675 @Override 2676 public void onTransitionEnd(Transition t) { 2677 t.removeListener(this); 2678 2679 // This null check shouldn't be necessary, but it's easier 2680 // to check here than it is to test every possible case. 2681 if (mCleanupAfterExit != null) { 2682 mCleanupAfterExit.run(); 2683 } 2684 } 2685 }); 2686 exitTransition.setEpicenterCallback(new EpicenterCallback() { 2687 @Override 2688 public Rect onGetEpicenter(Transition transition) { 2689 return epicenter; 2690 } 2691 }); 2692 2693 final int count = getChildCount(); 2694 for (int i = 0; i < count; i++) { 2695 final View child = getChildAt(i); 2696 exitTransition.addTarget(child); 2697 } 2698 2699 TransitionManager.beginDelayedTransition(this, exitTransition); 2700 2701 for (int i = 0; i < count; i++) { 2702 final View child = getChildAt(i); 2703 child.setVisibility(View.INVISIBLE); 2704 } 2705 } 2706 2707 /** 2708 * Cancels all pending or current transitions. 2709 */ 2710 public void cancelTransitions() { 2711 TransitionManager.endTransitions(this); 2712 2713 // If the cleanup runnable is still around, that means the 2714 // transition never started. We should run it now to clean up. 2715 if (mCleanupAfterExit != null) { 2716 mCleanupAfterExit.run(); 2717 } 2718 } 2719 2720 private final OnAttachStateChangeListener mOnAnchorRootDetachedListener = 2721 new OnAttachStateChangeListener() { 2722 @Override 2723 public void onViewAttachedToWindow(View v) {} 2724 2725 @Override 2726 public void onViewDetachedFromWindow(View v) { 2727 v.removeOnAttachStateChangeListener(this); 2728 2729 if (isAttachedToWindow()) { 2730 TransitionManager.endTransitions(PopupDecorView.this); 2731 } 2732 } 2733 }; 2734 2735 @Override 2736 public void requestKeyboardShortcuts(List<KeyboardShortcutGroup> list, int deviceId) { 2737 if (mParentRootView != null) { 2738 View parentRoot = mParentRootView.get(); 2739 if (parentRoot != null) { 2740 parentRoot.requestKeyboardShortcuts(list, deviceId); 2741 } 2742 } 2743 } 2744 2745 @Override 2746 protected void onAttachedToWindow() { 2747 super.onAttachedToWindow(); 2748 if (!WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(mContext)) { 2749 return; 2750 } 2751 2752 OnBackInvokedDispatcher dispatcher = findOnBackInvokedDispatcher(); 2753 if (dispatcher == null) { 2754 return; 2755 } 2756 2757 mBackCallback = PopupWindow.this::dismiss; 2758 2759 dispatcher.registerOnBackInvokedCallback(OnBackInvokedDispatcher.PRIORITY_DEFAULT, 2760 mBackCallback); 2761 } 2762 2763 @Override 2764 protected void onDetachedFromWindow() { 2765 super.onDetachedFromWindow(); 2766 unregisterBackCallback(findOnBackInvokedDispatcher()); 2767 } 2768 } 2769 2770 private class PopupBackgroundView extends FrameLayout { 2771 public PopupBackgroundView(Context context) { 2772 super(context); 2773 } 2774 2775 @Override 2776 protected int[] onCreateDrawableState(int extraSpace) { 2777 if (mAboveAnchor) { 2778 final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); 2779 View.mergeDrawableStates(drawableState, ABOVE_ANCHOR_STATE_SET); 2780 return drawableState; 2781 } else { 2782 return super.onCreateDrawableState(extraSpace); 2783 } 2784 } 2785 } 2786 } 2787