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