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