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