• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.widget;
18 
19 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
20 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
21 import static android.view.WindowManager.LayoutParams
22         .PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME;
23 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH;
24 
25 import android.annotation.NonNull;
26 import android.annotation.Nullable;
27 import android.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         if (p.accessibilityTitle == null) {
1338             p.accessibilityTitle = mContext.getString(R.string.popup_window_default_title);
1339         }
1340 
1341         // The old decor view may be transitioning out. Make sure it finishes
1342         // and cleans up before we try to create another one.
1343         if (mDecorView != null) {
1344             mDecorView.cancelTransitions();
1345         }
1346 
1347         // When a background is available, we embed the content view within
1348         // another view that owns the background drawable.
1349         if (mBackground != null) {
1350             mBackgroundView = createBackgroundView(mContentView);
1351             mBackgroundView.setBackground(mBackground);
1352         } else {
1353             mBackgroundView = mContentView;
1354         }
1355 
1356         mDecorView = createDecorView(mBackgroundView);
1357 
1358         // The background owner should be elevated so that it casts a shadow.
1359         mBackgroundView.setElevation(mElevation);
1360 
1361         // We may wrap that in another view, so we'll need to manually specify
1362         // the surface insets.
1363         p.setSurfaceInsets(mBackgroundView, true /*manual*/, true /*preservePrevious*/);
1364 
1365         mPopupViewInitialLayoutDirectionInherited =
1366                 (mContentView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT);
1367     }
1368 
1369     /**
1370      * Wraps a content view in a PopupViewContainer.
1371      *
1372      * @param contentView the content view to wrap
1373      * @return a PopupViewContainer that wraps the content view
1374      */
createBackgroundView(View contentView)1375     private PopupBackgroundView createBackgroundView(View contentView) {
1376         final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();
1377         final int height;
1378         if (layoutParams != null && layoutParams.height == WRAP_CONTENT) {
1379             height = WRAP_CONTENT;
1380         } else {
1381             height = MATCH_PARENT;
1382         }
1383 
1384         final PopupBackgroundView backgroundView = new PopupBackgroundView(mContext);
1385         final PopupBackgroundView.LayoutParams listParams = new PopupBackgroundView.LayoutParams(
1386                 MATCH_PARENT, height);
1387         backgroundView.addView(contentView, listParams);
1388 
1389         return backgroundView;
1390     }
1391 
1392     /**
1393      * Wraps a content view in a FrameLayout.
1394      *
1395      * @param contentView the content view to wrap
1396      * @return a FrameLayout that wraps the content view
1397      */
createDecorView(View contentView)1398     private PopupDecorView createDecorView(View contentView) {
1399         final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();
1400         final int height;
1401         if (layoutParams != null && layoutParams.height == WRAP_CONTENT) {
1402             height = WRAP_CONTENT;
1403         } else {
1404             height = MATCH_PARENT;
1405         }
1406 
1407         final PopupDecorView decorView = new PopupDecorView(mContext);
1408         decorView.addView(contentView, MATCH_PARENT, height);
1409         decorView.setClipChildren(false);
1410         decorView.setClipToPadding(false);
1411 
1412         return decorView;
1413     }
1414 
1415     /**
1416      * <p>Invoke the popup window by adding the content view to the window
1417      * manager.</p>
1418      *
1419      * <p>The content view must be non-null when this method is invoked.</p>
1420      *
1421      * @param p the layout parameters of the popup's content view
1422      */
invokePopup(WindowManager.LayoutParams p)1423     private void invokePopup(WindowManager.LayoutParams p) {
1424         if (mContext != null) {
1425             p.packageName = mContext.getPackageName();
1426         }
1427 
1428         final PopupDecorView decorView = mDecorView;
1429         decorView.setFitsSystemWindows(mLayoutInsetDecor);
1430 
1431         setLayoutDirectionFromAnchor();
1432 
1433         mWindowManager.addView(decorView, p);
1434 
1435         if (mEnterTransition != null) {
1436             decorView.requestEnterTransition(mEnterTransition);
1437         }
1438     }
1439 
setLayoutDirectionFromAnchor()1440     private void setLayoutDirectionFromAnchor() {
1441         if (mAnchor != null) {
1442             View anchor = mAnchor.get();
1443             if (anchor != null && mPopupViewInitialLayoutDirectionInherited) {
1444                 mDecorView.setLayoutDirection(anchor.getLayoutDirection());
1445             }
1446         }
1447     }
1448 
computeGravity()1449     private int computeGravity() {
1450         int gravity = mGravity == Gravity.NO_GRAVITY ?  Gravity.START | Gravity.TOP : mGravity;
1451         if (mIsDropdown && (mClipToScreen || mClippingEnabled)) {
1452             gravity |= Gravity.DISPLAY_CLIP_VERTICAL;
1453         }
1454         return gravity;
1455     }
1456 
1457     /**
1458      * <p>Generate the layout parameters for the popup window.</p>
1459      *
1460      * @param token the window token used to bind the popup's window
1461      *
1462      * @return the layout parameters to pass to the window manager
1463      *
1464      * @hide
1465      */
createPopupLayoutParams(IBinder token)1466     protected final WindowManager.LayoutParams createPopupLayoutParams(IBinder token) {
1467         final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
1468 
1469         // These gravity settings put the view at the top left corner of the
1470         // screen. The view is then positioned to the appropriate location by
1471         // setting the x and y offsets to match the anchor's bottom-left
1472         // corner.
1473         p.gravity = computeGravity();
1474         p.flags = computeFlags(p.flags);
1475         p.type = mWindowLayoutType;
1476         p.token = token;
1477         p.softInputMode = mSoftInputMode;
1478         p.windowAnimations = computeAnimationResource();
1479 
1480         if (mBackground != null) {
1481             p.format = mBackground.getOpacity();
1482         } else {
1483             p.format = PixelFormat.TRANSLUCENT;
1484         }
1485 
1486         if (mHeightMode < 0) {
1487             p.height = mLastHeight = mHeightMode;
1488         } else {
1489             p.height = mLastHeight = mHeight;
1490         }
1491 
1492         if (mWidthMode < 0) {
1493             p.width = mLastWidth = mWidthMode;
1494         } else {
1495             p.width = mLastWidth = mWidth;
1496         }
1497 
1498         p.privateFlags = PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH
1499                 | PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME;
1500 
1501         // Used for debugging.
1502         p.setTitle("PopupWindow:" + Integer.toHexString(hashCode()));
1503 
1504         return p;
1505     }
1506 
computeFlags(int curFlags)1507     private int computeFlags(int curFlags) {
1508         curFlags &= ~(
1509                 WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES |
1510                 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
1511                 WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE |
1512                 WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH |
1513                 WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS |
1514                 WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM |
1515                 WindowManager.LayoutParams.FLAG_SPLIT_TOUCH);
1516         if(mIgnoreCheekPress) {
1517             curFlags |= WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
1518         }
1519         if (!mFocusable) {
1520             curFlags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
1521             if (mInputMethodMode == INPUT_METHOD_NEEDED) {
1522                 curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
1523             }
1524         } else if (mInputMethodMode == INPUT_METHOD_NOT_NEEDED) {
1525             curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
1526         }
1527         if (!mTouchable) {
1528             curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
1529         }
1530         if (mOutsideTouchable) {
1531             curFlags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
1532         }
1533         if (!mClippingEnabled || mClipToScreen) {
1534             curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
1535         }
1536         if (isSplitTouchEnabled()) {
1537             curFlags |= WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
1538         }
1539         if (mLayoutInScreen) {
1540             curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
1541         }
1542         if (mLayoutInsetDecor) {
1543             curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
1544         }
1545         if (mNotTouchModal) {
1546             curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
1547         }
1548         if (mAttachedInDecor) {
1549             curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_ATTACHED_IN_DECOR;
1550         }
1551         return curFlags;
1552     }
1553 
computeAnimationResource()1554     private int computeAnimationResource() {
1555         if (mAnimationStyle == ANIMATION_STYLE_DEFAULT) {
1556             if (mIsDropdown) {
1557                 return mAboveAnchor
1558                         ? com.android.internal.R.style.Animation_DropDownUp
1559                         : com.android.internal.R.style.Animation_DropDownDown;
1560             }
1561             return 0;
1562         }
1563         return mAnimationStyle;
1564     }
1565 
1566     /**
1567      * Positions the popup window on screen. When the popup window is too tall
1568      * to fit under the anchor, a parent scroll view is seeked and scrolled up
1569      * to reclaim space. If scrolling is not possible or not enough, the popup
1570      * window gets moved on top of the anchor.
1571      * <p>
1572      * The results of positioning are placed in {@code outParams}.
1573      *
1574      * @param anchor the view on which the popup window must be anchored
1575      * @param outParams the layout parameters used to display the drop down
1576      * @param xOffset absolute horizontal offset from the left of the anchor
1577      * @param yOffset absolute vertical offset from the top of the anchor
1578      * @param gravity horizontal gravity specifying popup alignment
1579      * @param allowScroll whether the anchor view's parent may be scrolled
1580      *                    when the popup window doesn't fit on screen
1581      * @return true if the popup is translated upwards to fit on screen
1582      *
1583      * @hide
1584      */
findDropDownPosition(View anchor, WindowManager.LayoutParams outParams, int xOffset, int yOffset, int width, int height, int gravity, boolean allowScroll)1585     protected final boolean findDropDownPosition(View anchor, WindowManager.LayoutParams outParams,
1586             int xOffset, int yOffset, int width, int height, int gravity, boolean allowScroll) {
1587         final int anchorHeight = anchor.getHeight();
1588         final int anchorWidth = anchor.getWidth();
1589         if (mOverlapAnchor) {
1590             yOffset -= anchorHeight;
1591         }
1592 
1593         // Initially, align to the bottom-left corner of the anchor plus offsets.
1594         final int[] appScreenLocation = mTmpAppLocation;
1595         final View appRootView = getAppRootView(anchor);
1596         appRootView.getLocationOnScreen(appScreenLocation);
1597 
1598         final int[] screenLocation = mTmpScreenLocation;
1599         anchor.getLocationOnScreen(screenLocation);
1600 
1601         final int[] drawingLocation = mTmpDrawingLocation;
1602         drawingLocation[0] = screenLocation[0] - appScreenLocation[0];
1603         drawingLocation[1] = screenLocation[1] - appScreenLocation[1];
1604         outParams.x = drawingLocation[0] + xOffset;
1605         outParams.y = drawingLocation[1] + anchorHeight + yOffset;
1606 
1607         final Rect displayFrame = new Rect();
1608         appRootView.getWindowVisibleDisplayFrame(displayFrame);
1609         if (width == MATCH_PARENT) {
1610             width = displayFrame.right - displayFrame.left;
1611         }
1612         if (height == MATCH_PARENT) {
1613             height = displayFrame.bottom - displayFrame.top;
1614         }
1615 
1616         // Let the window manager know to align the top to y.
1617         outParams.gravity = computeGravity();
1618         outParams.width = width;
1619         outParams.height = height;
1620 
1621         // If we need to adjust for gravity RIGHT, align to the bottom-right
1622         // corner of the anchor (still accounting for offsets).
1623         final int hgrav = Gravity.getAbsoluteGravity(gravity, anchor.getLayoutDirection())
1624                 & Gravity.HORIZONTAL_GRAVITY_MASK;
1625         if (hgrav == Gravity.RIGHT) {
1626             outParams.x -= width - anchorWidth;
1627         }
1628 
1629         // First, attempt to fit the popup vertically without resizing.
1630         final boolean fitsVertical = tryFitVertical(outParams, yOffset, height,
1631                 anchorHeight, drawingLocation[1], screenLocation[1], displayFrame.top,
1632                 displayFrame.bottom, false);
1633 
1634         // Next, attempt to fit the popup horizontally without resizing.
1635         final boolean fitsHorizontal = tryFitHorizontal(outParams, xOffset, width,
1636                 anchorWidth, drawingLocation[0], screenLocation[0], displayFrame.left,
1637                 displayFrame.right, false);
1638 
1639         // If the popup still doesn't fit, attempt to scroll the parent.
1640         if (!fitsVertical || !fitsHorizontal) {
1641             final int scrollX = anchor.getScrollX();
1642             final int scrollY = anchor.getScrollY();
1643             final Rect r = new Rect(scrollX, scrollY, scrollX + width + xOffset,
1644                     scrollY + height + anchorHeight + yOffset);
1645             if (allowScroll && anchor.requestRectangleOnScreen(r, true)) {
1646                 // Reset for the new anchor position.
1647                 anchor.getLocationOnScreen(screenLocation);
1648                 drawingLocation[0] = screenLocation[0] - appScreenLocation[0];
1649                 drawingLocation[1] = screenLocation[1] - appScreenLocation[1];
1650                 outParams.x = drawingLocation[0] + xOffset;
1651                 outParams.y = drawingLocation[1] + anchorHeight + yOffset;
1652 
1653                 // Preserve the gravity adjustment.
1654                 if (hgrav == Gravity.RIGHT) {
1655                     outParams.x -= width - anchorWidth;
1656                 }
1657             }
1658 
1659             // Try to fit the popup again and allowing resizing.
1660             tryFitVertical(outParams, yOffset, height, anchorHeight, drawingLocation[1],
1661                     screenLocation[1], displayFrame.top, displayFrame.bottom, mClipToScreen);
1662             tryFitHorizontal(outParams, xOffset, width, anchorWidth, drawingLocation[0],
1663                     screenLocation[0], displayFrame.left, displayFrame.right, mClipToScreen);
1664         }
1665 
1666         // Return whether the popup's top edge is above the anchor's top edge.
1667         return outParams.y < drawingLocation[1];
1668     }
1669 
tryFitVertical(@onNull LayoutParams outParams, int yOffset, int height, int anchorHeight, int drawingLocationY, int screenLocationY, int displayFrameTop, int displayFrameBottom, boolean allowResize)1670     private boolean tryFitVertical(@NonNull LayoutParams outParams, int yOffset, int height,
1671             int anchorHeight, int drawingLocationY, int screenLocationY, int displayFrameTop,
1672             int displayFrameBottom, boolean allowResize) {
1673         final int winOffsetY = screenLocationY - drawingLocationY;
1674         final int anchorTopInScreen = outParams.y + winOffsetY;
1675         final int spaceBelow = displayFrameBottom - anchorTopInScreen;
1676         if (anchorTopInScreen >= 0 && height <= spaceBelow) {
1677             return true;
1678         }
1679 
1680         final int spaceAbove = anchorTopInScreen - anchorHeight - displayFrameTop;
1681         if (height <= spaceAbove) {
1682             // Move everything up.
1683             if (mOverlapAnchor) {
1684                 yOffset += anchorHeight;
1685             }
1686             outParams.y = drawingLocationY - height + yOffset;
1687 
1688             return true;
1689         }
1690 
1691         if (positionInDisplayVertical(outParams, height, drawingLocationY, screenLocationY,
1692                 displayFrameTop, displayFrameBottom, allowResize)) {
1693             return true;
1694         }
1695 
1696         return false;
1697     }
1698 
positionInDisplayVertical(@onNull LayoutParams outParams, int height, int drawingLocationY, int screenLocationY, int displayFrameTop, int displayFrameBottom, boolean canResize)1699     private boolean positionInDisplayVertical(@NonNull LayoutParams outParams, int height,
1700             int drawingLocationY, int screenLocationY, int displayFrameTop, int displayFrameBottom,
1701             boolean canResize) {
1702         boolean fitsInDisplay = true;
1703 
1704         final int winOffsetY = screenLocationY - drawingLocationY;
1705         outParams.y += winOffsetY;
1706         outParams.height = height;
1707 
1708         final int bottom = outParams.y + height;
1709         if (bottom > displayFrameBottom) {
1710             // The popup is too far down, move it back in.
1711             outParams.y -= bottom - displayFrameBottom;
1712         }
1713 
1714         if (outParams.y < displayFrameTop) {
1715             // The popup is too far up, move it back in and clip if
1716             // it's still too large.
1717             outParams.y = displayFrameTop;
1718 
1719             final int displayFrameHeight = displayFrameBottom - displayFrameTop;
1720             if (canResize && height > displayFrameHeight) {
1721                 outParams.height = displayFrameHeight;
1722             } else {
1723                 fitsInDisplay = false;
1724             }
1725         }
1726 
1727         outParams.y -= winOffsetY;
1728 
1729         return fitsInDisplay;
1730     }
1731 
tryFitHorizontal(@onNull LayoutParams outParams, int xOffset, int width, int anchorWidth, int drawingLocationX, int screenLocationX, int displayFrameLeft, int displayFrameRight, boolean allowResize)1732     private boolean tryFitHorizontal(@NonNull LayoutParams outParams, int xOffset, int width,
1733             int anchorWidth, int drawingLocationX, int screenLocationX, int displayFrameLeft,
1734             int displayFrameRight, boolean allowResize) {
1735         final int winOffsetX = screenLocationX - drawingLocationX;
1736         final int anchorLeftInScreen = outParams.x + winOffsetX;
1737         final int spaceRight = displayFrameRight - anchorLeftInScreen;
1738         if (anchorLeftInScreen >= 0 && width <= spaceRight) {
1739             return true;
1740         }
1741 
1742         if (positionInDisplayHorizontal(outParams, width, drawingLocationX, screenLocationX,
1743                 displayFrameLeft, displayFrameRight, allowResize)) {
1744             return true;
1745         }
1746 
1747         return false;
1748     }
1749 
positionInDisplayHorizontal(@onNull LayoutParams outParams, int width, int drawingLocationX, int screenLocationX, int displayFrameLeft, int displayFrameRight, boolean canResize)1750     private boolean positionInDisplayHorizontal(@NonNull LayoutParams outParams, int width,
1751             int drawingLocationX, int screenLocationX, int displayFrameLeft, int displayFrameRight,
1752             boolean canResize) {
1753         boolean fitsInDisplay = true;
1754 
1755         // Use screen coordinates for comparison against display frame.
1756         final int winOffsetX = screenLocationX - drawingLocationX;
1757         outParams.x += winOffsetX;
1758 
1759         final int right = outParams.x + width;
1760         if (right > displayFrameRight) {
1761             // The popup is too far right, move it back in.
1762             outParams.x -= right - displayFrameRight;
1763         }
1764 
1765         if (outParams.x < displayFrameLeft) {
1766             // The popup is too far left, move it back in and clip if it's
1767             // still too large.
1768             outParams.x = displayFrameLeft;
1769 
1770             final int displayFrameWidth = displayFrameRight - displayFrameLeft;
1771             if (canResize && width > displayFrameWidth) {
1772                 outParams.width = displayFrameWidth;
1773             } else {
1774                 fitsInDisplay = false;
1775             }
1776         }
1777 
1778         outParams.x -= winOffsetX;
1779 
1780         return fitsInDisplay;
1781     }
1782 
1783     /**
1784      * Returns the maximum height that is available for the popup to be
1785      * completely shown. It is recommended that this height be the maximum for
1786      * the popup's height, otherwise it is possible that the popup will be
1787      * clipped.
1788      *
1789      * @param anchor The view on which the popup window must be anchored.
1790      * @return The maximum available height for the popup to be completely
1791      *         shown.
1792      */
getMaxAvailableHeight(@onNull View anchor)1793     public int getMaxAvailableHeight(@NonNull View anchor) {
1794         return getMaxAvailableHeight(anchor, 0);
1795     }
1796 
1797     /**
1798      * Returns the maximum height that is available for the popup to be
1799      * completely shown. It is recommended that this height be the maximum for
1800      * the popup's height, otherwise it is possible that the popup will be
1801      * clipped.
1802      *
1803      * @param anchor The view on which the popup window must be anchored.
1804      * @param yOffset y offset from the view's bottom edge
1805      * @return The maximum available height for the popup to be completely
1806      *         shown.
1807      */
getMaxAvailableHeight(@onNull View anchor, int yOffset)1808     public int getMaxAvailableHeight(@NonNull View anchor, int yOffset) {
1809         return getMaxAvailableHeight(anchor, yOffset, false);
1810     }
1811 
1812     /**
1813      * Returns the maximum height that is available for the popup to be
1814      * completely shown, optionally ignoring any bottom decorations such as
1815      * the input method. It is recommended that this height be the maximum for
1816      * the popup's height, otherwise it is possible that the popup will be
1817      * clipped.
1818      *
1819      * @param anchor The view on which the popup window must be anchored.
1820      * @param yOffset y offset from the view's bottom edge
1821      * @param ignoreBottomDecorations if true, the height returned will be
1822      *        all the way to the bottom of the display, ignoring any
1823      *        bottom decorations
1824      * @return The maximum available height for the popup to be completely
1825      *         shown.
1826      */
getMaxAvailableHeight( @onNull View anchor, int yOffset, boolean ignoreBottomDecorations)1827     public int getMaxAvailableHeight(
1828             @NonNull View anchor, int yOffset, boolean ignoreBottomDecorations) {
1829         Rect displayFrame = null;
1830         final Rect visibleDisplayFrame = new Rect();
1831 
1832         final View appView = getAppRootView(anchor);
1833         appView.getWindowVisibleDisplayFrame(visibleDisplayFrame);
1834         if (ignoreBottomDecorations) {
1835             // In the ignore bottom decorations case we want to
1836             // still respect all other decorations so we use the inset visible
1837             // frame on the top right and left and take the bottom
1838             // value from the full frame.
1839             displayFrame = new Rect();
1840             anchor.getWindowDisplayFrame(displayFrame);
1841             displayFrame.top = visibleDisplayFrame.top;
1842             displayFrame.right = visibleDisplayFrame.right;
1843             displayFrame.left = visibleDisplayFrame.left;
1844         } else {
1845             displayFrame = visibleDisplayFrame;
1846         }
1847 
1848         final int[] anchorPos = mTmpDrawingLocation;
1849         anchor.getLocationOnScreen(anchorPos);
1850 
1851         final int bottomEdge = displayFrame.bottom;
1852 
1853         final int distanceToBottom;
1854         if (mOverlapAnchor) {
1855             distanceToBottom = bottomEdge - anchorPos[1] - yOffset;
1856         } else {
1857             distanceToBottom = bottomEdge - (anchorPos[1] + anchor.getHeight()) - yOffset;
1858         }
1859         final int distanceToTop = anchorPos[1] - displayFrame.top + yOffset;
1860 
1861         // anchorPos[1] is distance from anchor to top of screen
1862         int returnedHeight = Math.max(distanceToBottom, distanceToTop);
1863         if (mBackground != null) {
1864             mBackground.getPadding(mTempRect);
1865             returnedHeight -= mTempRect.top + mTempRect.bottom;
1866         }
1867 
1868         return returnedHeight;
1869     }
1870 
1871     /**
1872      * Disposes of the popup window. This method can be invoked only after
1873      * {@link #showAsDropDown(android.view.View)} has been executed. Failing
1874      * that, calling this method will have no effect.
1875      *
1876      * @see #showAsDropDown(android.view.View)
1877      */
dismiss()1878     public void dismiss() {
1879         if (!isShowing() || isTransitioningToDismiss()) {
1880             return;
1881         }
1882 
1883         final PopupDecorView decorView = mDecorView;
1884         final View contentView = mContentView;
1885 
1886         final ViewGroup contentHolder;
1887         final ViewParent contentParent = contentView.getParent();
1888         if (contentParent instanceof ViewGroup) {
1889             contentHolder = ((ViewGroup) contentParent);
1890         } else {
1891             contentHolder = null;
1892         }
1893 
1894         // Ensure any ongoing or pending transitions are canceled.
1895         decorView.cancelTransitions();
1896 
1897         mIsShowing = false;
1898         mIsTransitioningToDismiss = true;
1899 
1900         // This method may be called as part of window detachment, in which
1901         // case the anchor view (and its root) will still return true from
1902         // isAttachedToWindow() during execution of this method; however, we
1903         // can expect the OnAttachStateChangeListener to have been called prior
1904         // to executing this method, so we can rely on that instead.
1905         final Transition exitTransition = mExitTransition;
1906         if (exitTransition != null && decorView.isLaidOut()
1907                 && (mIsAnchorRootAttached || mAnchorRoot == null)) {
1908             // The decor view is non-interactive and non-IME-focusable during exit transitions.
1909             final LayoutParams p = (LayoutParams) decorView.getLayoutParams();
1910             p.flags |= LayoutParams.FLAG_NOT_TOUCHABLE;
1911             p.flags |= LayoutParams.FLAG_NOT_FOCUSABLE;
1912             p.flags &= ~LayoutParams.FLAG_ALT_FOCUSABLE_IM;
1913             mWindowManager.updateViewLayout(decorView, p);
1914 
1915             final View anchorRoot = mAnchorRoot != null ? mAnchorRoot.get() : null;
1916             final Rect epicenter = getTransitionEpicenter();
1917 
1918             // Once we start dismissing the decor view, all state (including
1919             // the anchor root) needs to be moved to the decor view since we
1920             // may open another popup while it's busy exiting.
1921             decorView.startExitTransition(exitTransition, anchorRoot, epicenter,
1922                     new TransitionListenerAdapter() {
1923                         @Override
1924                         public void onTransitionEnd(Transition transition) {
1925                             dismissImmediate(decorView, contentHolder, contentView);
1926                         }
1927                     });
1928         } else {
1929             dismissImmediate(decorView, contentHolder, contentView);
1930         }
1931 
1932         // Clears the anchor view.
1933         detachFromAnchor();
1934 
1935         if (mOnDismissListener != null) {
1936             mOnDismissListener.onDismiss();
1937         }
1938     }
1939 
1940     /**
1941      * Returns the window-relative epicenter bounds to be used by enter and
1942      * exit transitions.
1943      * <p>
1944      * <strong>Note:</strong> This is distinct from the rect passed to
1945      * {@link #setEpicenterBounds(Rect)}, which is anchor-relative.
1946      *
1947      * @return the window-relative epicenter bounds to be used by enter and
1948      *         exit transitions
1949      *
1950      * @hide
1951      */
getTransitionEpicenter()1952     protected final Rect getTransitionEpicenter() {
1953         final View anchor = mAnchor != null ? mAnchor.get() : null;
1954         final View decor = mDecorView;
1955         if (anchor == null || decor == null) {
1956             return null;
1957         }
1958 
1959         final int[] anchorLocation = anchor.getLocationOnScreen();
1960         final int[] popupLocation = mDecorView.getLocationOnScreen();
1961 
1962         // Compute the position of the anchor relative to the popup.
1963         final Rect bounds = new Rect(0, 0, anchor.getWidth(), anchor.getHeight());
1964         bounds.offset(anchorLocation[0] - popupLocation[0], anchorLocation[1] - popupLocation[1]);
1965 
1966         // Use anchor-relative epicenter, if specified.
1967         if (mEpicenterBounds != null) {
1968             final int offsetX = bounds.left;
1969             final int offsetY = bounds.top;
1970             bounds.set(mEpicenterBounds);
1971             bounds.offset(offsetX, offsetY);
1972         }
1973 
1974         return bounds;
1975     }
1976 
1977     /**
1978      * Removes the popup from the window manager and tears down the supporting
1979      * view hierarchy, if necessary.
1980      */
dismissImmediate(View decorView, ViewGroup contentHolder, View contentView)1981     private void dismissImmediate(View decorView, ViewGroup contentHolder, View contentView) {
1982         // If this method gets called and the decor view doesn't have a parent,
1983         // then it was either never added or was already removed. That should
1984         // never happen, but it's worth checking to avoid potential crashes.
1985         if (decorView.getParent() != null) {
1986             mWindowManager.removeViewImmediate(decorView);
1987         }
1988 
1989         if (contentHolder != null) {
1990             contentHolder.removeView(contentView);
1991         }
1992 
1993         // This needs to stay until after all transitions have ended since we
1994         // need the reference to cancel transitions in preparePopup().
1995         mDecorView = null;
1996         mBackgroundView = null;
1997         mIsTransitioningToDismiss = false;
1998     }
1999 
2000     /**
2001      * Sets the listener to be called when the window is dismissed.
2002      *
2003      * @param onDismissListener The listener.
2004      */
setOnDismissListener(OnDismissListener onDismissListener)2005     public void setOnDismissListener(OnDismissListener onDismissListener) {
2006         mOnDismissListener = onDismissListener;
2007     }
2008 
2009     /** @hide */
getOnDismissListener()2010     protected final OnDismissListener getOnDismissListener() {
2011         return mOnDismissListener;
2012     }
2013 
2014     /**
2015      * Updates the state of the popup window, if it is currently being displayed,
2016      * from the currently set state.
2017      * <p>
2018      * This includes:
2019      * <ul>
2020      *     <li>{@link #setClippingEnabled(boolean)}</li>
2021      *     <li>{@link #setFocusable(boolean)}</li>
2022      *     <li>{@link #setIgnoreCheekPress()}</li>
2023      *     <li>{@link #setInputMethodMode(int)}</li>
2024      *     <li>{@link #setTouchable(boolean)}</li>
2025      *     <li>{@link #setAnimationStyle(int)}</li>
2026      * </ul>
2027      */
update()2028     public void update() {
2029         if (!isShowing() || !hasContentView()) {
2030             return;
2031         }
2032 
2033         final WindowManager.LayoutParams p = getDecorViewLayoutParams();
2034 
2035         boolean update = false;
2036 
2037         final int newAnim = computeAnimationResource();
2038         if (newAnim != p.windowAnimations) {
2039             p.windowAnimations = newAnim;
2040             update = true;
2041         }
2042 
2043         final int newFlags = computeFlags(p.flags);
2044         if (newFlags != p.flags) {
2045             p.flags = newFlags;
2046             update = true;
2047         }
2048 
2049         final int newGravity = computeGravity();
2050         if (newGravity != p.gravity) {
2051             p.gravity = newGravity;
2052             update = true;
2053         }
2054 
2055         if (update) {
2056             update(mAnchor != null ? mAnchor.get() : null, p);
2057         }
2058     }
2059 
2060     /** @hide */
update(View anchor, WindowManager.LayoutParams params)2061     protected void update(View anchor, WindowManager.LayoutParams params) {
2062         setLayoutDirectionFromAnchor();
2063         mWindowManager.updateViewLayout(mDecorView, params);
2064     }
2065 
2066     /**
2067      * Updates the dimension of the popup window.
2068      * <p>
2069      * Calling this function also updates the window with the current popup
2070      * state as described for {@link #update()}.
2071      *
2072      * @param width the new width in pixels, must be >= 0 or -1 to ignore
2073      * @param height the new height in pixels, must be >= 0 or -1 to ignore
2074      */
update(int width, int height)2075     public void update(int width, int height) {
2076         final WindowManager.LayoutParams p = getDecorViewLayoutParams();
2077         update(p.x, p.y, width, height, false);
2078     }
2079 
2080     /**
2081      * Updates the position and the dimension of the popup window.
2082      * <p>
2083      * Width and height can be set to -1 to update location only. Calling this
2084      * function also updates the window with the current popup state as
2085      * described for {@link #update()}.
2086      *
2087      * @param x the new x location
2088      * @param y the new y location
2089      * @param width the new width in pixels, must be >= 0 or -1 to ignore
2090      * @param height the new height in pixels, must be >= 0 or -1 to ignore
2091      */
update(int x, int y, int width, int height)2092     public void update(int x, int y, int width, int height) {
2093         update(x, y, width, height, false);
2094     }
2095 
2096     /**
2097      * Updates the position and the dimension of the popup window.
2098      * <p>
2099      * Width and height can be set to -1 to update location only. Calling this
2100      * function also updates the window with the current popup state as
2101      * described for {@link #update()}.
2102      *
2103      * @param x the new x location
2104      * @param y the new y location
2105      * @param width the new width in pixels, must be >= 0 or -1 to ignore
2106      * @param height the new height in pixels, must be >= 0 or -1 to ignore
2107      * @param force {@code true} to reposition the window even if the specified
2108      *              position already seems to correspond to the LayoutParams,
2109      *              {@code false} to only reposition if needed
2110      */
update(int x, int y, int width, int height, boolean force)2111     public void update(int x, int y, int width, int height, boolean force) {
2112         if (width >= 0) {
2113             mLastWidth = width;
2114             setWidth(width);
2115         }
2116 
2117         if (height >= 0) {
2118             mLastHeight = height;
2119             setHeight(height);
2120         }
2121 
2122         if (!isShowing() || !hasContentView()) {
2123             return;
2124         }
2125 
2126         final WindowManager.LayoutParams p = getDecorViewLayoutParams();
2127 
2128         boolean update = force;
2129 
2130         final int finalWidth = mWidthMode < 0 ? mWidthMode : mLastWidth;
2131         if (width != -1 && p.width != finalWidth) {
2132             p.width = mLastWidth = finalWidth;
2133             update = true;
2134         }
2135 
2136         final int finalHeight = mHeightMode < 0 ? mHeightMode : mLastHeight;
2137         if (height != -1 && p.height != finalHeight) {
2138             p.height = mLastHeight = finalHeight;
2139             update = true;
2140         }
2141 
2142         if (p.x != x) {
2143             p.x = x;
2144             update = true;
2145         }
2146 
2147         if (p.y != y) {
2148             p.y = y;
2149             update = true;
2150         }
2151 
2152         final int newAnim = computeAnimationResource();
2153         if (newAnim != p.windowAnimations) {
2154             p.windowAnimations = newAnim;
2155             update = true;
2156         }
2157 
2158         final int newFlags = computeFlags(p.flags);
2159         if (newFlags != p.flags) {
2160             p.flags = newFlags;
2161             update = true;
2162         }
2163 
2164         final int newGravity = computeGravity();
2165         if (newGravity != p.gravity) {
2166             p.gravity = newGravity;
2167             update = true;
2168         }
2169 
2170         View anchor = null;
2171         int newAccessibilityIdOfAnchor = -1;
2172 
2173         if (mAnchor != null && mAnchor.get() != null) {
2174             anchor = mAnchor.get();
2175             newAccessibilityIdOfAnchor = anchor.getAccessibilityViewId();
2176         }
2177 
2178         if (newAccessibilityIdOfAnchor != p.accessibilityIdOfAnchor) {
2179             p.accessibilityIdOfAnchor = newAccessibilityIdOfAnchor;
2180             update = true;
2181         }
2182 
2183         if (update) {
2184             update(anchor, p);
2185         }
2186     }
2187 
2188     /** @hide */
2189     protected boolean hasContentView() {
2190         return mContentView != null;
2191     }
2192 
2193     /** @hide */
2194     protected boolean hasDecorView() {
2195         return mDecorView != null;
2196     }
2197 
2198     /** @hide */
2199     protected WindowManager.LayoutParams getDecorViewLayoutParams() {
2200         return (WindowManager.LayoutParams) mDecorView.getLayoutParams();
2201     }
2202 
2203     /**
2204      * Updates the position and the dimension of the popup window.
2205      * <p>
2206      * Calling this function also updates the window with the current popup
2207      * state as described for {@link #update()}.
2208      *
2209      * @param anchor the popup's anchor view
2210      * @param width the new width in pixels, must be >= 0 or -1 to ignore
2211      * @param height the new height in pixels, must be >= 0 or -1 to ignore
2212      */
2213     public void update(View anchor, int width, int height) {
2214         update(anchor, false, 0, 0, width, height);
2215     }
2216 
2217     /**
2218      * Updates the position and the dimension of the popup window.
2219      * <p>
2220      * Width and height can be set to -1 to update location only. Calling this
2221      * function also updates the window with the current popup state as
2222      * described for {@link #update()}.
2223      * <p>
2224      * If the view later scrolls to move {@code anchor} to a different
2225      * location, the popup will be moved correspondingly.
2226      *
2227      * @param anchor the popup's anchor view
2228      * @param xoff x offset from the view's left edge
2229      * @param yoff y offset from the view's bottom edge
2230      * @param width the new width in pixels, must be >= 0 or -1 to ignore
2231      * @param height the new height in pixels, must be >= 0 or -1 to ignore
2232      */
2233     public void update(View anchor, int xoff, int yoff, int width, int height) {
2234         update(anchor, true, xoff, yoff, width, height);
2235     }
2236 
2237     private void update(View anchor, boolean updateLocation, int xoff, int yoff,
2238             int width, int height) {
2239 
2240         if (!isShowing() || !hasContentView()) {
2241             return;
2242         }
2243 
2244         final WeakReference<View> oldAnchor = mAnchor;
2245         final int gravity = mAnchoredGravity;
2246 
2247         final boolean needsUpdate = updateLocation && (mAnchorXoff != xoff || mAnchorYoff != yoff);
2248         if (oldAnchor == null || oldAnchor.get() != anchor || (needsUpdate && !mIsDropdown)) {
2249             attachToAnchor(anchor, xoff, yoff, gravity);
2250         } else if (needsUpdate) {
2251             // No need to register again if this is a DropDown, showAsDropDown already did.
2252             mAnchorXoff = xoff;
2253             mAnchorYoff = yoff;
2254         }
2255 
2256         final WindowManager.LayoutParams p = getDecorViewLayoutParams();
2257         final int oldGravity = p.gravity;
2258         final int oldWidth = p.width;
2259         final int oldHeight = p.height;
2260         final int oldX = p.x;
2261         final int oldY = p.y;
2262 
2263         // If an explicit width/height has not specified, use the most recent
2264         // explicitly specified value (either from setWidth/Height or update).
2265         if (width < 0) {
2266             width = mWidth;
2267         }
2268         if (height < 0) {
2269             height = mHeight;
2270         }
2271 
2272         final boolean aboveAnchor = findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff,
2273                 width, height, gravity, mAllowScrollingAnchorParent);
2274         updateAboveAnchor(aboveAnchor);
2275 
2276         final boolean paramsChanged = oldGravity != p.gravity || oldX != p.x || oldY != p.y
2277                 || oldWidth != p.width || oldHeight != p.height;
2278 
2279         // If width and mWidth were both < 0 then we have a MATCH_PARENT or
2280         // WRAP_CONTENT case. findDropDownPosition will have resolved this to
2281         // absolute values, but we don't want to update mWidth/mHeight to these
2282         // absolute values.
2283         final int newWidth = width < 0 ? width : p.width;
2284         final int newHeight = height < 0 ? height : p.height;
2285         update(p.x, p.y, newWidth, newHeight, paramsChanged);
2286     }
2287 
2288     /**
2289      * Listener that is called when this popup window is dismissed.
2290      */
2291     public interface OnDismissListener {
2292         /**
2293          * Called when this popup window is dismissed.
2294          */
2295         public void onDismiss();
2296     }
2297 
2298     /** @hide */
2299     protected void detachFromAnchor() {
2300         final View anchor = getAnchor();
2301         if (anchor != null) {
2302             final ViewTreeObserver vto = anchor.getViewTreeObserver();
2303             vto.removeOnScrollChangedListener(mOnScrollChangedListener);
2304             anchor.removeOnAttachStateChangeListener(mOnAnchorDetachedListener);
2305         }
2306 
2307         final View anchorRoot = mAnchorRoot != null ? mAnchorRoot.get() : null;
2308         if (anchorRoot != null) {
2309             anchorRoot.removeOnAttachStateChangeListener(mOnAnchorRootDetachedListener);
2310             anchorRoot.removeOnLayoutChangeListener(mOnLayoutChangeListener);
2311         }
2312 
2313         mAnchor = null;
2314         mAnchorRoot = null;
2315         mIsAnchorRootAttached = false;
2316     }
2317 
2318     /** @hide */
2319     protected void attachToAnchor(View anchor, int xoff, int yoff, int gravity) {
2320         detachFromAnchor();
2321 
2322         final ViewTreeObserver vto = anchor.getViewTreeObserver();
2323         if (vto != null) {
2324             vto.addOnScrollChangedListener(mOnScrollChangedListener);
2325         }
2326         anchor.addOnAttachStateChangeListener(mOnAnchorDetachedListener);
2327 
2328         final View anchorRoot = anchor.getRootView();
2329         anchorRoot.addOnAttachStateChangeListener(mOnAnchorRootDetachedListener);
2330         anchorRoot.addOnLayoutChangeListener(mOnLayoutChangeListener);
2331 
2332         mAnchor = new WeakReference<>(anchor);
2333         mAnchorRoot = new WeakReference<>(anchorRoot);
2334         mIsAnchorRootAttached = anchorRoot.isAttachedToWindow();
2335         mParentRootView = mAnchorRoot;
2336 
2337         mAnchorXoff = xoff;
2338         mAnchorYoff = yoff;
2339         mAnchoredGravity = gravity;
2340     }
2341 
2342     /** @hide */
2343     protected @Nullable View getAnchor() {
2344         return mAnchor != null ? mAnchor.get() : null;
2345     }
2346 
2347     private void alignToAnchor() {
2348         final View anchor = mAnchor != null ? mAnchor.get() : null;
2349         if (anchor != null && anchor.isAttachedToWindow() && hasDecorView()) {
2350             final WindowManager.LayoutParams p = getDecorViewLayoutParams();
2351 
2352             updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff,
2353                     p.width, p.height, mAnchoredGravity, false));
2354             update(p.x, p.y, -1, -1, true);
2355         }
2356     }
2357 
2358     private View getAppRootView(View anchor) {
2359         final View appWindowView = WindowManagerGlobal.getInstance().getWindowView(
2360                 anchor.getApplicationWindowToken());
2361         if (appWindowView != null) {
2362             return appWindowView;
2363         }
2364         return anchor.getRootView();
2365     }
2366 
2367     private class PopupDecorView extends FrameLayout {
2368         /** Runnable used to clean up listeners after exit transition. */
2369         private Runnable mCleanupAfterExit;
2370 
2371         public PopupDecorView(Context context) {
2372             super(context);
2373         }
2374 
2375         @Override
2376         public boolean dispatchKeyEvent(KeyEvent event) {
2377             if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
2378                 if (getKeyDispatcherState() == null) {
2379                     return super.dispatchKeyEvent(event);
2380                 }
2381 
2382                 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
2383                     final KeyEvent.DispatcherState state = getKeyDispatcherState();
2384                     if (state != null) {
2385                         state.startTracking(event, this);
2386                     }
2387                     return true;
2388                 } else if (event.getAction() == KeyEvent.ACTION_UP) {
2389                     final KeyEvent.DispatcherState state = getKeyDispatcherState();
2390                     if (state != null && state.isTracking(event) && !event.isCanceled()) {
2391                         dismiss();
2392                         return true;
2393                     }
2394                 }
2395                 return super.dispatchKeyEvent(event);
2396             } else {
2397                 return super.dispatchKeyEvent(event);
2398             }
2399         }
2400 
2401         @Override
2402         public boolean dispatchTouchEvent(MotionEvent ev) {
2403             if (mTouchInterceptor != null && mTouchInterceptor.onTouch(this, ev)) {
2404                 return true;
2405             }
2406             return super.dispatchTouchEvent(ev);
2407         }
2408 
2409         @Override
2410         public boolean onTouchEvent(MotionEvent event) {
2411             final int x = (int) event.getX();
2412             final int y = (int) event.getY();
2413 
2414             if ((event.getAction() == MotionEvent.ACTION_DOWN)
2415                     && ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) {
2416                 dismiss();
2417                 return true;
2418             } else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
2419                 dismiss();
2420                 return true;
2421             } else {
2422                 return super.onTouchEvent(event);
2423             }
2424         }
2425 
2426         /**
2427          * Requests that an enter transition run after the next layout pass.
2428          */
2429         public void requestEnterTransition(Transition transition) {
2430             final ViewTreeObserver observer = getViewTreeObserver();
2431             if (observer != null && transition != null) {
2432                 final Transition enterTransition = transition.clone();
2433 
2434                 // Postpone the enter transition after the first layout pass.
2435                 observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
2436                     @Override
2437                     public void onGlobalLayout() {
2438                         final ViewTreeObserver observer = getViewTreeObserver();
2439                         if (observer != null) {
2440                             observer.removeOnGlobalLayoutListener(this);
2441                         }
2442 
2443                         final Rect epicenter = getTransitionEpicenter();
2444                         enterTransition.setEpicenterCallback(new EpicenterCallback() {
2445                             @Override
2446                             public Rect onGetEpicenter(Transition transition) {
2447                                 return epicenter;
2448                             }
2449                         });
2450                         startEnterTransition(enterTransition);
2451                     }
2452                 });
2453             }
2454         }
2455 
2456         /**
2457          * Starts the pending enter transition, if one is set.
2458          */
2459         private void startEnterTransition(Transition enterTransition) {
2460             final int count = getChildCount();
2461             for (int i = 0; i < count; i++) {
2462                 final View child = getChildAt(i);
2463                 enterTransition.addTarget(child);
2464                 child.setVisibility(View.INVISIBLE);
2465             }
2466 
2467             TransitionManager.beginDelayedTransition(this, enterTransition);
2468 
2469             for (int i = 0; i < count; i++) {
2470                 final View child = getChildAt(i);
2471                 child.setVisibility(View.VISIBLE);
2472             }
2473         }
2474 
2475         /**
2476          * Starts an exit transition immediately.
2477          * <p>
2478          * <strong>Note:</strong> The transition listener is guaranteed to have
2479          * its {@code onTransitionEnd} method called even if the transition
2480          * never starts.
2481          */
2482         public void startExitTransition(@NonNull Transition transition,
2483                 @Nullable final View anchorRoot, @Nullable final Rect epicenter,
2484                 @NonNull final TransitionListener listener) {
2485             if (transition == null) {
2486                 return;
2487             }
2488 
2489             // The anchor view's window may go away while we're executing our
2490             // transition, in which case we need to end the transition
2491             // immediately and execute the listener to remove the popup.
2492             if (anchorRoot != null) {
2493                 anchorRoot.addOnAttachStateChangeListener(mOnAnchorRootDetachedListener);
2494             }
2495 
2496             // The cleanup runnable MUST be called even if the transition is
2497             // canceled before it starts (and thus can't call onTransitionEnd).
2498             mCleanupAfterExit = () -> {
2499                 listener.onTransitionEnd(transition);
2500 
2501                 if (anchorRoot != null) {
2502                     anchorRoot.removeOnAttachStateChangeListener(mOnAnchorRootDetachedListener);
2503                 }
2504 
2505                 // The listener was called. Our job here is done.
2506                 mCleanupAfterExit = null;
2507             };
2508 
2509             final Transition exitTransition = transition.clone();
2510             exitTransition.addListener(new TransitionListenerAdapter() {
2511                 @Override
2512                 public void onTransitionEnd(Transition t) {
2513                     t.removeListener(this);
2514 
2515                     // This null check shouldn't be necessary, but it's easier
2516                     // to check here than it is to test every possible case.
2517                     if (mCleanupAfterExit != null) {
2518                         mCleanupAfterExit.run();
2519                     }
2520                 }
2521             });
2522             exitTransition.setEpicenterCallback(new EpicenterCallback() {
2523                 @Override
2524                 public Rect onGetEpicenter(Transition transition) {
2525                     return epicenter;
2526                 }
2527             });
2528 
2529             final int count = getChildCount();
2530             for (int i = 0; i < count; i++) {
2531                 final View child = getChildAt(i);
2532                 exitTransition.addTarget(child);
2533             }
2534 
2535             TransitionManager.beginDelayedTransition(this, exitTransition);
2536 
2537             for (int i = 0; i < count; i++) {
2538                 final View child = getChildAt(i);
2539                 child.setVisibility(View.INVISIBLE);
2540             }
2541         }
2542 
2543         /**
2544          * Cancels all pending or current transitions.
2545          */
2546         public void cancelTransitions() {
2547             TransitionManager.endTransitions(this);
2548 
2549             // If the cleanup runnable is still around, that means the
2550             // transition never started. We should run it now to clean up.
2551             if (mCleanupAfterExit != null) {
2552                 mCleanupAfterExit.run();
2553             }
2554         }
2555 
2556         private final OnAttachStateChangeListener mOnAnchorRootDetachedListener =
2557                 new OnAttachStateChangeListener() {
2558                     @Override
2559                     public void onViewAttachedToWindow(View v) {}
2560 
2561                     @Override
2562                     public void onViewDetachedFromWindow(View v) {
2563                         v.removeOnAttachStateChangeListener(this);
2564 
2565                         TransitionManager.endTransitions(PopupDecorView.this);
2566                     }
2567                 };
2568 
2569         @Override
2570         public void requestKeyboardShortcuts(List<KeyboardShortcutGroup> list, int deviceId) {
2571             if (mParentRootView != null) {
2572                 View parentRoot = mParentRootView.get();
2573                 if (parentRoot != null) {
2574                     parentRoot.requestKeyboardShortcuts(list, deviceId);
2575                 }
2576             }
2577         }
2578     }
2579 
2580     private class PopupBackgroundView extends FrameLayout {
2581         public PopupBackgroundView(Context context) {
2582             super(context);
2583         }
2584 
2585         @Override
2586         protected int[] onCreateDrawableState(int extraSpace) {
2587             if (mAboveAnchor) {
2588                 final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
2589                 View.mergeDrawableStates(drawableState, ABOVE_ANCHOR_STATE_SET);
2590                 return drawableState;
2591             } else {
2592                 return super.onCreateDrawableState(extraSpace);
2593             }
2594         }
2595     }
2596 }
2597