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