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 com.android.internal.R;
20 
21 import android.content.Context;
22 import android.content.res.Resources;
23 import android.content.res.TypedArray;
24 import android.graphics.PixelFormat;
25 import android.graphics.Rect;
26 import android.graphics.drawable.Drawable;
27 import android.graphics.drawable.StateListDrawable;
28 import android.os.Build;
29 import android.os.IBinder;
30 import android.transition.Transition;
31 import android.transition.Transition.EpicenterCallback;
32 import android.transition.Transition.TransitionListener;
33 import android.transition.Transition.TransitionListenerAdapter;
34 import android.transition.TransitionInflater;
35 import android.transition.TransitionManager;
36 import android.transition.TransitionSet;
37 import android.util.AttributeSet;
38 import android.view.Gravity;
39 import android.view.KeyEvent;
40 import android.view.MotionEvent;
41 import android.view.View;
42 import android.view.View.OnTouchListener;
43 import android.view.ViewGroup;
44 import android.view.ViewParent;
45 import android.view.ViewTreeObserver;
46 import android.view.ViewTreeObserver.OnGlobalLayoutListener;
47 import android.view.ViewTreeObserver.OnScrollChangedListener;
48 import android.view.WindowManager;
49 import android.view.WindowManager.LayoutParams;
50 
51 import java.lang.ref.WeakReference;
52 
53 /**
54  * <p>A popup window that can be used to display an arbitrary view. The popup
55  * window is a floating container that appears on top of the current
56  * activity.</p>
57  *
58  * @see android.widget.AutoCompleteTextView
59  * @see android.widget.Spinner
60  */
61 public class PopupWindow {
62     /**
63      * Mode for {@link #setInputMethodMode(int)}: the requirements for the
64      * input method should be based on the focusability of the popup.  That is
65      * if it is focusable than it needs to work with the input method, else
66      * it doesn't.
67      */
68     public static final int INPUT_METHOD_FROM_FOCUSABLE = 0;
69 
70     /**
71      * Mode for {@link #setInputMethodMode(int)}: this popup always needs to
72      * work with an input method, regardless of whether it is focusable.  This
73      * means that it will always be displayed so that the user can also operate
74      * the input method while it is shown.
75      */
76     public static final int INPUT_METHOD_NEEDED = 1;
77 
78     /**
79      * Mode for {@link #setInputMethodMode(int)}: this popup never needs to
80      * work with an input method, regardless of whether it is focusable.  This
81      * means that it will always be displayed to use as much space on the
82      * screen as needed, regardless of whether this covers the input method.
83      */
84     public static final int INPUT_METHOD_NOT_NEEDED = 2;
85 
86     private static final int DEFAULT_ANCHORED_GRAVITY = Gravity.TOP | Gravity.START;
87 
88     /**
89      * Default animation style indicating that separate animations should be
90      * used for top/bottom anchoring states.
91      */
92     private static final int ANIMATION_STYLE_DEFAULT = -1;
93 
94     private final int[] mDrawingLocation = new int[2];
95     private final int[] mScreenLocation = new int[2];
96     private final Rect mTempRect = new Rect();
97 
98     private Context mContext;
99     private WindowManager mWindowManager;
100 
101     private boolean mIsShowing;
102     private boolean mIsTransitioningToDismiss;
103     private boolean mIsDropdown;
104 
105     /** View that handles event dispatch and content transitions. */
106     private PopupDecorView mDecorView;
107 
108     /** View that holds the background and may animate during a transition. */
109     private View mBackgroundView;
110 
111     /** The contents of the popup. May be identical to the background view. */
112     private View mContentView;
113 
114     private boolean mFocusable;
115     private int mInputMethodMode = INPUT_METHOD_FROM_FOCUSABLE;
116     private int mSoftInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED;
117     private boolean mTouchable = true;
118     private boolean mOutsideTouchable = false;
119     private boolean mClippingEnabled = true;
120     private int mSplitTouchEnabled = -1;
121     private boolean mLayoutInScreen;
122     private boolean mClipToScreen;
123     private boolean mAllowScrollingAnchorParent = true;
124     private boolean mLayoutInsetDecor = false;
125     private boolean mNotTouchModal;
126     private boolean mAttachedInDecor = true;
127     private boolean mAttachedInDecorSet = false;
128 
129     private OnTouchListener mTouchInterceptor;
130 
131     private int mWidthMode;
132     private int mWidth = LayoutParams.WRAP_CONTENT;
133     private int mLastWidth;
134     private int mHeightMode;
135     private int mHeight = LayoutParams.WRAP_CONTENT;
136     private int mLastHeight;
137 
138     private int mPopupWidth;
139     private int mPopupHeight;
140 
141     private float mElevation;
142 
143     private Drawable mBackground;
144     private Drawable mAboveAnchorBackgroundDrawable;
145     private Drawable mBelowAnchorBackgroundDrawable;
146 
147     private Transition mEnterTransition;
148     private Transition mExitTransition;
149 
150     private boolean mAboveAnchor;
151     private int mWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
152 
153     private OnDismissListener mOnDismissListener;
154     private boolean mIgnoreCheekPress = false;
155 
156     private int mAnimationStyle = ANIMATION_STYLE_DEFAULT;
157 
158     private static final int[] ABOVE_ANCHOR_STATE_SET = new int[] {
159         com.android.internal.R.attr.state_above_anchor
160     };
161 
162     private WeakReference<View> mAnchor;
163 
164     private final OnScrollChangedListener mOnScrollChangedListener = new OnScrollChangedListener() {
165         @Override
166         public void onScrollChanged() {
167             final View anchor = mAnchor != null ? mAnchor.get() : null;
168             if (anchor != null && mDecorView != null) {
169                 final WindowManager.LayoutParams p = (WindowManager.LayoutParams)
170                         mDecorView.getLayoutParams();
171 
172                 updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff,
173                         mAnchoredGravity));
174                 update(p.x, p.y, -1, -1, true);
175             }
176         }
177     };
178 
179     private int mAnchorXoff;
180     private int mAnchorYoff;
181     private int mAnchoredGravity;
182     private boolean mOverlapAnchor;
183 
184     private boolean mPopupViewInitialLayoutDirectionInherited;
185 
186     /**
187      * <p>Create a new empty, non focusable popup window of dimension (0,0).</p>
188      *
189      * <p>The popup does provide a background.</p>
190      */
PopupWindow(Context context)191     public PopupWindow(Context context) {
192         this(context, null);
193     }
194 
195     /**
196      * <p>Create a new empty, non focusable popup window of dimension (0,0).</p>
197      *
198      * <p>The popup does provide a background.</p>
199      */
PopupWindow(Context context, AttributeSet attrs)200     public PopupWindow(Context context, AttributeSet attrs) {
201         this(context, attrs, com.android.internal.R.attr.popupWindowStyle);
202     }
203 
204     /**
205      * <p>Create a new empty, non focusable popup window of dimension (0,0).</p>
206      *
207      * <p>The popup does provide a background.</p>
208      */
PopupWindow(Context context, AttributeSet attrs, int defStyleAttr)209     public PopupWindow(Context context, AttributeSet attrs, int defStyleAttr) {
210         this(context, attrs, defStyleAttr, 0);
211     }
212 
213     /**
214      * <p>Create a new, empty, non focusable popup window of dimension (0,0).</p>
215      *
216      * <p>The popup does not provide a background.</p>
217      */
PopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)218     public PopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
219         mContext = context;
220         mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
221 
222         final TypedArray a = context.obtainStyledAttributes(
223                 attrs, R.styleable.PopupWindow, defStyleAttr, defStyleRes);
224         final Drawable bg = a.getDrawable(R.styleable.PopupWindow_popupBackground);
225         mElevation = a.getDimension(R.styleable.PopupWindow_popupElevation, 0);
226         mOverlapAnchor = a.getBoolean(R.styleable.PopupWindow_overlapAnchor, false);
227 
228         // Preserve default behavior from Gingerbread. If the animation is
229         // undefined or explicitly specifies the Gingerbread animation style,
230         // use a sentinel value.
231         if (a.hasValueOrEmpty(R.styleable.PopupWindow_popupAnimationStyle)) {
232             final int animStyle = a.getResourceId(R.styleable.PopupWindow_popupAnimationStyle, 0);
233             if (animStyle == R.style.Animation_PopupWindow) {
234                 mAnimationStyle = ANIMATION_STYLE_DEFAULT;
235             } else {
236                 mAnimationStyle = animStyle;
237             }
238         } else {
239             mAnimationStyle = ANIMATION_STYLE_DEFAULT;
240         }
241 
242         final Transition enterTransition = getTransition(a.getResourceId(
243                 R.styleable.PopupWindow_popupEnterTransition, 0));
244         final Transition exitTransition;
245         if (a.hasValueOrEmpty(R.styleable.PopupWindow_popupExitTransition)) {
246             exitTransition = getTransition(a.getResourceId(
247                     R.styleable.PopupWindow_popupExitTransition, 0));
248         } else {
249             exitTransition = enterTransition == null ? null : enterTransition.clone();
250         }
251 
252         a.recycle();
253 
254         setEnterTransition(enterTransition);
255         setExitTransition(exitTransition);
256         setBackgroundDrawable(bg);
257     }
258 
259     /**
260      * <p>Create a new empty, non focusable popup window of dimension (0,0).</p>
261      *
262      * <p>The popup does not provide any background. This should be handled
263      * by the content view.</p>
264      */
PopupWindow()265     public PopupWindow() {
266         this(null, 0, 0);
267     }
268 
269     /**
270      * <p>Create a new non focusable popup window which can display the
271      * <tt>contentView</tt>. The dimension of the window are (0,0).</p>
272      *
273      * <p>The popup does not provide any background. This should be handled
274      * by the content view.</p>
275      *
276      * @param contentView the popup's content
277      */
PopupWindow(View contentView)278     public PopupWindow(View contentView) {
279         this(contentView, 0, 0);
280     }
281 
282     /**
283      * <p>Create a new empty, non focusable popup window. The dimension of the
284      * window must be passed to this constructor.</p>
285      *
286      * <p>The popup does not provide any background. This should be handled
287      * by the content view.</p>
288      *
289      * @param width the popup's width
290      * @param height the popup's height
291      */
PopupWindow(int width, int height)292     public PopupWindow(int width, int height) {
293         this(null, width, height);
294     }
295 
296     /**
297      * <p>Create a new non focusable popup window which can display the
298      * <tt>contentView</tt>. The dimension of the window must be passed to
299      * this constructor.</p>
300      *
301      * <p>The popup does not provide any background. This should be handled
302      * by the content view.</p>
303      *
304      * @param contentView the popup's content
305      * @param width the popup's width
306      * @param height the popup's height
307      */
PopupWindow(View contentView, int width, int height)308     public PopupWindow(View contentView, int width, int height) {
309         this(contentView, width, height, false);
310     }
311 
312     /**
313      * <p>Create a new popup window which can display the <tt>contentView</tt>.
314      * The dimension of the window must be passed to this constructor.</p>
315      *
316      * <p>The popup does not provide any background. This should be handled
317      * by the content view.</p>
318      *
319      * @param contentView the popup's content
320      * @param width the popup's width
321      * @param height the popup's height
322      * @param focusable true if the popup can be focused, false otherwise
323      */
PopupWindow(View contentView, int width, int height, boolean focusable)324     public PopupWindow(View contentView, int width, int height, boolean focusable) {
325         if (contentView != null) {
326             mContext = contentView.getContext();
327             mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
328         }
329 
330         setContentView(contentView);
331         setWidth(width);
332         setHeight(height);
333         setFocusable(focusable);
334     }
335 
setEnterTransition(Transition enterTransition)336     public void setEnterTransition(Transition enterTransition) {
337         mEnterTransition = enterTransition;
338     }
339 
setExitTransition(Transition exitTransition)340     public void setExitTransition(Transition exitTransition) {
341         mExitTransition = exitTransition;
342     }
343 
getTransition(int resId)344     private Transition getTransition(int resId) {
345         if (resId != 0 && resId != R.transition.no_transition) {
346             final TransitionInflater inflater = TransitionInflater.from(mContext);
347             final Transition transition = inflater.inflateTransition(resId);
348             if (transition != null) {
349                 final boolean isEmpty = transition instanceof TransitionSet
350                         && ((TransitionSet) transition).getTransitionCount() == 0;
351                 if (!isEmpty) {
352                     return transition;
353                 }
354             }
355         }
356         return null;
357     }
358 
359     /**
360      * Return the drawable used as the popup window's background.
361      *
362      * @return the background drawable or {@code null} if not set
363      * @see #setBackgroundDrawable(Drawable)
364      * @attr ref android.R.styleable#PopupWindow_popupBackground
365      */
getBackground()366     public Drawable getBackground() {
367         return mBackground;
368     }
369 
370     /**
371      * Specifies the background drawable for this popup window. The background
372      * can be set to {@code null}.
373      *
374      * @param background the popup's background
375      * @see #getBackground()
376      * @attr ref android.R.styleable#PopupWindow_popupBackground
377      */
setBackgroundDrawable(Drawable background)378     public void setBackgroundDrawable(Drawable background) {
379         mBackground = background;
380 
381         // If this is a StateListDrawable, try to find and store the drawable to be
382         // used when the drop-down is placed above its anchor view, and the one to be
383         // used when the drop-down is placed below its anchor view. We extract
384         // the drawables ourselves to work around a problem with using refreshDrawableState
385         // that it will take into account the padding of all drawables specified in a
386         // StateListDrawable, thus adding superfluous padding to drop-down views.
387         //
388         // We assume a StateListDrawable will have a drawable for ABOVE_ANCHOR_STATE_SET and
389         // at least one other drawable, intended for the 'below-anchor state'.
390         if (mBackground instanceof StateListDrawable) {
391             StateListDrawable stateList = (StateListDrawable) mBackground;
392 
393             // Find the above-anchor view - this one's easy, it should be labeled as such.
394             int aboveAnchorStateIndex = stateList.getStateDrawableIndex(ABOVE_ANCHOR_STATE_SET);
395 
396             // Now, for the below-anchor view, look for any other drawable specified in the
397             // StateListDrawable which is not for the above-anchor state and use that.
398             int count = stateList.getStateCount();
399             int belowAnchorStateIndex = -1;
400             for (int i = 0; i < count; i++) {
401                 if (i != aboveAnchorStateIndex) {
402                     belowAnchorStateIndex = i;
403                     break;
404                 }
405             }
406 
407             // Store the drawables we found, if we found them. Otherwise, set them both
408             // to null so that we'll just use refreshDrawableState.
409             if (aboveAnchorStateIndex != -1 && belowAnchorStateIndex != -1) {
410                 mAboveAnchorBackgroundDrawable = stateList.getStateDrawable(aboveAnchorStateIndex);
411                 mBelowAnchorBackgroundDrawable = stateList.getStateDrawable(belowAnchorStateIndex);
412             } else {
413                 mBelowAnchorBackgroundDrawable = null;
414                 mAboveAnchorBackgroundDrawable = null;
415             }
416         }
417     }
418 
419     /**
420      * @return the elevation for this popup window in pixels
421      * @see #setElevation(float)
422      * @attr ref android.R.styleable#PopupWindow_popupElevation
423      */
getElevation()424     public float getElevation() {
425         return mElevation;
426     }
427 
428     /**
429      * Specifies the elevation for this popup window.
430      *
431      * @param elevation the popup's elevation in pixels
432      * @see #getElevation()
433      * @attr ref android.R.styleable#PopupWindow_popupElevation
434      */
setElevation(float elevation)435     public void setElevation(float elevation) {
436         mElevation = elevation;
437     }
438 
439     /**
440      * <p>Return the animation style to use the popup appears and disappears</p>
441      *
442      * @return the animation style to use the popup appears and disappears
443      */
getAnimationStyle()444     public int getAnimationStyle() {
445         return mAnimationStyle;
446     }
447 
448     /**
449      * Set the flag on popup to ignore cheek press events; by default this flag
450      * is set to false
451      * which means the popup will not ignore cheek press dispatch events.
452      *
453      * <p>If the popup is showing, calling this method will take effect only
454      * the next time the popup is shown or through a manual call to one of
455      * the {@link #update()} methods.</p>
456      *
457      * @see #update()
458      */
setIgnoreCheekPress()459     public void setIgnoreCheekPress() {
460         mIgnoreCheekPress = true;
461     }
462 
463 
464     /**
465      * <p>Change the animation style resource for this popup.</p>
466      *
467      * <p>If the popup is showing, calling this method will take effect only
468      * the next time the popup is shown or through a manual call to one of
469      * the {@link #update()} methods.</p>
470      *
471      * @param animationStyle animation style to use when the popup appears
472      *      and disappears.  Set to -1 for the default animation, 0 for no
473      *      animation, or a resource identifier for an explicit animation.
474      *
475      * @see #update()
476      */
setAnimationStyle(int animationStyle)477     public void setAnimationStyle(int animationStyle) {
478         mAnimationStyle = animationStyle;
479     }
480 
481     /**
482      * <p>Return the view used as the content of the popup window.</p>
483      *
484      * @return a {@link android.view.View} representing the popup's content
485      *
486      * @see #setContentView(android.view.View)
487      */
getContentView()488     public View getContentView() {
489         return mContentView;
490     }
491 
492     /**
493      * <p>Change the popup's content. The content is represented by an instance
494      * of {@link android.view.View}.</p>
495      *
496      * <p>This method has no effect if called when the popup is showing.</p>
497      *
498      * @param contentView the new content for the popup
499      *
500      * @see #getContentView()
501      * @see #isShowing()
502      */
setContentView(View contentView)503     public void setContentView(View contentView) {
504         if (isShowing()) {
505             return;
506         }
507 
508         mContentView = contentView;
509 
510         if (mContext == null && mContentView != null) {
511             mContext = mContentView.getContext();
512         }
513 
514         if (mWindowManager == null && mContentView != null) {
515             mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
516         }
517 
518         // Setting the default for attachedInDecor based on SDK version here
519         // instead of in the constructor since we might not have the context
520         // object in the constructor. We only want to set default here if the
521         // app hasn't already set the attachedInDecor.
522         if (mContext != null && !mAttachedInDecorSet) {
523             // Attach popup window in decor frame of parent window by default for
524             // {@link Build.VERSION_CODES.LOLLIPOP_MR1} or greater. Keep current
525             // behavior of not attaching to decor frame for older SDKs.
526             setAttachedInDecor(mContext.getApplicationInfo().targetSdkVersion
527                     >= Build.VERSION_CODES.LOLLIPOP_MR1);
528         }
529 
530     }
531 
532     /**
533      * Set a callback for all touch events being dispatched to the popup
534      * window.
535      */
setTouchInterceptor(OnTouchListener l)536     public void setTouchInterceptor(OnTouchListener l) {
537         mTouchInterceptor = l;
538     }
539 
540     /**
541      * <p>Indicate whether the popup window can grab the focus.</p>
542      *
543      * @return true if the popup is focusable, false otherwise
544      *
545      * @see #setFocusable(boolean)
546      */
isFocusable()547     public boolean isFocusable() {
548         return mFocusable;
549     }
550 
551     /**
552      * <p>Changes the focusability of the popup window. When focusable, the
553      * window will grab the focus from the current focused widget if the popup
554      * contains a focusable {@link android.view.View}.  By default a popup
555      * window is not focusable.</p>
556      *
557      * <p>If the popup is showing, calling this method will take effect only
558      * the next time the popup is shown or through a manual call to one of
559      * the {@link #update()} methods.</p>
560      *
561      * @param focusable true if the popup should grab focus, false otherwise.
562      *
563      * @see #isFocusable()
564      * @see #isShowing()
565      * @see #update()
566      */
setFocusable(boolean focusable)567     public void setFocusable(boolean focusable) {
568         mFocusable = focusable;
569     }
570 
571     /**
572      * Return the current value in {@link #setInputMethodMode(int)}.
573      *
574      * @see #setInputMethodMode(int)
575      */
getInputMethodMode()576     public int getInputMethodMode() {
577         return mInputMethodMode;
578 
579     }
580 
581     /**
582      * Control how the popup operates with an input method: one of
583      * {@link #INPUT_METHOD_FROM_FOCUSABLE}, {@link #INPUT_METHOD_NEEDED},
584      * or {@link #INPUT_METHOD_NOT_NEEDED}.
585      *
586      * <p>If the popup is showing, calling this method will take effect only
587      * the next time the popup is shown or through a manual call to one of
588      * the {@link #update()} methods.</p>
589      *
590      * @see #getInputMethodMode()
591      * @see #update()
592      */
setInputMethodMode(int mode)593     public void setInputMethodMode(int mode) {
594         mInputMethodMode = mode;
595     }
596 
597     /**
598      * Sets the operating mode for the soft input area.
599      *
600      * @param mode The desired mode, see
601      *        {@link android.view.WindowManager.LayoutParams#softInputMode}
602      *        for the full list
603      *
604      * @see android.view.WindowManager.LayoutParams#softInputMode
605      * @see #getSoftInputMode()
606      */
setSoftInputMode(int mode)607     public void setSoftInputMode(int mode) {
608         mSoftInputMode = mode;
609     }
610 
611     /**
612      * Returns the current value in {@link #setSoftInputMode(int)}.
613      *
614      * @see #setSoftInputMode(int)
615      * @see android.view.WindowManager.LayoutParams#softInputMode
616      */
getSoftInputMode()617     public int getSoftInputMode() {
618         return mSoftInputMode;
619     }
620 
621     /**
622      * <p>Indicates whether the popup window receives touch events.</p>
623      *
624      * @return true if the popup is touchable, false otherwise
625      *
626      * @see #setTouchable(boolean)
627      */
isTouchable()628     public boolean isTouchable() {
629         return mTouchable;
630     }
631 
632     /**
633      * <p>Changes the touchability of the popup window. When touchable, the
634      * window will receive touch events, otherwise touch events will go to the
635      * window below it. By default the window is touchable.</p>
636      *
637      * <p>If the popup is showing, calling this method will take effect only
638      * the next time the popup is shown or through a manual call to one of
639      * the {@link #update()} methods.</p>
640      *
641      * @param touchable true if the popup should receive touch events, false otherwise
642      *
643      * @see #isTouchable()
644      * @see #isShowing()
645      * @see #update()
646      */
setTouchable(boolean touchable)647     public void setTouchable(boolean touchable) {
648         mTouchable = touchable;
649     }
650 
651     /**
652      * <p>Indicates whether the popup window will be informed of touch events
653      * outside of its window.</p>
654      *
655      * @return true if the popup is outside touchable, false otherwise
656      *
657      * @see #setOutsideTouchable(boolean)
658      */
isOutsideTouchable()659     public boolean isOutsideTouchable() {
660         return mOutsideTouchable;
661     }
662 
663     /**
664      * <p>Controls whether the pop-up will be informed of touch events outside
665      * of its window.  This only makes sense for pop-ups that are touchable
666      * but not focusable, which means touches outside of the window will
667      * be delivered to the window behind.  The default is false.</p>
668      *
669      * <p>If the popup is showing, calling this method will take effect only
670      * the next time the popup is shown or through a manual call to one of
671      * the {@link #update()} methods.</p>
672      *
673      * @param touchable true if the popup should receive outside
674      * touch events, false otherwise
675      *
676      * @see #isOutsideTouchable()
677      * @see #isShowing()
678      * @see #update()
679      */
setOutsideTouchable(boolean touchable)680     public void setOutsideTouchable(boolean touchable) {
681         mOutsideTouchable = touchable;
682     }
683 
684     /**
685      * <p>Indicates whether clipping of the popup window is enabled.</p>
686      *
687      * @return true if the clipping is enabled, false otherwise
688      *
689      * @see #setClippingEnabled(boolean)
690      */
isClippingEnabled()691     public boolean isClippingEnabled() {
692         return mClippingEnabled;
693     }
694 
695     /**
696      * <p>Allows the popup window to extend beyond the bounds of the screen. By default the
697      * window is clipped to the screen boundaries. Setting this to false will allow windows to be
698      * accurately positioned.</p>
699      *
700      * <p>If the popup is showing, calling this method will take effect only
701      * the next time the popup is shown or through a manual call to one of
702      * the {@link #update()} methods.</p>
703      *
704      * @param enabled false if the window should be allowed to extend outside of the screen
705      * @see #isShowing()
706      * @see #isClippingEnabled()
707      * @see #update()
708      */
setClippingEnabled(boolean enabled)709     public void setClippingEnabled(boolean enabled) {
710         mClippingEnabled = enabled;
711     }
712 
713     /**
714      * Clip this popup window to the screen, but not to the containing window.
715      *
716      * @param enabled True to clip to the screen.
717      * @hide
718      */
setClipToScreenEnabled(boolean enabled)719     public void setClipToScreenEnabled(boolean enabled) {
720         mClipToScreen = enabled;
721         setClippingEnabled(!enabled);
722     }
723 
724     /**
725      * Allow PopupWindow to scroll the anchor's parent to provide more room
726      * for the popup. Enabled by default.
727      *
728      * @param enabled True to scroll the anchor's parent when more room is desired by the popup.
729      */
setAllowScrollingAnchorParent(boolean enabled)730     void setAllowScrollingAnchorParent(boolean enabled) {
731         mAllowScrollingAnchorParent = enabled;
732     }
733 
734     /**
735      * <p>Indicates whether the popup window supports splitting touches.</p>
736      *
737      * @return true if the touch splitting is enabled, false otherwise
738      *
739      * @see #setSplitTouchEnabled(boolean)
740      */
isSplitTouchEnabled()741     public boolean isSplitTouchEnabled() {
742         if (mSplitTouchEnabled < 0 && mContext != null) {
743             return mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB;
744         }
745         return mSplitTouchEnabled == 1;
746     }
747 
748     /**
749      * <p>Allows the popup window to split touches across other windows that also
750      * support split touch.  When this flag is false, the first pointer
751      * that goes down determines the window to which all subsequent touches
752      * go until all pointers go up.  When this flag is true, each pointer
753      * (not necessarily the first) that goes down determines the window
754      * to which all subsequent touches of that pointer will go until that
755      * pointer goes up thereby enabling touches with multiple pointers
756      * to be split across multiple windows.</p>
757      *
758      * @param enabled true if the split touches should be enabled, false otherwise
759      * @see #isSplitTouchEnabled()
760      */
setSplitTouchEnabled(boolean enabled)761     public void setSplitTouchEnabled(boolean enabled) {
762         mSplitTouchEnabled = enabled ? 1 : 0;
763     }
764 
765     /**
766      * <p>Indicates whether the popup window will be forced into using absolute screen coordinates
767      * for positioning.</p>
768      *
769      * @return true if the window will always be positioned in screen coordinates.
770      * @hide
771      */
isLayoutInScreenEnabled()772     public boolean isLayoutInScreenEnabled() {
773         return mLayoutInScreen;
774     }
775 
776     /**
777      * <p>Allows the popup window to force the flag
778      * {@link WindowManager.LayoutParams#FLAG_LAYOUT_IN_SCREEN}, overriding default behavior.
779      * This will cause the popup to be positioned in absolute screen coordinates.</p>
780      *
781      * @param enabled true if the popup should always be positioned in screen coordinates
782      * @hide
783      */
setLayoutInScreenEnabled(boolean enabled)784     public void setLayoutInScreenEnabled(boolean enabled) {
785         mLayoutInScreen = enabled;
786     }
787 
788     /**
789      * <p>Indicates whether the popup window will be attached in the decor frame of its parent
790      * window.
791      *
792      * @return true if the window will be attached to the decor frame of its parent window.
793      *
794      * @see #setAttachedInDecor(boolean)
795      * @see WindowManager.LayoutParams#FLAG_LAYOUT_ATTACHED_IN_DECOR
796      */
isAttachedInDecor()797     public boolean isAttachedInDecor() {
798         return mAttachedInDecor;
799     }
800 
801     /**
802      * <p>This will attach the popup window to the decor frame of the parent window to avoid
803      * overlaping with screen decorations like the navigation bar. Overrides the default behavior of
804      * the flag {@link WindowManager.LayoutParams#FLAG_LAYOUT_ATTACHED_IN_DECOR}.
805      *
806      * <p>By default the flag is set on SDK version {@link Build.VERSION_CODES#LOLLIPOP_MR1} or
807      * greater and cleared on lesser SDK versions.
808      *
809      * @param enabled true if the popup should be attached to the decor frame of its parent window.
810      *
811      * @see WindowManager.LayoutParams#FLAG_LAYOUT_ATTACHED_IN_DECOR
812      */
setAttachedInDecor(boolean enabled)813     public void setAttachedInDecor(boolean enabled) {
814         mAttachedInDecor = enabled;
815         mAttachedInDecorSet = true;
816     }
817 
818     /**
819      * Allows the popup window to force the flag
820      * {@link WindowManager.LayoutParams#FLAG_LAYOUT_INSET_DECOR}, overriding default behavior.
821      * This will cause the popup to inset its content to account for system windows overlaying
822      * the screen, such as the status bar.
823      *
824      * <p>This will often be combined with {@link #setLayoutInScreenEnabled(boolean)}.
825      *
826      * @param enabled true if the popup's views should inset content to account for system windows,
827      *                the way that decor views behave for full-screen windows.
828      * @hide
829      */
setLayoutInsetDecor(boolean enabled)830     public void setLayoutInsetDecor(boolean enabled) {
831         mLayoutInsetDecor = enabled;
832     }
833 
834     /**
835      * Set the layout type for this window.
836      * <p>
837      * See {@link WindowManager.LayoutParams#type} for possible values.
838      *
839      * @param layoutType Layout type for this window.
840      *
841      * @see WindowManager.LayoutParams#type
842      */
setWindowLayoutType(int layoutType)843     public void setWindowLayoutType(int layoutType) {
844         mWindowLayoutType = layoutType;
845     }
846 
847     /**
848      * Returns the layout type for this window.
849      *
850      * @see #setWindowLayoutType(int)
851      */
getWindowLayoutType()852     public int getWindowLayoutType() {
853         return mWindowLayoutType;
854     }
855 
856     /**
857      * Set whether this window is touch modal or if outside touches will be sent to
858      * other windows behind it.
859      * @hide
860      */
setTouchModal(boolean touchModal)861     public void setTouchModal(boolean touchModal) {
862         mNotTouchModal = !touchModal;
863     }
864 
865     /**
866      * <p>Change the width and height measure specs that are given to the
867      * window manager by the popup.  By default these are 0, meaning that
868      * the current width or height is requested as an explicit size from
869      * the window manager.  You can supply
870      * {@link ViewGroup.LayoutParams#WRAP_CONTENT} or
871      * {@link ViewGroup.LayoutParams#MATCH_PARENT} to have that measure
872      * spec supplied instead, replacing the absolute width and height that
873      * has been set in the popup.</p>
874      *
875      * <p>If the popup is showing, calling this method will take effect only
876      * the next time the popup is shown.</p>
877      *
878      * @param widthSpec an explicit width measure spec mode, either
879      * {@link ViewGroup.LayoutParams#WRAP_CONTENT},
880      * {@link ViewGroup.LayoutParams#MATCH_PARENT}, or 0 to use the absolute
881      * width.
882      * @param heightSpec an explicit height measure spec mode, either
883      * {@link ViewGroup.LayoutParams#WRAP_CONTENT},
884      * {@link ViewGroup.LayoutParams#MATCH_PARENT}, or 0 to use the absolute
885      * height.
886      *
887      * @deprecated Use {@link #setWidth(int)} and {@link #setHeight(int)}.
888      */
889     @Deprecated
setWindowLayoutMode(int widthSpec, int heightSpec)890     public void setWindowLayoutMode(int widthSpec, int heightSpec) {
891         mWidthMode = widthSpec;
892         mHeightMode = heightSpec;
893     }
894 
895     /**
896      * Returns the popup's height MeasureSpec.
897      *
898      * @return the height MeasureSpec of the popup
899      * @see #setHeight(int)
900      */
getHeight()901     public int getHeight() {
902         return mHeight;
903     }
904 
905     /**
906      * Sets the popup's height MeasureSpec.
907      * <p>
908      * If the popup is showing, calling this method will take effect the next
909      * time the popup is shown.
910      *
911      * @param height the height MeasureSpec of the popup
912      * @see #getHeight()
913      * @see #isShowing()
914      */
setHeight(int height)915     public void setHeight(int height) {
916         mHeight = height;
917     }
918 
919     /**
920      * Returns the popup's width MeasureSpec.
921      *
922      * @return the width MeasureSpec of the popup
923      * @see #setWidth(int)
924      */
getWidth()925     public int getWidth() {
926         return mWidth;
927     }
928 
929     /**
930      * Sets the popup's width MeasureSpec.
931      * <p>
932      * If the popup is showing, calling this method will take effect the next
933      * time the popup is shown.
934      *
935      * @param width the width MeasureSpec of the popup
936      * @see #getWidth()
937      * @see #isShowing()
938      */
setWidth(int width)939     public void setWidth(int width) {
940         mWidth = width;
941     }
942 
943     /**
944      * Sets whether the popup window should overlap its anchor view when
945      * displayed as a drop-down.
946      * <p>
947      * If the popup is showing, calling this method will take effect only
948      * the next time the popup is shown.
949      *
950      * @param overlapAnchor Whether the popup should overlap its anchor.
951      *
952      * @see #getOverlapAnchor()
953      * @see #isShowing()
954      */
setOverlapAnchor(boolean overlapAnchor)955     public void setOverlapAnchor(boolean overlapAnchor) {
956         mOverlapAnchor = overlapAnchor;
957     }
958 
959     /**
960      * Returns whether the popup window should overlap its anchor view when
961      * displayed as a drop-down.
962      *
963      * @return Whether the popup should overlap its anchor.
964      *
965      * @see #setOverlapAnchor(boolean)
966      */
getOverlapAnchor()967     public boolean getOverlapAnchor() {
968         return mOverlapAnchor;
969     }
970 
971     /**
972      * <p>Indicate whether this popup window is showing on screen.</p>
973      *
974      * @return true if the popup is showing, false otherwise
975      */
isShowing()976     public boolean isShowing() {
977         return mIsShowing;
978     }
979 
980     /**
981      * <p>
982      * Display the content view in a popup window at the specified location. If the popup window
983      * cannot fit on screen, it will be clipped. See {@link android.view.WindowManager.LayoutParams}
984      * for more information on how gravity and the x and y parameters are related. Specifying
985      * a gravity of {@link android.view.Gravity#NO_GRAVITY} is similar to specifying
986      * <code>Gravity.LEFT | Gravity.TOP</code>.
987      * </p>
988      *
989      * @param parent a parent view to get the {@link android.view.View#getWindowToken()} token from
990      * @param gravity the gravity which controls the placement of the popup window
991      * @param x the popup's x location offset
992      * @param y the popup's y location offset
993      */
showAtLocation(View parent, int gravity, int x, int y)994     public void showAtLocation(View parent, int gravity, int x, int y) {
995         showAtLocation(parent.getWindowToken(), gravity, x, y);
996     }
997 
998     /**
999      * Display the content view in a popup window at the specified location.
1000      *
1001      * @param token Window token to use for creating the new window
1002      * @param gravity the gravity which controls the placement of the popup window
1003      * @param x the popup's x location offset
1004      * @param y the popup's y location offset
1005      *
1006      * @hide Internal use only. Applications should use
1007      *       {@link #showAtLocation(View, int, int, int)} instead.
1008      */
showAtLocation(IBinder token, int gravity, int x, int y)1009     public void showAtLocation(IBinder token, int gravity, int x, int y) {
1010         if (isShowing() || mContentView == null) {
1011             return;
1012         }
1013 
1014         TransitionManager.endTransitions(mDecorView);
1015 
1016         unregisterForScrollChanged();
1017 
1018         mIsShowing = true;
1019         mIsDropdown = false;
1020 
1021         final WindowManager.LayoutParams p = createPopupLayoutParams(token);
1022         preparePopup(p);
1023 
1024         // Only override the default if some gravity was specified.
1025         if (gravity != Gravity.NO_GRAVITY) {
1026             p.gravity = gravity;
1027         }
1028 
1029         p.x = x;
1030         p.y = y;
1031 
1032         invokePopup(p);
1033     }
1034 
1035     /**
1036      * Display the content view in a popup window anchored to the bottom-left
1037      * corner of the anchor view. If there is not enough room on screen to show
1038      * the popup in its entirety, this method tries to find a parent scroll
1039      * view to scroll. If no parent scroll view can be scrolled, the
1040      * bottom-left corner of the popup is pinned at the top left corner of the
1041      * anchor view.
1042      *
1043      * @param anchor the view on which to pin the popup window
1044      *
1045      * @see #dismiss()
1046      */
showAsDropDown(View anchor)1047     public void showAsDropDown(View anchor) {
1048         showAsDropDown(anchor, 0, 0);
1049     }
1050 
1051     /**
1052      * Display the content view in a popup window anchored to the bottom-left
1053      * corner of the anchor view offset by the specified x and y coordinates.
1054      * If there is not enough room on screen to show the popup in its entirety,
1055      * this method tries to find a parent scroll view to scroll. If no parent
1056      * scroll view can be scrolled, the bottom-left corner of the popup is
1057      * pinned at the top left corner of the anchor view.
1058      * <p>
1059      * If the view later scrolls to move <code>anchor</code> to a different
1060      * location, the popup will be moved correspondingly.
1061      *
1062      * @param anchor the view on which to pin the popup window
1063      * @param xoff A horizontal offset from the anchor in pixels
1064      * @param yoff A vertical offset from the anchor in pixels
1065      *
1066      * @see #dismiss()
1067      */
showAsDropDown(View anchor, int xoff, int yoff)1068     public void showAsDropDown(View anchor, int xoff, int yoff) {
1069         showAsDropDown(anchor, xoff, yoff, DEFAULT_ANCHORED_GRAVITY);
1070     }
1071 
1072     /**
1073      * Displays the content view in a popup window anchored to the corner of
1074      * another view. The window is positioned according to the specified
1075      * gravity and offset by the specified x and y coordinates.
1076      * <p>
1077      * If there is not enough room on screen to show the popup in its entirety,
1078      * this method tries to find a parent scroll view to scroll. If no parent
1079      * view can be scrolled, the specified vertical gravity will be ignored and
1080      * the popup will anchor itself such that it is visible.
1081      * <p>
1082      * If the view later scrolls to move <code>anchor</code> to a different
1083      * location, the popup will be moved correspondingly.
1084      *
1085      * @param anchor the view on which to pin the popup window
1086      * @param xoff A horizontal offset from the anchor in pixels
1087      * @param yoff A vertical offset from the anchor in pixels
1088      * @param gravity Alignment of the popup relative to the anchor
1089      *
1090      * @see #dismiss()
1091      */
showAsDropDown(View anchor, int xoff, int yoff, int gravity)1092     public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
1093         if (isShowing() || mContentView == null) {
1094             return;
1095         }
1096 
1097         TransitionManager.endTransitions(mDecorView);
1098 
1099         registerForScrollChanged(anchor, xoff, yoff, gravity);
1100 
1101         mIsShowing = true;
1102         mIsDropdown = true;
1103 
1104         final WindowManager.LayoutParams p = createPopupLayoutParams(anchor.getWindowToken());
1105         preparePopup(p);
1106 
1107         final boolean aboveAnchor = findDropDownPosition(anchor, p, xoff, yoff, gravity);
1108         updateAboveAnchor(aboveAnchor);
1109 
1110         invokePopup(p);
1111     }
1112 
updateAboveAnchor(boolean aboveAnchor)1113     private void updateAboveAnchor(boolean aboveAnchor) {
1114         if (aboveAnchor != mAboveAnchor) {
1115             mAboveAnchor = aboveAnchor;
1116 
1117             if (mBackground != null && mBackgroundView != null) {
1118                 // If the background drawable provided was a StateListDrawable
1119                 // with above-anchor and below-anchor states, use those.
1120                 // Otherwise, rely on refreshDrawableState to do the job.
1121                 if (mAboveAnchorBackgroundDrawable != null) {
1122                     if (mAboveAnchor) {
1123                         mBackgroundView.setBackground(mAboveAnchorBackgroundDrawable);
1124                     } else {
1125                         mBackgroundView.setBackground(mBelowAnchorBackgroundDrawable);
1126                     }
1127                 } else {
1128                     mBackgroundView.refreshDrawableState();
1129                 }
1130             }
1131         }
1132     }
1133 
1134     /**
1135      * Indicates whether the popup is showing above (the y coordinate of the popup's bottom
1136      * is less than the y coordinate of the anchor) or below the anchor view (the y coordinate
1137      * of the popup is greater than y coordinate of the anchor's bottom).
1138      *
1139      * The value returned
1140      * by this method is meaningful only after {@link #showAsDropDown(android.view.View)}
1141      * or {@link #showAsDropDown(android.view.View, int, int)} was invoked.
1142      *
1143      * @return True if this popup is showing above the anchor view, false otherwise.
1144      */
isAboveAnchor()1145     public boolean isAboveAnchor() {
1146         return mAboveAnchor;
1147     }
1148 
1149     /**
1150      * Prepare the popup by embedding it into a new ViewGroup if the background
1151      * drawable is not null. If embedding is required, the layout parameters'
1152      * height is modified to take into account the background's padding.
1153      *
1154      * @param p the layout parameters of the popup's content view
1155      */
preparePopup(WindowManager.LayoutParams p)1156     private void preparePopup(WindowManager.LayoutParams p) {
1157         if (mContentView == null || mContext == null || mWindowManager == null) {
1158             throw new IllegalStateException("You must specify a valid content view by "
1159                     + "calling setContentView() before attempting to show the popup.");
1160         }
1161 
1162         // The old decor view may be transitioning out. Make sure it finishes
1163         // and cleans up before we try to create another one.
1164         if (mDecorView != null) {
1165             mDecorView.cancelTransitions();
1166         }
1167 
1168         // When a background is available, we embed the content view within
1169         // another view that owns the background drawable.
1170         if (mBackground != null) {
1171             mBackgroundView = createBackgroundView(mContentView);
1172             mBackgroundView.setBackground(mBackground);
1173         } else {
1174             mBackgroundView = mContentView;
1175         }
1176 
1177         mDecorView = createDecorView(mBackgroundView);
1178 
1179         // The background owner should be elevated so that it casts a shadow.
1180         mBackgroundView.setElevation(mElevation);
1181 
1182         // We may wrap that in another view, so we'll need to manually specify
1183         // the surface insets.
1184         final int surfaceInset = (int) Math.ceil(mBackgroundView.getZ() * 2);
1185         p.surfaceInsets.set(surfaceInset, surfaceInset, surfaceInset, surfaceInset);
1186         p.hasManualSurfaceInsets = true;
1187 
1188         mPopupViewInitialLayoutDirectionInherited =
1189                 (mContentView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT);
1190         mPopupWidth = p.width;
1191         mPopupHeight = p.height;
1192     }
1193 
1194     /**
1195      * Wraps a content view in a PopupViewContainer.
1196      *
1197      * @param contentView the content view to wrap
1198      * @return a PopupViewContainer that wraps the content view
1199      */
createBackgroundView(View contentView)1200     private PopupBackgroundView createBackgroundView(View contentView) {
1201         final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();
1202         final int height;
1203         if (layoutParams != null && layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
1204             height = ViewGroup.LayoutParams.WRAP_CONTENT;
1205         } else {
1206             height = ViewGroup.LayoutParams.MATCH_PARENT;
1207         }
1208 
1209         final PopupBackgroundView backgroundView = new PopupBackgroundView(mContext);
1210         final PopupBackgroundView.LayoutParams listParams = new PopupBackgroundView.LayoutParams(
1211                 ViewGroup.LayoutParams.MATCH_PARENT, height);
1212         backgroundView.addView(contentView, listParams);
1213 
1214         return backgroundView;
1215     }
1216 
1217     /**
1218      * Wraps a content view in a FrameLayout.
1219      *
1220      * @param contentView the content view to wrap
1221      * @return a FrameLayout that wraps the content view
1222      */
createDecorView(View contentView)1223     private PopupDecorView createDecorView(View contentView) {
1224         final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();
1225         final int height;
1226         if (layoutParams != null && layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
1227             height = ViewGroup.LayoutParams.WRAP_CONTENT;
1228         } else {
1229             height = ViewGroup.LayoutParams.MATCH_PARENT;
1230         }
1231 
1232         final PopupDecorView decorView = new PopupDecorView(mContext);
1233         decorView.addView(contentView, ViewGroup.LayoutParams.MATCH_PARENT, height);
1234         decorView.setClipChildren(false);
1235         decorView.setClipToPadding(false);
1236 
1237         return decorView;
1238     }
1239 
1240     /**
1241      * <p>Invoke the popup window by adding the content view to the window
1242      * manager.</p>
1243      *
1244      * <p>The content view must be non-null when this method is invoked.</p>
1245      *
1246      * @param p the layout parameters of the popup's content view
1247      */
invokePopup(WindowManager.LayoutParams p)1248     private void invokePopup(WindowManager.LayoutParams p) {
1249         if (mContext != null) {
1250             p.packageName = mContext.getPackageName();
1251         }
1252 
1253         final PopupDecorView decorView = mDecorView;
1254         decorView.setFitsSystemWindows(mLayoutInsetDecor);
1255 
1256         setLayoutDirectionFromAnchor();
1257 
1258         mWindowManager.addView(decorView, p);
1259 
1260         if (mEnterTransition != null) {
1261             decorView.requestEnterTransition(mEnterTransition);
1262         }
1263     }
1264 
setLayoutDirectionFromAnchor()1265     private void setLayoutDirectionFromAnchor() {
1266         if (mAnchor != null) {
1267             View anchor = mAnchor.get();
1268             if (anchor != null && mPopupViewInitialLayoutDirectionInherited) {
1269                 mDecorView.setLayoutDirection(anchor.getLayoutDirection());
1270             }
1271         }
1272     }
1273 
1274     /**
1275      * <p>Generate the layout parameters for the popup window.</p>
1276      *
1277      * @param token the window token used to bind the popup's window
1278      *
1279      * @return the layout parameters to pass to the window manager
1280      */
createPopupLayoutParams(IBinder token)1281     private WindowManager.LayoutParams createPopupLayoutParams(IBinder token) {
1282         final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
1283 
1284         // These gravity settings put the view at the top left corner of the
1285         // screen. The view is then positioned to the appropriate location by
1286         // setting the x and y offsets to match the anchor's bottom-left
1287         // corner.
1288         p.gravity = Gravity.START | Gravity.TOP;
1289         p.flags = computeFlags(p.flags);
1290         p.type = mWindowLayoutType;
1291         p.token = token;
1292         p.softInputMode = mSoftInputMode;
1293         p.windowAnimations = computeAnimationResource();
1294 
1295         if (mBackground != null) {
1296             p.format = mBackground.getOpacity();
1297         } else {
1298             p.format = PixelFormat.TRANSLUCENT;
1299         }
1300 
1301         if (mHeightMode < 0) {
1302             p.height = mLastHeight = mHeightMode;
1303         } else {
1304             p.height = mLastHeight = mHeight;
1305         }
1306 
1307         if (mWidthMode < 0) {
1308             p.width = mLastWidth = mWidthMode;
1309         } else {
1310             p.width = mLastWidth = mWidth;
1311         }
1312 
1313         // Used for debugging.
1314         p.setTitle("PopupWindow:" + Integer.toHexString(hashCode()));
1315 
1316         return p;
1317     }
1318 
computeFlags(int curFlags)1319     private int computeFlags(int curFlags) {
1320         curFlags &= ~(
1321                 WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES |
1322                 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
1323                 WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE |
1324                 WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH |
1325                 WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS |
1326                 WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM |
1327                 WindowManager.LayoutParams.FLAG_SPLIT_TOUCH);
1328         if(mIgnoreCheekPress) {
1329             curFlags |= WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
1330         }
1331         if (!mFocusable) {
1332             curFlags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
1333             if (mInputMethodMode == INPUT_METHOD_NEEDED) {
1334                 curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
1335             }
1336         } else if (mInputMethodMode == INPUT_METHOD_NOT_NEEDED) {
1337             curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
1338         }
1339         if (!mTouchable) {
1340             curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
1341         }
1342         if (mOutsideTouchable) {
1343             curFlags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
1344         }
1345         if (!mClippingEnabled) {
1346             curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
1347         }
1348         if (isSplitTouchEnabled()) {
1349             curFlags |= WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
1350         }
1351         if (mLayoutInScreen) {
1352             curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
1353         }
1354         if (mLayoutInsetDecor) {
1355             curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
1356         }
1357         if (mNotTouchModal) {
1358             curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
1359         }
1360         if (mAttachedInDecor) {
1361           curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_ATTACHED_IN_DECOR;
1362         }
1363         return curFlags;
1364     }
1365 
computeAnimationResource()1366     private int computeAnimationResource() {
1367         if (mAnimationStyle == ANIMATION_STYLE_DEFAULT) {
1368             if (mIsDropdown) {
1369                 return mAboveAnchor
1370                         ? com.android.internal.R.style.Animation_DropDownUp
1371                         : com.android.internal.R.style.Animation_DropDownDown;
1372             }
1373             return 0;
1374         }
1375         return mAnimationStyle;
1376     }
1377 
1378     /**
1379      * Positions the popup window on screen. When the popup window is too tall
1380      * to fit under the anchor, a parent scroll view is seeked and scrolled up
1381      * to reclaim space. If scrolling is not possible or not enough, the popup
1382      * window gets moved on top of the anchor.
1383      * <p>
1384      * The height must have been set on the layout parameters prior to calling
1385      * this method.
1386      *
1387      * @param anchor the view on which the popup window must be anchored
1388      * @param p the layout parameters used to display the drop down
1389      * @param xoff horizontal offset used to adjust for background padding
1390      * @param yoff vertical offset used to adjust for background padding
1391      * @param gravity horizontal gravity specifying popup alignment
1392      * @return true if the popup is translated upwards to fit on screen
1393      */
findDropDownPosition(View anchor, WindowManager.LayoutParams p, int xoff, int yoff, int gravity)1394     private boolean findDropDownPosition(View anchor, WindowManager.LayoutParams p, int xoff,
1395             int yoff, int gravity) {
1396         final int anchorHeight = anchor.getHeight();
1397         final int anchorWidth = anchor.getWidth();
1398         if (mOverlapAnchor) {
1399             yoff -= anchorHeight;
1400         }
1401 
1402         anchor.getLocationInWindow(mDrawingLocation);
1403         p.x = mDrawingLocation[0] + xoff;
1404         p.y = mDrawingLocation[1] + anchorHeight + yoff;
1405 
1406         final int hgrav = Gravity.getAbsoluteGravity(gravity, anchor.getLayoutDirection())
1407                 & Gravity.HORIZONTAL_GRAVITY_MASK;
1408         if (hgrav == Gravity.RIGHT) {
1409             // Flip the location to align the right sides of the popup and
1410             // anchor instead of left.
1411             p.x -= mPopupWidth - anchorWidth;
1412         }
1413 
1414         boolean onTop = false;
1415 
1416         p.gravity = Gravity.LEFT | Gravity.TOP;
1417 
1418         anchor.getLocationOnScreen(mScreenLocation);
1419         final Rect displayFrame = new Rect();
1420         anchor.getWindowVisibleDisplayFrame(displayFrame);
1421 
1422         final int screenY = mScreenLocation[1] + anchorHeight + yoff;
1423         final View root = anchor.getRootView();
1424         if (screenY + mPopupHeight > displayFrame.bottom
1425                 || p.x + mPopupWidth - root.getWidth() > 0) {
1426             // If the drop down disappears at the bottom of the screen, we try
1427             // to scroll a parent scrollview or move the drop down back up on
1428             // top of the edit box.
1429             if (mAllowScrollingAnchorParent) {
1430                 final int scrollX = anchor.getScrollX();
1431                 final int scrollY = anchor.getScrollY();
1432                 final Rect r = new Rect(scrollX, scrollY, scrollX + mPopupWidth + xoff,
1433                         scrollY + mPopupHeight + anchorHeight + yoff);
1434                 anchor.requestRectangleOnScreen(r, true);
1435             }
1436 
1437             // Now we re-evaluate the space available, and decide from that
1438             // whether the pop-up will go above or below the anchor.
1439             anchor.getLocationInWindow(mDrawingLocation);
1440             p.x = mDrawingLocation[0] + xoff;
1441             p.y = mDrawingLocation[1] + anchorHeight + yoff;
1442 
1443             // Preserve the gravity adjustment.
1444             if (hgrav == Gravity.RIGHT) {
1445                 p.x -= mPopupWidth - anchorWidth;
1446             }
1447 
1448             // Determine whether there is more space above or below the anchor.
1449             anchor.getLocationOnScreen(mScreenLocation);
1450             onTop = (displayFrame.bottom - mScreenLocation[1] - anchorHeight - yoff) <
1451                     (mScreenLocation[1] - yoff - displayFrame.top);
1452             if (onTop) {
1453                 p.gravity = Gravity.LEFT | Gravity.BOTTOM;
1454                 p.y = root.getHeight() - mDrawingLocation[1] + yoff;
1455             } else {
1456                 p.y = mDrawingLocation[1] + anchorHeight + yoff;
1457             }
1458         }
1459 
1460         if (mClipToScreen) {
1461             final int displayFrameWidth = displayFrame.right - displayFrame.left;
1462             final int right = p.x + p.width;
1463             if (right > displayFrameWidth) {
1464                 p.x -= right - displayFrameWidth;
1465             }
1466 
1467             if (p.x < displayFrame.left) {
1468                 p.x = displayFrame.left;
1469                 p.width = Math.min(p.width, displayFrameWidth);
1470             }
1471 
1472             if (onTop) {
1473                 final int popupTop = mScreenLocation[1] + yoff - mPopupHeight;
1474                 if (popupTop < 0) {
1475                     p.y += popupTop;
1476                 }
1477             } else {
1478                 p.y = Math.max(p.y, displayFrame.top);
1479             }
1480         }
1481 
1482         p.gravity |= Gravity.DISPLAY_CLIP_VERTICAL;
1483 
1484         return onTop;
1485     }
1486 
1487     /**
1488      * Returns the maximum height that is available for the popup to be
1489      * completely shown. It is recommended that this height be the maximum for
1490      * the popup's height, otherwise it is possible that the popup will be
1491      * clipped.
1492      *
1493      * @param anchor The view on which the popup window must be anchored.
1494      * @return The maximum available height for the popup to be completely
1495      *         shown.
1496      */
getMaxAvailableHeight(View anchor)1497     public int getMaxAvailableHeight(View anchor) {
1498         return getMaxAvailableHeight(anchor, 0);
1499     }
1500 
1501     /**
1502      * Returns the maximum height that is available for the popup to be
1503      * completely shown. It is recommended that this height be the maximum for
1504      * the popup's height, otherwise it is possible that the popup will be
1505      * clipped.
1506      *
1507      * @param anchor The view on which the popup window must be anchored.
1508      * @param yOffset y offset from the view's bottom edge
1509      * @return The maximum available height for the popup to be completely
1510      *         shown.
1511      */
getMaxAvailableHeight(View anchor, int yOffset)1512     public int getMaxAvailableHeight(View anchor, int yOffset) {
1513         return getMaxAvailableHeight(anchor, yOffset, false);
1514     }
1515 
1516     /**
1517      * Returns the maximum height that is available for the popup to be
1518      * completely shown, optionally ignoring any bottom decorations such as
1519      * the input method. It is recommended that this height be the maximum for
1520      * the popup's height, otherwise it is possible that the popup will be
1521      * clipped.
1522      *
1523      * @param anchor The view on which the popup window must be anchored.
1524      * @param yOffset y offset from the view's bottom edge
1525      * @param ignoreBottomDecorations if true, the height returned will be
1526      *        all the way to the bottom of the display, ignoring any
1527      *        bottom decorations
1528      * @return The maximum available height for the popup to be completely
1529      *         shown.
1530      *
1531      * @hide Pending API council approval.
1532      */
getMaxAvailableHeight(View anchor, int yOffset, boolean ignoreBottomDecorations)1533     public int getMaxAvailableHeight(View anchor, int yOffset, boolean ignoreBottomDecorations) {
1534         final Rect displayFrame = new Rect();
1535         anchor.getWindowVisibleDisplayFrame(displayFrame);
1536 
1537         final int[] anchorPos = mDrawingLocation;
1538         anchor.getLocationOnScreen(anchorPos);
1539 
1540         int bottomEdge = displayFrame.bottom;
1541         if (ignoreBottomDecorations) {
1542             Resources res = anchor.getContext().getResources();
1543             bottomEdge = res.getDisplayMetrics().heightPixels;
1544         }
1545         final int distanceToBottom = bottomEdge - (anchorPos[1] + anchor.getHeight()) - yOffset;
1546         final int distanceToTop = anchorPos[1] - displayFrame.top + yOffset;
1547 
1548         // anchorPos[1] is distance from anchor to top of screen
1549         int returnedHeight = Math.max(distanceToBottom, distanceToTop);
1550         if (mBackground != null) {
1551             mBackground.getPadding(mTempRect);
1552             returnedHeight -= mTempRect.top + mTempRect.bottom;
1553         }
1554 
1555         return returnedHeight;
1556     }
1557 
1558     /**
1559      * Disposes of the popup window. This method can be invoked only after
1560      * {@link #showAsDropDown(android.view.View)} has been executed. Failing
1561      * that, calling this method will have no effect.
1562      *
1563      * @see #showAsDropDown(android.view.View)
1564      */
dismiss()1565     public void dismiss() {
1566         if (!isShowing() || mIsTransitioningToDismiss) {
1567             return;
1568         }
1569 
1570         final PopupDecorView decorView = mDecorView;
1571         final View contentView = mContentView;
1572 
1573         final ViewGroup contentHolder;
1574         final ViewParent contentParent = contentView.getParent();
1575         if (contentParent instanceof ViewGroup) {
1576             contentHolder = ((ViewGroup) contentParent);
1577         } else {
1578             contentHolder = null;
1579         }
1580 
1581         // Ensure any ongoing or pending transitions are canceled.
1582         decorView.cancelTransitions();
1583 
1584         mIsShowing = false;
1585         mIsTransitioningToDismiss = true;
1586 
1587         final Transition exitTransition = mExitTransition;
1588         if (exitTransition != null && decorView.isLaidOut()) {
1589             // The decor view is non-interactive during exit transitions.
1590             final LayoutParams p = (LayoutParams) decorView.getLayoutParams();
1591             p.flags |= LayoutParams.FLAG_NOT_TOUCHABLE;
1592             p.flags |= LayoutParams.FLAG_NOT_FOCUSABLE;
1593             mWindowManager.updateViewLayout(decorView, p);
1594 
1595             final Rect epicenter = getRelativeAnchorBounds();
1596             exitTransition.setEpicenterCallback(new EpicenterCallback() {
1597                 @Override
1598                 public Rect onGetEpicenter(Transition transition) {
1599                     return epicenter;
1600                 }
1601             });
1602             decorView.startExitTransition(exitTransition, new TransitionListenerAdapter() {
1603                 @Override
1604                 public void onTransitionEnd(Transition transition) {
1605                     dismissImmediate(decorView, contentHolder, contentView);
1606                 }
1607             });
1608         } else {
1609             dismissImmediate(decorView, contentHolder, contentView);
1610         }
1611 
1612         // Clears the anchor view.
1613         unregisterForScrollChanged();
1614 
1615         if (mOnDismissListener != null) {
1616             mOnDismissListener.onDismiss();
1617         }
1618     }
1619 
getRelativeAnchorBounds()1620     private Rect getRelativeAnchorBounds() {
1621         final View anchor = mAnchor != null ? mAnchor.get() : null;
1622         final View decor = mDecorView;
1623         if (anchor == null || decor == null) {
1624             return null;
1625         }
1626 
1627         final int[] anchorLocation = anchor.getLocationOnScreen();
1628         final int[] popupLocation = mDecorView.getLocationOnScreen();
1629 
1630         // Compute the position of the anchor relative to the popup.
1631         final Rect bounds = new Rect(0, 0, anchor.getWidth(), anchor.getHeight());
1632         bounds.offset(anchorLocation[0] - popupLocation[0], anchorLocation[1] - popupLocation[1]);
1633         return bounds;
1634     }
1635 
1636     /**
1637      * Removes the popup from the window manager and tears down the supporting
1638      * view hierarchy, if necessary.
1639      */
dismissImmediate(View decorView, ViewGroup contentHolder, View contentView)1640     private void dismissImmediate(View decorView, ViewGroup contentHolder, View contentView) {
1641         // If this method gets called and the decor view doesn't have a parent,
1642         // then it was either never added or was already removed. That should
1643         // never happen, but it's worth checking to avoid potential crashes.
1644         if (decorView.getParent() != null) {
1645             mWindowManager.removeViewImmediate(decorView);
1646         }
1647 
1648         if (contentHolder != null) {
1649             contentHolder.removeView(contentView);
1650         }
1651 
1652         // This needs to stay until after all transitions have ended since we
1653         // need the reference to cancel transitions in preparePopup().
1654         mDecorView = null;
1655         mBackgroundView = null;
1656         mIsTransitioningToDismiss = false;
1657     }
1658 
1659     /**
1660      * Sets the listener to be called when the window is dismissed.
1661      *
1662      * @param onDismissListener The listener.
1663      */
setOnDismissListener(OnDismissListener onDismissListener)1664     public void setOnDismissListener(OnDismissListener onDismissListener) {
1665         mOnDismissListener = onDismissListener;
1666     }
1667 
1668     /**
1669      * Updates the state of the popup window, if it is currently being displayed,
1670      * from the currently set state.
1671      * <p>
1672      * This includes:
1673      * <ul>
1674      *     <li>{@link #setClippingEnabled(boolean)}</li>
1675      *     <li>{@link #setFocusable(boolean)}</li>
1676      *     <li>{@link #setIgnoreCheekPress()}</li>
1677      *     <li>{@link #setInputMethodMode(int)}</li>
1678      *     <li>{@link #setTouchable(boolean)}</li>
1679      *     <li>{@link #setAnimationStyle(int)}</li>
1680      * </ul>
1681      */
update()1682     public void update() {
1683         if (!isShowing() || mContentView == null) {
1684             return;
1685         }
1686 
1687         final WindowManager.LayoutParams p =
1688                 (WindowManager.LayoutParams) mDecorView.getLayoutParams();
1689 
1690         boolean update = false;
1691 
1692         final int newAnim = computeAnimationResource();
1693         if (newAnim != p.windowAnimations) {
1694             p.windowAnimations = newAnim;
1695             update = true;
1696         }
1697 
1698         final int newFlags = computeFlags(p.flags);
1699         if (newFlags != p.flags) {
1700             p.flags = newFlags;
1701             update = true;
1702         }
1703 
1704         if (update) {
1705             setLayoutDirectionFromAnchor();
1706             mWindowManager.updateViewLayout(mDecorView, p);
1707         }
1708     }
1709 
1710     /**
1711      * Updates the dimension of the popup window.
1712      * <p>
1713      * Calling this function also updates the window with the current popup
1714      * state as described for {@link #update()}.
1715      *
1716      * @param width the new width, must be >= 0 or -1 to ignore
1717      * @param height the new height, must be >= 0 or -1 to ignore
1718      */
update(int width, int height)1719     public void update(int width, int height) {
1720         final WindowManager.LayoutParams p =
1721                 (WindowManager.LayoutParams) mDecorView.getLayoutParams();
1722         update(p.x, p.y, width, height, false);
1723     }
1724 
1725     /**
1726      * Updates the position and the dimension of the popup window.
1727      * <p>
1728      * Width and height can be set to -1 to update location only. Calling this
1729      * function also updates the window with the current popup state as
1730      * described for {@link #update()}.
1731      *
1732      * @param x the new x location
1733      * @param y the new y location
1734      * @param width the new width, must be >= 0 or -1 to ignore
1735      * @param height the new height, must be >= 0 or -1 to ignore
1736      */
update(int x, int y, int width, int height)1737     public void update(int x, int y, int width, int height) {
1738         update(x, y, width, height, false);
1739     }
1740 
1741     /**
1742      * Updates the position and the dimension of the popup window.
1743      * <p>
1744      * Width and height can be set to -1 to update location only. Calling this
1745      * function also updates the window with the current popup state as
1746      * described for {@link #update()}.
1747      *
1748      * @param x the new x location
1749      * @param y the new y location
1750      * @param width the new width, must be >= 0 or -1 to ignore
1751      * @param height the new height, must be >= 0 or -1 to ignore
1752      * @param force {@code true} to reposition the window even if the specified
1753      *              position already seems to correspond to the LayoutParams,
1754      *              {@code false} to only reposition if needed
1755      */
update(int x, int y, int width, int height, boolean force)1756     public void update(int x, int y, int width, int height, boolean force) {
1757         if (width >= 0) {
1758             mLastWidth = width;
1759             setWidth(width);
1760         }
1761 
1762         if (height >= 0) {
1763             mLastHeight = height;
1764             setHeight(height);
1765         }
1766 
1767         if (!isShowing() || mContentView == null) {
1768             return;
1769         }
1770 
1771         final WindowManager.LayoutParams p =
1772                 (WindowManager.LayoutParams) mDecorView.getLayoutParams();
1773 
1774         boolean update = force;
1775 
1776         final int finalWidth = mWidthMode < 0 ? mWidthMode : mLastWidth;
1777         if (width != -1 && p.width != finalWidth) {
1778             p.width = mLastWidth = finalWidth;
1779             update = true;
1780         }
1781 
1782         final int finalHeight = mHeightMode < 0 ? mHeightMode : mLastHeight;
1783         if (height != -1 && p.height != finalHeight) {
1784             p.height = mLastHeight = finalHeight;
1785             update = true;
1786         }
1787 
1788         if (p.x != x) {
1789             p.x = x;
1790             update = true;
1791         }
1792 
1793         if (p.y != y) {
1794             p.y = y;
1795             update = true;
1796         }
1797 
1798         final int newAnim = computeAnimationResource();
1799         if (newAnim != p.windowAnimations) {
1800             p.windowAnimations = newAnim;
1801             update = true;
1802         }
1803 
1804         final int newFlags = computeFlags(p.flags);
1805         if (newFlags != p.flags) {
1806             p.flags = newFlags;
1807             update = true;
1808         }
1809 
1810         if (update) {
1811             setLayoutDirectionFromAnchor();
1812             mWindowManager.updateViewLayout(mDecorView, p);
1813         }
1814     }
1815 
1816     /**
1817      * Updates the position and the dimension of the popup window.
1818      * <p>
1819      * Calling this function also updates the window with the current popup
1820      * state as described for {@link #update()}.
1821      *
1822      * @param anchor the popup's anchor view
1823      * @param width the new width, must be >= 0 or -1 to ignore
1824      * @param height the new height, must be >= 0 or -1 to ignore
1825      */
1826     public void update(View anchor, int width, int height) {
1827         update(anchor, false, 0, 0, true, width, height);
1828     }
1829 
1830     /**
1831      * Updates the position and the dimension of the popup window.
1832      * <p>
1833      * Width and height can be set to -1 to update location only. Calling this
1834      * function also updates the window with the current popup state as
1835      * described for {@link #update()}.
1836      * <p>
1837      * If the view later scrolls to move {@code anchor} to a different
1838      * location, the popup will be moved correspondingly.
1839      *
1840      * @param anchor the popup's anchor view
1841      * @param xoff x offset from the view's left edge
1842      * @param yoff y offset from the view's bottom edge
1843      * @param width the new width, must be >= 0 or -1 to ignore
1844      * @param height the new height, must be >= 0 or -1 to ignore
1845      */
1846     public void update(View anchor, int xoff, int yoff, int width, int height) {
1847         update(anchor, true, xoff, yoff, true, width, height);
1848     }
1849 
1850     private void update(View anchor, boolean updateLocation, int xoff, int yoff,
1851             boolean updateDimension, int width, int height) {
1852 
1853         if (!isShowing() || mContentView == null) {
1854             return;
1855         }
1856 
1857         final WeakReference<View> oldAnchor = mAnchor;
1858         final boolean needsUpdate = updateLocation && (mAnchorXoff != xoff || mAnchorYoff != yoff);
1859         if (oldAnchor == null || oldAnchor.get() != anchor || (needsUpdate && !mIsDropdown)) {
1860             registerForScrollChanged(anchor, xoff, yoff, mAnchoredGravity);
1861         } else if (needsUpdate) {
1862             // No need to register again if this is a DropDown, showAsDropDown already did.
1863             mAnchorXoff = xoff;
1864             mAnchorYoff = yoff;
1865         }
1866 
1867         if (updateDimension) {
1868             if (width == -1) {
1869                 width = mPopupWidth;
1870             } else {
1871                 mPopupWidth = width;
1872             }
1873             if (height == -1) {
1874                 height = mPopupHeight;
1875             } else {
1876                 mPopupHeight = height;
1877             }
1878         }
1879 
1880         final WindowManager.LayoutParams p =
1881                 (WindowManager.LayoutParams) mDecorView.getLayoutParams();
1882         final int x = p.x;
1883         final int y = p.y;
1884         if (updateLocation) {
1885             updateAboveAnchor(findDropDownPosition(anchor, p, xoff, yoff, mAnchoredGravity));
1886         } else {
1887             updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff,
1888                     mAnchoredGravity));
1889         }
1890 
1891         update(p.x, p.y, width, height, x != p.x || y != p.y);
1892     }
1893 
1894     /**
1895      * Listener that is called when this popup window is dismissed.
1896      */
1897     public interface OnDismissListener {
1898         /**
1899          * Called when this popup window is dismissed.
1900          */
1901         public void onDismiss();
1902     }
1903 
1904     private void unregisterForScrollChanged() {
1905         final WeakReference<View> anchorRef = mAnchor;
1906         final View anchor = anchorRef == null ? null : anchorRef.get();
1907         if (anchor != null) {
1908             final ViewTreeObserver vto = anchor.getViewTreeObserver();
1909             vto.removeOnScrollChangedListener(mOnScrollChangedListener);
1910         }
1911 
1912         mAnchor = null;
1913     }
1914 
1915     private void registerForScrollChanged(View anchor, int xoff, int yoff, int gravity) {
1916         unregisterForScrollChanged();
1917 
1918         mAnchor = new WeakReference<>(anchor);
1919 
1920         final ViewTreeObserver vto = anchor.getViewTreeObserver();
1921         if (vto != null) {
1922             vto.addOnScrollChangedListener(mOnScrollChangedListener);
1923         }
1924 
1925         mAnchorXoff = xoff;
1926         mAnchorYoff = yoff;
1927         mAnchoredGravity = gravity;
1928     }
1929 
1930     private class PopupDecorView extends FrameLayout {
1931         private TransitionListenerAdapter mPendingExitListener;
1932 
1933         public PopupDecorView(Context context) {
1934             super(context);
1935         }
1936 
1937         @Override
1938         public boolean dispatchKeyEvent(KeyEvent event) {
1939             if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
1940                 if (getKeyDispatcherState() == null) {
1941                     return super.dispatchKeyEvent(event);
1942                 }
1943 
1944                 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
1945                     final KeyEvent.DispatcherState state = getKeyDispatcherState();
1946                     if (state != null) {
1947                         state.startTracking(event, this);
1948                     }
1949                     return true;
1950                 } else if (event.getAction() == KeyEvent.ACTION_UP) {
1951                     final KeyEvent.DispatcherState state = getKeyDispatcherState();
1952                     if (state != null && state.isTracking(event) && !event.isCanceled()) {
1953                         dismiss();
1954                         return true;
1955                     }
1956                 }
1957                 return super.dispatchKeyEvent(event);
1958             } else {
1959                 return super.dispatchKeyEvent(event);
1960             }
1961         }
1962 
1963         @Override
1964         public boolean dispatchTouchEvent(MotionEvent ev) {
1965             if (mTouchInterceptor != null && mTouchInterceptor.onTouch(this, ev)) {
1966                 return true;
1967             }
1968             return super.dispatchTouchEvent(ev);
1969         }
1970 
1971         @Override
1972         public boolean onTouchEvent(MotionEvent event) {
1973             final int x = (int) event.getX();
1974             final int y = (int) event.getY();
1975 
1976             if ((event.getAction() == MotionEvent.ACTION_DOWN)
1977                     && ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) {
1978                 dismiss();
1979                 return true;
1980             } else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
1981                 dismiss();
1982                 return true;
1983             } else {
1984                 return super.onTouchEvent(event);
1985             }
1986         }
1987 
1988         /**
1989          * Requests that an enter transition run after the next layout pass.
1990          */
1991         public void requestEnterTransition(Transition transition) {
1992             final ViewTreeObserver observer = getViewTreeObserver();
1993             if (observer != null && transition != null) {
1994                 final Transition enterTransition = transition.clone();
1995 
1996                 // Postpone the enter transition after the first layout pass.
1997                 observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
1998                     @Override
1999                     public void onGlobalLayout() {
2000                         final ViewTreeObserver observer = getViewTreeObserver();
2001                         if (observer != null) {
2002                             observer.removeOnGlobalLayoutListener(this);
2003                         }
2004 
2005                         final Rect epicenter = getRelativeAnchorBounds();
2006                         enterTransition.setEpicenterCallback(new EpicenterCallback() {
2007                             @Override
2008                             public Rect onGetEpicenter(Transition transition) {
2009                                 return epicenter;
2010                             }
2011                         });
2012                         startEnterTransition(enterTransition);
2013                     }
2014                 });
2015             }
2016         }
2017 
2018         /**
2019          * Starts the pending enter transition, if one is set.
2020          */
2021         private void startEnterTransition(Transition enterTransition) {
2022             final int count = getChildCount();
2023             for (int i = 0; i < count; i++) {
2024                 final View child = getChildAt(i);
2025                 enterTransition.addTarget(child);
2026                 child.setVisibility(View.INVISIBLE);
2027             }
2028 
2029             TransitionManager.beginDelayedTransition(this, enterTransition);
2030 
2031             for (int i = 0; i < count; i++) {
2032                 final View child = getChildAt(i);
2033                 child.setVisibility(View.VISIBLE);
2034             }
2035         }
2036 
2037         /**
2038          * Starts an exit transition immediately.
2039          * <p>
2040          * <strong>Note:</strong> The transition listener is guaranteed to have
2041          * its {@code onTransitionEnd} method called even if the transition
2042          * never starts; however, it may be called with a {@code null} argument.
2043          */
2044         public void startExitTransition(Transition transition, final TransitionListener listener) {
2045             if (transition == null) {
2046                 return;
2047             }
2048 
2049             // The exit listener MUST be called for cleanup, even if the
2050             // transition never starts or ends. Stash it for later.
2051             mPendingExitListener = new TransitionListenerAdapter() {
2052                 @Override
2053                 public void onTransitionEnd(Transition transition) {
2054                     listener.onTransitionEnd(transition);
2055 
2056                     // The listener was called. Our job here is done.
2057                     mPendingExitListener = null;
2058                 }
2059             };
2060 
2061             final Transition exitTransition = transition.clone();
2062             exitTransition.addListener(mPendingExitListener);
2063 
2064             final int count = getChildCount();
2065             for (int i = 0; i < count; i++) {
2066                 final View child = getChildAt(i);
2067                 exitTransition.addTarget(child);
2068             }
2069 
2070             TransitionManager.beginDelayedTransition(this, exitTransition);
2071 
2072             for (int i = 0; i < count; i++) {
2073                 final View child = getChildAt(i);
2074                 child.setVisibility(View.INVISIBLE);
2075             }
2076         }
2077 
2078         /**
2079          * Cancels all pending or current transitions.
2080          */
2081         public void cancelTransitions() {
2082             TransitionManager.endTransitions(this);
2083 
2084             if (mPendingExitListener != null) {
2085                 mPendingExitListener.onTransitionEnd(null);
2086             }
2087         }
2088     }
2089 
2090     private class PopupBackgroundView extends FrameLayout {
2091         public PopupBackgroundView(Context context) {
2092             super(context);
2093         }
2094 
2095         @Override
2096         protected int[] onCreateDrawableState(int extraSpace) {
2097             if (mAboveAnchor) {
2098                 final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
2099                 View.mergeDrawableStates(drawableState, ABOVE_ANCHOR_STATE_SET);
2100                 return drawableState;
2101             } else {
2102                 return super.onCreateDrawableState(extraSpace);
2103             }
2104         }
2105     }
2106 }
2107