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