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