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