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