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
17package android.widget;
18
19import com.android.internal.R;
20
21import android.content.Context;
22import android.content.res.Resources;
23import android.content.res.TypedArray;
24import android.graphics.PixelFormat;
25import android.graphics.Rect;
26import android.graphics.drawable.Drawable;
27import android.graphics.drawable.StateListDrawable;
28import android.os.Build;
29import android.os.IBinder;
30import android.transition.Transition;
31import android.transition.Transition.EpicenterCallback;
32import android.transition.Transition.TransitionListener;
33import android.transition.Transition.TransitionListenerAdapter;
34import android.transition.TransitionInflater;
35import android.transition.TransitionManager;
36import android.transition.TransitionSet;
37import android.util.AttributeSet;
38import android.view.Gravity;
39import android.view.KeyEvent;
40import android.view.MotionEvent;
41import android.view.View;
42import android.view.View.OnTouchListener;
43import android.view.ViewGroup;
44import android.view.ViewParent;
45import android.view.ViewTreeObserver;
46import android.view.ViewTreeObserver.OnGlobalLayoutListener;
47import android.view.ViewTreeObserver.OnScrollChangedListener;
48import android.view.WindowManager;
49import android.view.WindowManager.LayoutParams;
50
51import 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 */
61public 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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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
336    public void setEnterTransition(Transition enterTransition) {
337        mEnterTransition = enterTransition;
338    }
339
340    public void setExitTransition(Transition exitTransition) {
341        mExitTransition = exitTransition;
342    }
343
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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
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     */
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     */
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     */
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     */
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     */
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
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     */
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
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
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     */
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     */
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     */
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     */
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     */
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
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     */
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     */
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     */
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     */
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     */
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     */
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