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