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