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