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