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