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