PopupWindow.java revision ce8c358712414e4fa98e4504e6a1b8ad36d37c6c
1/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.widget;
18
19import com.android.internal.R;
20
21import android.animation.ValueAnimator;
22import android.content.Context;
23import android.content.res.Resources;
24import android.content.res.TypedArray;
25import android.graphics.Insets;
26import android.graphics.PixelFormat;
27import android.graphics.Rect;
28import android.graphics.drawable.Drawable;
29import android.graphics.drawable.StateListDrawable;
30import android.os.Build;
31import android.os.IBinder;
32import android.util.AttributeSet;
33import android.view.Gravity;
34import android.view.KeyEvent;
35import android.view.MotionEvent;
36import android.view.View;
37import android.view.View.OnTouchListener;
38import android.view.ViewGroup;
39import android.view.ViewTreeObserver;
40import android.view.ViewTreeObserver.OnScrollChangedListener;
41import android.view.WindowManager;
42
43import java.lang.ref.WeakReference;
44
45/**
46 * <p>A popup window that can be used to display an arbitrary view. The popup
47 * window is a floating container that appears on top of the current
48 * activity.</p>
49 *
50 * @see android.widget.AutoCompleteTextView
51 * @see android.widget.Spinner
52 */
53public class PopupWindow {
54    /**
55     * Mode for {@link #setInputMethodMode(int)}: the requirements for the
56     * input method should be based on the focusability of the popup.  That is
57     * if it is focusable than it needs to work with the input method, else
58     * it doesn't.
59     */
60    public static final int INPUT_METHOD_FROM_FOCUSABLE = 0;
61
62    /**
63     * Mode for {@link #setInputMethodMode(int)}: this popup always needs to
64     * work with an input method, regardless of whether it is focusable.  This
65     * means that it will always be displayed so that the user can also operate
66     * the input method while it is shown.
67     */
68    public static final int INPUT_METHOD_NEEDED = 1;
69
70    /**
71     * Mode for {@link #setInputMethodMode(int)}: this popup never needs to
72     * work with an input method, regardless of whether it is focusable.  This
73     * means that it will always be displayed to use as much space on the
74     * screen as needed, regardless of whether this covers the input method.
75     */
76    public static final int INPUT_METHOD_NOT_NEEDED = 2;
77
78    private static final int DEFAULT_ANCHORED_GRAVITY = Gravity.TOP | Gravity.START;
79
80    private Context mContext;
81    private WindowManager mWindowManager;
82
83    private boolean mIsShowing;
84    private boolean mIsDropdown;
85
86    private View mContentView;
87    private View mPopupView;
88    private boolean mFocusable;
89    private int mInputMethodMode = INPUT_METHOD_FROM_FOCUSABLE;
90    private int mSoftInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED;
91    private boolean mTouchable = true;
92    private boolean mOutsideTouchable = false;
93    private boolean mClippingEnabled = true;
94    private int mSplitTouchEnabled = -1;
95    private boolean mLayoutInScreen;
96    private boolean mClipToScreen;
97    private boolean mAllowScrollingAnchorParent = true;
98    private boolean mLayoutInsetDecor = false;
99    private boolean mNotTouchModal;
100    private boolean mAttachedInDecor = true;
101    private boolean mAttachedInDecorSet = false;
102
103    private OnTouchListener mTouchInterceptor;
104
105    private int mWidthMode;
106    private int mWidth;
107    private int mLastWidth;
108    private int mHeightMode;
109    private int mHeight;
110    private int mLastHeight;
111
112    private int mPopupWidth;
113    private int mPopupHeight;
114
115    private float mElevation;
116
117    private int[] mDrawingLocation = new int[2];
118    private int[] mScreenLocation = new int[2];
119    private Rect mTempRect = new Rect();
120
121    private Drawable mBackground;
122    private Drawable mAboveAnchorBackgroundDrawable;
123    private Drawable mBelowAnchorBackgroundDrawable;
124
125    // Temporary animation centers. Should be moved into window params?
126    private int mAnchorRelativeX;
127    private int mAnchorRelativeY;
128
129    private boolean mAboveAnchor;
130    private int mWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
131
132    private OnDismissListener mOnDismissListener;
133    private boolean mIgnoreCheekPress = false;
134
135    private int mAnimationStyle = -1;
136
137    private static final int[] ABOVE_ANCHOR_STATE_SET = new int[] {
138        com.android.internal.R.attr.state_above_anchor
139    };
140
141    private WeakReference<View> mAnchor;
142
143    private final OnScrollChangedListener mOnScrollChangedListener =
144        new OnScrollChangedListener() {
145            @Override
146            public void onScrollChanged() {
147                final View anchor = mAnchor != null ? mAnchor.get() : null;
148                if (anchor != null && mPopupView != null) {
149                    final WindowManager.LayoutParams p = (WindowManager.LayoutParams)
150                            mPopupView.getLayoutParams();
151
152                    updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff,
153                            mAnchoredGravity));
154                    update(p.x, p.y, -1, -1, true);
155                }
156            }
157        };
158
159    private int mAnchorXoff, mAnchorYoff, mAnchoredGravity;
160    private boolean mOverlapAnchor;
161
162    private boolean mPopupViewInitialLayoutDirectionInherited;
163
164    /**
165     * <p>Create a new empty, non focusable popup window of dimension (0,0).</p>
166     *
167     * <p>The popup does provide a background.</p>
168     */
169    public PopupWindow(Context context) {
170        this(context, null);
171    }
172
173    /**
174     * <p>Create a new empty, non focusable popup window of dimension (0,0).</p>
175     *
176     * <p>The popup does provide a background.</p>
177     */
178    public PopupWindow(Context context, AttributeSet attrs) {
179        this(context, attrs, com.android.internal.R.attr.popupWindowStyle);
180    }
181
182    /**
183     * <p>Create a new empty, non focusable popup window of dimension (0,0).</p>
184     *
185     * <p>The popup does provide a background.</p>
186     */
187    public PopupWindow(Context context, AttributeSet attrs, int defStyleAttr) {
188        this(context, attrs, defStyleAttr, 0);
189    }
190
191    /**
192     * <p>Create a new, empty, non focusable popup window of dimension (0,0).</p>
193     *
194     * <p>The popup does not provide a background.</p>
195     */
196    public PopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
197        mContext = context;
198        mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
199
200        final TypedArray a = context.obtainStyledAttributes(
201                attrs, R.styleable.PopupWindow, defStyleAttr, defStyleRes);
202        final Drawable bg = a.getDrawable(R.styleable.PopupWindow_popupBackground);
203        mElevation = a.getDimension(R.styleable.PopupWindow_popupElevation, 0);
204        mOverlapAnchor = a.getBoolean(R.styleable.PopupWindow_overlapAnchor, false);
205
206        final int animStyle = a.getResourceId(R.styleable.PopupWindow_popupAnimationStyle, -1);
207        mAnimationStyle = animStyle == R.style.Animation_PopupWindow ? -1 : animStyle;
208
209        a.recycle();
210
211        setBackgroundDrawable(bg);
212    }
213
214    /**
215     * <p>Create a new empty, non focusable popup window of dimension (0,0).</p>
216     *
217     * <p>The popup does not provide any background. This should be handled
218     * by the content view.</p>
219     */
220    public PopupWindow() {
221        this(null, 0, 0);
222    }
223
224    /**
225     * <p>Create a new non focusable popup window which can display the
226     * <tt>contentView</tt>. The dimension of the window are (0,0).</p>
227     *
228     * <p>The popup does not provide any background. This should be handled
229     * by the content view.</p>
230     *
231     * @param contentView the popup's content
232     */
233    public PopupWindow(View contentView) {
234        this(contentView, 0, 0);
235    }
236
237    /**
238     * <p>Create a new empty, non focusable popup window. The dimension of the
239     * window must be passed to this constructor.</p>
240     *
241     * <p>The popup does not provide any background. This should be handled
242     * by the content view.</p>
243     *
244     * @param width the popup's width
245     * @param height the popup's height
246     */
247    public PopupWindow(int width, int height) {
248        this(null, width, height);
249    }
250
251    /**
252     * <p>Create a new non focusable popup window which can display the
253     * <tt>contentView</tt>. The dimension of the window must be passed to
254     * this constructor.</p>
255     *
256     * <p>The popup does not provide any background. This should be handled
257     * by the content view.</p>
258     *
259     * @param contentView the popup's content
260     * @param width the popup's width
261     * @param height the popup's height
262     */
263    public PopupWindow(View contentView, int width, int height) {
264        this(contentView, width, height, false);
265    }
266
267    /**
268     * <p>Create a new popup window which can display the <tt>contentView</tt>.
269     * The dimension of the window must be passed to this constructor.</p>
270     *
271     * <p>The popup does not provide any background. This should be handled
272     * by the content view.</p>
273     *
274     * @param contentView the popup's content
275     * @param width the popup's width
276     * @param height the popup's height
277     * @param focusable true if the popup can be focused, false otherwise
278     */
279    public PopupWindow(View contentView, int width, int height, boolean focusable) {
280        if (contentView != null) {
281            mContext = contentView.getContext();
282            mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
283        }
284
285        setContentView(contentView);
286        setWidth(width);
287        setHeight(height);
288        setFocusable(focusable);
289    }
290
291    /**
292     * Return the drawable used as the popup window's background.
293     *
294     * @return the background drawable or {@code null} if not set
295     * @see #setBackgroundDrawable(Drawable)
296     * @attr ref android.R.styleable#PopupWindow_popupBackground
297     */
298    public Drawable getBackground() {
299        return mBackground;
300    }
301
302    /**
303     * Specifies the background drawable for this popup window. The background
304     * can be set to {@code null}.
305     *
306     * @param background the popup's background
307     * @see #getBackground()
308     * @attr ref android.R.styleable#PopupWindow_popupBackground
309     */
310    public void setBackgroundDrawable(Drawable background) {
311        mBackground = background;
312
313        // If this is a StateListDrawable, try to find and store the drawable to be
314        // used when the drop-down is placed above its anchor view, and the one to be
315        // used when the drop-down is placed below its anchor view. We extract
316        // the drawables ourselves to work around a problem with using refreshDrawableState
317        // that it will take into account the padding of all drawables specified in a
318        // StateListDrawable, thus adding superfluous padding to drop-down views.
319        //
320        // We assume a StateListDrawable will have a drawable for ABOVE_ANCHOR_STATE_SET and
321        // at least one other drawable, intended for the 'below-anchor state'.
322        if (mBackground instanceof StateListDrawable) {
323            StateListDrawable stateList = (StateListDrawable) mBackground;
324
325            // Find the above-anchor view - this one's easy, it should be labeled as such.
326            int aboveAnchorStateIndex = stateList.getStateDrawableIndex(ABOVE_ANCHOR_STATE_SET);
327
328            // Now, for the below-anchor view, look for any other drawable specified in the
329            // StateListDrawable which is not for the above-anchor state and use that.
330            int count = stateList.getStateCount();
331            int belowAnchorStateIndex = -1;
332            for (int i = 0; i < count; i++) {
333                if (i != aboveAnchorStateIndex) {
334                    belowAnchorStateIndex = i;
335                    break;
336                }
337            }
338
339            // Store the drawables we found, if we found them. Otherwise, set them both
340            // to null so that we'll just use refreshDrawableState.
341            if (aboveAnchorStateIndex != -1 && belowAnchorStateIndex != -1) {
342                mAboveAnchorBackgroundDrawable = stateList.getStateDrawable(aboveAnchorStateIndex);
343                mBelowAnchorBackgroundDrawable = stateList.getStateDrawable(belowAnchorStateIndex);
344            } else {
345                mBelowAnchorBackgroundDrawable = null;
346                mAboveAnchorBackgroundDrawable = null;
347            }
348        }
349    }
350
351    /**
352     * @return the elevation for this popup window in pixels
353     * @see #setElevation(float)
354     * @attr ref android.R.styleable#PopupWindow_popupElevation
355     */
356    public float getElevation() {
357        return mElevation;
358    }
359
360    /**
361     * Specifies the elevation for this popup window.
362     *
363     * @param elevation the popup's elevation in pixels
364     * @see #getElevation()
365     * @attr ref android.R.styleable#PopupWindow_popupElevation
366     */
367    public void setElevation(float elevation) {
368        mElevation = elevation;
369    }
370
371    /**
372     * <p>Return the animation style to use the popup appears and disappears</p>
373     *
374     * @return the animation style to use the popup appears and disappears
375     */
376    public int getAnimationStyle() {
377        return mAnimationStyle;
378    }
379
380    /**
381     * Set the flag on popup to ignore cheek press event; by default this flag
382     * is set to false
383     * which means the pop wont ignore cheek press dispatch events.
384     *
385     * <p>If the popup is showing, calling this method will take effect only
386     * the next time the popup is shown or through a manual call to one of
387     * the {@link #update()} methods.</p>
388     *
389     * @see #update()
390     */
391    public void setIgnoreCheekPress() {
392        mIgnoreCheekPress = true;
393    }
394
395
396    /**
397     * <p>Change the animation style resource for this popup.</p>
398     *
399     * <p>If the popup is showing, calling this method will take effect only
400     * the next time the popup is shown or through a manual call to one of
401     * the {@link #update()} methods.</p>
402     *
403     * @param animationStyle animation style to use when the popup appears
404     *      and disappears.  Set to -1 for the default animation, 0 for no
405     *      animation, or a resource identifier for an explicit animation.
406     *
407     * @see #update()
408     */
409    public void setAnimationStyle(int animationStyle) {
410        mAnimationStyle = animationStyle;
411    }
412
413    /**
414     * <p>Return the view used as the content of the popup window.</p>
415     *
416     * @return a {@link android.view.View} representing the popup's content
417     *
418     * @see #setContentView(android.view.View)
419     */
420    public View getContentView() {
421        return mContentView;
422    }
423
424    /**
425     * <p>Change the popup's content. The content is represented by an instance
426     * of {@link android.view.View}.</p>
427     *
428     * <p>This method has no effect if called when the popup is showing.</p>
429     *
430     * @param contentView the new content for the popup
431     *
432     * @see #getContentView()
433     * @see #isShowing()
434     */
435    public void setContentView(View contentView) {
436        if (isShowing()) {
437            return;
438        }
439
440        mContentView = contentView;
441
442        if (mContext == null && mContentView != null) {
443            mContext = mContentView.getContext();
444        }
445
446        if (mWindowManager == null && mContentView != null) {
447            mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
448        }
449
450        // Setting the default for attachedInDecor based on SDK version here
451        // instead of in the constructor since we might not have the context
452        // object in the constructor. We only want to set default here if the
453        // app hasn't already set the attachedInDecor.
454        if (mContext != null && !mAttachedInDecorSet) {
455            // Attach popup window in decor frame of parent window by default for
456            // {@link Build.VERSION_CODES.LOLLIPOP_MR1} or greater. Keep current
457            // behavior of not attaching to decor frame for older SDKs.
458            setAttachedInDecor(mContext.getApplicationInfo().targetSdkVersion
459                    >= Build.VERSION_CODES.LOLLIPOP_MR1);
460        }
461
462    }
463
464    /**
465     * Set a callback for all touch events being dispatched to the popup
466     * window.
467     */
468    public void setTouchInterceptor(OnTouchListener l) {
469        mTouchInterceptor = l;
470    }
471
472    /**
473     * <p>Indicate whether the popup window can grab the focus.</p>
474     *
475     * @return true if the popup is focusable, false otherwise
476     *
477     * @see #setFocusable(boolean)
478     */
479    public boolean isFocusable() {
480        return mFocusable;
481    }
482
483    /**
484     * <p>Changes the focusability of the popup window. When focusable, the
485     * window will grab the focus from the current focused widget if the popup
486     * contains a focusable {@link android.view.View}.  By default a popup
487     * window is not focusable.</p>
488     *
489     * <p>If the popup is showing, calling this method will take effect only
490     * the next time the popup is shown or through a manual call to one of
491     * the {@link #update()} methods.</p>
492     *
493     * @param focusable true if the popup should grab focus, false otherwise.
494     *
495     * @see #isFocusable()
496     * @see #isShowing()
497     * @see #update()
498     */
499    public void setFocusable(boolean focusable) {
500        mFocusable = focusable;
501    }
502
503    /**
504     * Return the current value in {@link #setInputMethodMode(int)}.
505     *
506     * @see #setInputMethodMode(int)
507     */
508    public int getInputMethodMode() {
509        return mInputMethodMode;
510
511    }
512
513    /**
514     * Control how the popup operates with an input method: one of
515     * {@link #INPUT_METHOD_FROM_FOCUSABLE}, {@link #INPUT_METHOD_NEEDED},
516     * or {@link #INPUT_METHOD_NOT_NEEDED}.
517     *
518     * <p>If the popup is showing, calling this method will take effect only
519     * the next time the popup is shown or through a manual call to one of
520     * the {@link #update()} methods.</p>
521     *
522     * @see #getInputMethodMode()
523     * @see #update()
524     */
525    public void setInputMethodMode(int mode) {
526        mInputMethodMode = mode;
527    }
528
529    /**
530     * Sets the operating mode for the soft input area.
531     *
532     * @param mode The desired mode, see
533     *        {@link android.view.WindowManager.LayoutParams#softInputMode}
534     *        for the full list
535     *
536     * @see android.view.WindowManager.LayoutParams#softInputMode
537     * @see #getSoftInputMode()
538     */
539    public void setSoftInputMode(int mode) {
540        mSoftInputMode = mode;
541    }
542
543    /**
544     * Returns the current value in {@link #setSoftInputMode(int)}.
545     *
546     * @see #setSoftInputMode(int)
547     * @see android.view.WindowManager.LayoutParams#softInputMode
548     */
549    public int getSoftInputMode() {
550        return mSoftInputMode;
551    }
552
553    /**
554     * <p>Indicates whether the popup window receives touch events.</p>
555     *
556     * @return true if the popup is touchable, false otherwise
557     *
558     * @see #setTouchable(boolean)
559     */
560    public boolean isTouchable() {
561        return mTouchable;
562    }
563
564    /**
565     * <p>Changes the touchability of the popup window. When touchable, the
566     * window will receive touch events, otherwise touch events will go to the
567     * window below it. By default the window is touchable.</p>
568     *
569     * <p>If the popup is showing, calling this method will take effect only
570     * the next time the popup is shown or through a manual call to one of
571     * the {@link #update()} methods.</p>
572     *
573     * @param touchable true if the popup should receive touch events, false otherwise
574     *
575     * @see #isTouchable()
576     * @see #isShowing()
577     * @see #update()
578     */
579    public void setTouchable(boolean touchable) {
580        mTouchable = touchable;
581    }
582
583    /**
584     * <p>Indicates whether the popup window will be informed of touch events
585     * outside of its window.</p>
586     *
587     * @return true if the popup is outside touchable, false otherwise
588     *
589     * @see #setOutsideTouchable(boolean)
590     */
591    public boolean isOutsideTouchable() {
592        return mOutsideTouchable;
593    }
594
595    /**
596     * <p>Controls whether the pop-up will be informed of touch events outside
597     * of its window.  This only makes sense for pop-ups that are touchable
598     * but not focusable, which means touches outside of the window will
599     * be delivered to the window behind.  The default is false.</p>
600     *
601     * <p>If the popup is showing, calling this method will take effect only
602     * the next time the popup is shown or through a manual call to one of
603     * the {@link #update()} methods.</p>
604     *
605     * @param touchable true if the popup should receive outside
606     * touch events, false otherwise
607     *
608     * @see #isOutsideTouchable()
609     * @see #isShowing()
610     * @see #update()
611     */
612    public void setOutsideTouchable(boolean touchable) {
613        mOutsideTouchable = touchable;
614    }
615
616    /**
617     * <p>Indicates whether clipping of the popup window is enabled.</p>
618     *
619     * @return true if the clipping is enabled, false otherwise
620     *
621     * @see #setClippingEnabled(boolean)
622     */
623    public boolean isClippingEnabled() {
624        return mClippingEnabled;
625    }
626
627    /**
628     * <p>Allows the popup window to extend beyond the bounds of the screen. By default the
629     * window is clipped to the screen boundaries. Setting this to false will allow windows to be
630     * accurately positioned.</p>
631     *
632     * <p>If the popup is showing, calling this method will take effect only
633     * the next time the popup is shown or through a manual call to one of
634     * the {@link #update()} methods.</p>
635     *
636     * @param enabled false if the window should be allowed to extend outside of the screen
637     * @see #isShowing()
638     * @see #isClippingEnabled()
639     * @see #update()
640     */
641    public void setClippingEnabled(boolean enabled) {
642        mClippingEnabled = enabled;
643    }
644
645    /**
646     * Clip this popup window to the screen, but not to the containing window.
647     *
648     * @param enabled True to clip to the screen.
649     * @hide
650     */
651    public void setClipToScreenEnabled(boolean enabled) {
652        mClipToScreen = enabled;
653        setClippingEnabled(!enabled);
654    }
655
656    /**
657     * Allow PopupWindow to scroll the anchor's parent to provide more room
658     * for the popup. Enabled by default.
659     *
660     * @param enabled True to scroll the anchor's parent when more room is desired by the popup.
661     */
662    void setAllowScrollingAnchorParent(boolean enabled) {
663        mAllowScrollingAnchorParent = enabled;
664    }
665
666    /**
667     * <p>Indicates whether the popup window supports splitting touches.</p>
668     *
669     * @return true if the touch splitting is enabled, false otherwise
670     *
671     * @see #setSplitTouchEnabled(boolean)
672     */
673    public boolean isSplitTouchEnabled() {
674        if (mSplitTouchEnabled < 0 && mContext != null) {
675            return mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB;
676        }
677        return mSplitTouchEnabled == 1;
678    }
679
680    /**
681     * <p>Allows the popup window to split touches across other windows that also
682     * support split touch.  When this flag is false, the first pointer
683     * that goes down determines the window to which all subsequent touches
684     * go until all pointers go up.  When this flag is true, each pointer
685     * (not necessarily the first) that goes down determines the window
686     * to which all subsequent touches of that pointer will go until that
687     * pointer goes up thereby enabling touches with multiple pointers
688     * to be split across multiple windows.</p>
689     *
690     * @param enabled true if the split touches should be enabled, false otherwise
691     * @see #isSplitTouchEnabled()
692     */
693    public void setSplitTouchEnabled(boolean enabled) {
694        mSplitTouchEnabled = enabled ? 1 : 0;
695    }
696
697    /**
698     * <p>Indicates whether the popup window will be forced into using absolute screen coordinates
699     * for positioning.</p>
700     *
701     * @return true if the window will always be positioned in screen coordinates.
702     * @hide
703     */
704    public boolean isLayoutInScreenEnabled() {
705        return mLayoutInScreen;
706    }
707
708    /**
709     * <p>Allows the popup window to force the flag
710     * {@link WindowManager.LayoutParams#FLAG_LAYOUT_IN_SCREEN}, overriding default behavior.
711     * This will cause the popup to be positioned in absolute screen coordinates.</p>
712     *
713     * @param enabled true if the popup should always be positioned in screen coordinates
714     * @hide
715     */
716    public void setLayoutInScreenEnabled(boolean enabled) {
717        mLayoutInScreen = enabled;
718    }
719
720    /**
721     * <p>Indicates whether the popup window will be attached in the decor frame of its parent
722     * window.
723     *
724     * @return true if the window will be attached to the decor frame of its parent window.
725     *
726     * @see #setAttachedInDecor(boolean)
727     * @see WindowManager.LayoutParams#FLAG_LAYOUT_ATTACHED_IN_DECOR
728     */
729    public boolean isAttachedInDecor() {
730        return mAttachedInDecor;
731    }
732
733    /**
734     * <p>This will attach the popup window to the decor frame of the parent window to avoid
735     * overlaping with screen decorations like the navigation bar. Overrides the default behavior of
736     * the flag {@link WindowManager.LayoutParams#FLAG_LAYOUT_ATTACHED_IN_DECOR}.
737     *
738     * <p>By default the flag is set on SDK version {@link Build.VERSION_CODES#LOLLIPOP_MR1} or
739     * greater and cleared on lesser SDK versions.
740     *
741     * @param enabled true if the popup should be attached to the decor frame of its parent window.
742     *
743     * @see WindowManager.LayoutParams#FLAG_LAYOUT_ATTACHED_IN_DECOR
744     */
745    public void setAttachedInDecor(boolean enabled) {
746        mAttachedInDecor = enabled;
747        mAttachedInDecorSet = true;
748    }
749
750    /**
751     * Allows the popup window to force the flag
752     * {@link WindowManager.LayoutParams#FLAG_LAYOUT_INSET_DECOR}, overriding default behavior.
753     * This will cause the popup to inset its content to account for system windows overlaying
754     * the screen, such as the status bar.
755     *
756     * <p>This will often be combined with {@link #setLayoutInScreenEnabled(boolean)}.
757     *
758     * @param enabled true if the popup's views should inset content to account for system windows,
759     *                the way that decor views behave for full-screen windows.
760     * @hide
761     */
762    public void setLayoutInsetDecor(boolean enabled) {
763        mLayoutInsetDecor = enabled;
764    }
765
766    /**
767     * Set the layout type for this window. Should be one of the TYPE constants defined in
768     * {@link WindowManager.LayoutParams}.
769     *
770     * @param layoutType Layout type for this window.
771     * @hide
772     */
773    public void setWindowLayoutType(int layoutType) {
774        mWindowLayoutType = layoutType;
775    }
776
777    /**
778     * @return The layout type for this window.
779     * @hide
780     */
781    public int getWindowLayoutType() {
782        return mWindowLayoutType;
783    }
784
785    /**
786     * Set whether this window is touch modal or if outside touches will be sent to
787     * other windows behind it.
788     * @hide
789     */
790    public void setTouchModal(boolean touchModal) {
791        mNotTouchModal = !touchModal;
792    }
793
794    /**
795     * <p>Change the width and height measure specs that are given to the
796     * window manager by the popup.  By default these are 0, meaning that
797     * the current width or height is requested as an explicit size from
798     * the window manager.  You can supply
799     * {@link ViewGroup.LayoutParams#WRAP_CONTENT} or
800     * {@link ViewGroup.LayoutParams#MATCH_PARENT} to have that measure
801     * spec supplied instead, replacing the absolute width and height that
802     * has been set in the popup.</p>
803     *
804     * <p>If the popup is showing, calling this method will take effect only
805     * the next time the popup is shown.</p>
806     *
807     * @param widthSpec an explicit width measure spec mode, either
808     * {@link ViewGroup.LayoutParams#WRAP_CONTENT},
809     * {@link ViewGroup.LayoutParams#MATCH_PARENT}, or 0 to use the absolute
810     * width.
811     * @param heightSpec an explicit height measure spec mode, either
812     * {@link ViewGroup.LayoutParams#WRAP_CONTENT},
813     * {@link ViewGroup.LayoutParams#MATCH_PARENT}, or 0 to use the absolute
814     * height.
815     */
816    public void setWindowLayoutMode(int widthSpec, int heightSpec) {
817        mWidthMode = widthSpec;
818        mHeightMode = heightSpec;
819    }
820
821    /**
822     * <p>Return this popup's height MeasureSpec</p>
823     *
824     * @return the height MeasureSpec of the popup
825     *
826     * @see #setHeight(int)
827     */
828    public int getHeight() {
829        return mHeight;
830    }
831
832    /**
833     * <p>Change the popup's height MeasureSpec</p>
834     *
835     * <p>If the popup is showing, calling this method will take effect only
836     * the next time the popup is shown.</p>
837     *
838     * @param height the height MeasureSpec of the popup
839     *
840     * @see #getHeight()
841     * @see #isShowing()
842     */
843    public void setHeight(int height) {
844        mHeight = height;
845    }
846
847    /**
848     * <p>Return this popup's width MeasureSpec</p>
849     *
850     * @return the width MeasureSpec of the popup
851     *
852     * @see #setWidth(int)
853     */
854    public int getWidth() {
855        return mWidth;
856    }
857
858    /**
859     * <p>Change the popup's width MeasureSpec</p>
860     *
861     * <p>If the popup is showing, calling this method will take effect only
862     * the next time the popup is shown.</p>
863     *
864     * @param width the width MeasureSpec of the popup
865     *
866     * @see #getWidth()
867     * @see #isShowing()
868     */
869    public void setWidth(int width) {
870        mWidth = width;
871    }
872
873    /**
874     * <p>Indicate whether this popup window is showing on screen.</p>
875     *
876     * @return true if the popup is showing, false otherwise
877     */
878    public boolean isShowing() {
879        return mIsShowing;
880    }
881
882    /**
883     * <p>
884     * Display the content view in a popup window at the specified location. If the popup window
885     * cannot fit on screen, it will be clipped. See {@link android.view.WindowManager.LayoutParams}
886     * for more information on how gravity and the x and y parameters are related. Specifying
887     * a gravity of {@link android.view.Gravity#NO_GRAVITY} is similar to specifying
888     * <code>Gravity.LEFT | Gravity.TOP</code>.
889     * </p>
890     *
891     * @param parent a parent view to get the {@link android.view.View#getWindowToken()} token from
892     * @param gravity the gravity which controls the placement of the popup window
893     * @param x the popup's x location offset
894     * @param y the popup's y location offset
895     */
896    public void showAtLocation(View parent, int gravity, int x, int y) {
897        showAtLocation(parent.getWindowToken(), gravity, x, y);
898    }
899
900    /**
901     * Display the content view in a popup window at the specified location.
902     *
903     * @param token Window token to use for creating the new window
904     * @param gravity the gravity which controls the placement of the popup window
905     * @param x the popup's x location offset
906     * @param y the popup's y location offset
907     *
908     * @hide Internal use only. Applications should use
909     *       {@link #showAtLocation(View, int, int, int)} instead.
910     */
911    public void showAtLocation(IBinder token, int gravity, int x, int y) {
912        if (isShowing() || mContentView == null) {
913            return;
914        }
915
916        unregisterForScrollChanged();
917
918        mIsShowing = true;
919        mIsDropdown = false;
920
921        WindowManager.LayoutParams p = createPopupLayout(token);
922        p.windowAnimations = computeAnimationResource();
923
924        preparePopup(p);
925        if (gravity == Gravity.NO_GRAVITY) {
926            gravity = Gravity.TOP | Gravity.START;
927        }
928        p.gravity = gravity;
929        p.x = x;
930        p.y = y;
931        if (mHeightMode < 0) p.height = mLastHeight = mHeightMode;
932        if (mWidthMode < 0) p.width = mLastWidth = mWidthMode;
933        invokePopup(p);
934    }
935
936    /**
937     * <p>Display the content view in a popup window anchored to the bottom-left
938     * corner of the anchor view. If there is not enough room on screen to show
939     * the popup in its entirety, this method tries to find a parent scroll
940     * view to scroll. If no parent scroll view can be scrolled, the bottom-left
941     * corner of the popup is pinned at the top left corner of the anchor view.</p>
942     *
943     * @param anchor the view on which to pin the popup window
944     *
945     * @see #dismiss()
946     */
947    public void showAsDropDown(View anchor) {
948        showAsDropDown(anchor, 0, 0);
949    }
950
951    /**
952     * <p>Display the content view in a popup window anchored to the bottom-left
953     * corner of the anchor view offset by the specified x and y coordinates.
954     * If there is not enough room on screen to show
955     * the popup in its entirety, this method tries to find a parent scroll
956     * view to scroll. If no parent scroll view can be scrolled, the bottom-left
957     * corner of the popup is pinned at the top left corner of the anchor view.</p>
958     * <p>If the view later scrolls to move <code>anchor</code> to a different
959     * location, the popup will be moved correspondingly.</p>
960     *
961     * @param anchor the view on which to pin the popup window
962     * @param xoff A horizontal offset from the anchor in pixels
963     * @param yoff A vertical offset from the anchor in pixels
964     *
965     * @see #dismiss()
966     */
967    public void showAsDropDown(View anchor, int xoff, int yoff) {
968        showAsDropDown(anchor, xoff, yoff, DEFAULT_ANCHORED_GRAVITY);
969    }
970
971    /**
972     * <p>Display the content view in a popup window anchored to the bottom-left
973     * corner of the anchor view offset by the specified x and y coordinates.
974     * If there is not enough room on screen to show
975     * the popup in its entirety, this method tries to find a parent scroll
976     * view to scroll. If no parent scroll view can be scrolled, the bottom-left
977     * corner of the popup is pinned at the top left corner of the anchor view.</p>
978     * <p>If the view later scrolls to move <code>anchor</code> to a different
979     * location, the popup will be moved correspondingly.</p>
980     *
981     * @param anchor the view on which to pin the popup window
982     * @param xoff A horizontal offset from the anchor in pixels
983     * @param yoff A vertical offset from the anchor in pixels
984     * @param gravity Alignment of the popup relative to the anchor
985     *
986     * @see #dismiss()
987     */
988    public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
989        if (isShowing() || mContentView == null) {
990            return;
991        }
992
993        registerForScrollChanged(anchor, xoff, yoff, gravity);
994
995        mIsShowing = true;
996        mIsDropdown = true;
997
998        WindowManager.LayoutParams p = createPopupLayout(anchor.getWindowToken());
999        preparePopup(p);
1000
1001        updateAboveAnchor(findDropDownPosition(anchor, p, xoff, yoff, gravity));
1002
1003        if (mHeightMode < 0) p.height = mLastHeight = mHeightMode;
1004        if (mWidthMode < 0) p.width = mLastWidth = mWidthMode;
1005
1006        p.windowAnimations = computeAnimationResource();
1007
1008        invokePopup(p);
1009    }
1010
1011    private void updateAboveAnchor(boolean aboveAnchor) {
1012        if (aboveAnchor != mAboveAnchor) {
1013            mAboveAnchor = aboveAnchor;
1014
1015            if (mBackground != null) {
1016                // If the background drawable provided was a StateListDrawable with above-anchor
1017                // and below-anchor states, use those. Otherwise rely on refreshDrawableState to
1018                // do the job.
1019                if (mAboveAnchorBackgroundDrawable != null) {
1020                    if (mAboveAnchor) {
1021                        mPopupView.setBackground(mAboveAnchorBackgroundDrawable);
1022                    } else {
1023                        mPopupView.setBackground(mBelowAnchorBackgroundDrawable);
1024                    }
1025                } else {
1026                    mPopupView.refreshDrawableState();
1027                }
1028            }
1029        }
1030    }
1031
1032    /**
1033     * Indicates whether the popup is showing above (the y coordinate of the popup's bottom
1034     * is less than the y coordinate of the anchor) or below the anchor view (the y coordinate
1035     * of the popup is greater than y coordinate of the anchor's bottom).
1036     *
1037     * The value returned
1038     * by this method is meaningful only after {@link #showAsDropDown(android.view.View)}
1039     * or {@link #showAsDropDown(android.view.View, int, int)} was invoked.
1040     *
1041     * @return True if this popup is showing above the anchor view, false otherwise.
1042     */
1043    public boolean isAboveAnchor() {
1044        return mAboveAnchor;
1045    }
1046
1047    /**
1048     * <p>Prepare the popup by embedding in into a new ViewGroup if the
1049     * background drawable is not null. If embedding is required, the layout
1050     * parameters' height is modified to take into account the background's
1051     * padding.</p>
1052     *
1053     * @param p the layout parameters of the popup's content view
1054     */
1055    private void preparePopup(WindowManager.LayoutParams p) {
1056        if (mContentView == null || mContext == null || mWindowManager == null) {
1057            throw new IllegalStateException("You must specify a valid content view by "
1058                    + "calling setContentView() before attempting to show the popup.");
1059        }
1060
1061        if (mBackground != null) {
1062            final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();
1063            int height = ViewGroup.LayoutParams.MATCH_PARENT;
1064            if (layoutParams != null &&
1065                    layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
1066                height = ViewGroup.LayoutParams.WRAP_CONTENT;
1067            }
1068
1069            // when a background is available, we embed the content view
1070            // within another view that owns the background drawable
1071            PopupViewContainer popupViewContainer = new PopupViewContainer(mContext);
1072            PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams(
1073                    ViewGroup.LayoutParams.MATCH_PARENT, height
1074            );
1075            popupViewContainer.setBackground(mBackground);
1076            popupViewContainer.addView(mContentView, listParams);
1077
1078            mPopupView = popupViewContainer;
1079        } else {
1080            mPopupView = mContentView;
1081        }
1082
1083        mPopupView.setElevation(mElevation);
1084        mPopupViewInitialLayoutDirectionInherited =
1085                (mPopupView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT);
1086        mPopupWidth = p.width;
1087        mPopupHeight = p.height;
1088    }
1089
1090    /**
1091     * <p>Invoke the popup window by adding the content view to the window
1092     * manager.</p>
1093     *
1094     * <p>The content view must be non-null when this method is invoked.</p>
1095     *
1096     * @param p the layout parameters of the popup's content view
1097     */
1098    private void invokePopup(WindowManager.LayoutParams p) {
1099        if (mContext != null) {
1100            p.packageName = mContext.getPackageName();
1101        }
1102        mPopupView.setFitsSystemWindows(mLayoutInsetDecor);
1103        setLayoutDirectionFromAnchor();
1104        mWindowManager.addView(mPopupView, p);
1105    }
1106
1107    private void setLayoutDirectionFromAnchor() {
1108        if (mAnchor != null) {
1109            View anchor = mAnchor.get();
1110            if (anchor != null && mPopupViewInitialLayoutDirectionInherited) {
1111                mPopupView.setLayoutDirection(anchor.getLayoutDirection());
1112            }
1113        }
1114    }
1115
1116    /**
1117     * <p>Generate the layout parameters for the popup window.</p>
1118     *
1119     * @param token the window token used to bind the popup's window
1120     *
1121     * @return the layout parameters to pass to the window manager
1122     */
1123    private WindowManager.LayoutParams createPopupLayout(IBinder token) {
1124        // generates the layout parameters for the drop down
1125        // we want a fixed size view located at the bottom left of the anchor
1126        WindowManager.LayoutParams p = new WindowManager.LayoutParams();
1127        // these gravity settings put the view at the top left corner of the
1128        // screen. The view is then positioned to the appropriate location
1129        // by setting the x and y offsets to match the anchor's bottom
1130        // left corner
1131        p.gravity = Gravity.START | Gravity.TOP;
1132        p.width = mLastWidth = mWidth;
1133        p.height = mLastHeight = mHeight;
1134        if (mBackground != null) {
1135            p.format = mBackground.getOpacity();
1136        } else {
1137            p.format = PixelFormat.TRANSLUCENT;
1138        }
1139        p.flags = computeFlags(p.flags);
1140        p.type = mWindowLayoutType;
1141        p.token = token;
1142        p.softInputMode = mSoftInputMode;
1143        p.setTitle("PopupWindow:" + Integer.toHexString(hashCode()));
1144
1145        return p;
1146    }
1147
1148    private int computeFlags(int curFlags) {
1149        curFlags &= ~(
1150                WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES |
1151                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
1152                WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE |
1153                WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH |
1154                WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS |
1155                WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM |
1156                WindowManager.LayoutParams.FLAG_SPLIT_TOUCH);
1157        if(mIgnoreCheekPress) {
1158            curFlags |= WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
1159        }
1160        if (!mFocusable) {
1161            curFlags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
1162            if (mInputMethodMode == INPUT_METHOD_NEEDED) {
1163                curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
1164            }
1165        } else if (mInputMethodMode == INPUT_METHOD_NOT_NEEDED) {
1166            curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
1167        }
1168        if (!mTouchable) {
1169            curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
1170        }
1171        if (mOutsideTouchable) {
1172            curFlags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
1173        }
1174        if (!mClippingEnabled) {
1175            curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
1176        }
1177        if (isSplitTouchEnabled()) {
1178            curFlags |= WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
1179        }
1180        if (mLayoutInScreen) {
1181            curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
1182        }
1183        if (mLayoutInsetDecor) {
1184            curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
1185        }
1186        if (mNotTouchModal) {
1187            curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
1188        }
1189        if (mAttachedInDecor) {
1190          curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_ATTACHED_IN_DECOR;
1191        }
1192        return curFlags;
1193    }
1194
1195    private int computeAnimationResource() {
1196        if (mAnimationStyle == -1) {
1197            if (mIsDropdown) {
1198                return mAboveAnchor
1199                        ? com.android.internal.R.style.Animation_DropDownUp
1200                        : com.android.internal.R.style.Animation_DropDownDown;
1201            }
1202            return 0;
1203        }
1204        return mAnimationStyle;
1205    }
1206
1207    /**
1208     * Positions the popup window on screen. When the popup window is too tall
1209     * to fit under the anchor, a parent scroll view is seeked and scrolled up
1210     * to reclaim space. If scrolling is not possible or not enough, the popup
1211     * window gets moved on top of the anchor.
1212     * <p>
1213     * The height must have been set on the layout parameters prior to calling
1214     * this method.
1215     *
1216     * @param anchor the view on which the popup window must be anchored
1217     * @param p the layout parameters used to display the drop down
1218     * @param xoff horizontal offset used to adjust for background padding
1219     * @param yoff vertical offset used to adjust for background padding
1220     * @param gravity horizontal gravity specifying popup alignment
1221     * @return true if the popup is translated upwards to fit on screen
1222     */
1223    private boolean findDropDownPosition(View anchor, WindowManager.LayoutParams p, int xoff,
1224            int yoff, int gravity) {
1225        final int anchorHeight = anchor.getHeight();
1226        final int anchorWidth = anchor.getWidth();
1227        if (mOverlapAnchor) {
1228            yoff -= anchorHeight;
1229        }
1230
1231        anchor.getLocationInWindow(mDrawingLocation);
1232        p.x = mDrawingLocation[0] + xoff;
1233        p.y = mDrawingLocation[1] + anchorHeight + yoff;
1234
1235        final int hgrav = Gravity.getAbsoluteGravity(gravity, anchor.getLayoutDirection())
1236                & Gravity.HORIZONTAL_GRAVITY_MASK;
1237        if (hgrav == Gravity.RIGHT) {
1238            // Flip the location to align the right sides of the popup and
1239            // anchor instead of left.
1240            p.x -= mPopupWidth - anchorWidth;
1241        }
1242
1243        boolean onTop = false;
1244
1245        p.gravity = Gravity.LEFT | Gravity.TOP;
1246
1247        anchor.getLocationOnScreen(mScreenLocation);
1248        final Rect displayFrame = new Rect();
1249        anchor.getWindowVisibleDisplayFrame(displayFrame);
1250
1251        final int screenY = mScreenLocation[1] + anchorHeight + yoff;
1252        final View root = anchor.getRootView();
1253        if (screenY + mPopupHeight > displayFrame.bottom
1254                || p.x + mPopupWidth - root.getWidth() > 0) {
1255            // If the drop down disappears at the bottom of the screen, we try
1256            // to scroll a parent scrollview or move the drop down back up on
1257            // top of the edit box.
1258            if (mAllowScrollingAnchorParent) {
1259                final int scrollX = anchor.getScrollX();
1260                final int scrollY = anchor.getScrollY();
1261                final Rect r = new Rect(scrollX, scrollY, scrollX + mPopupWidth + xoff,
1262                        scrollY + mPopupHeight + anchorHeight + yoff);
1263                anchor.requestRectangleOnScreen(r, true);
1264            }
1265
1266            // Now we re-evaluate the space available, and decide from that
1267            // whether the pop-up will go above or below the anchor.
1268            anchor.getLocationInWindow(mDrawingLocation);
1269            p.x = mDrawingLocation[0] + xoff;
1270            p.y = mDrawingLocation[1] + anchorHeight + yoff;
1271
1272            // Preserve the gravity adjustment.
1273            if (hgrav == Gravity.RIGHT) {
1274                p.x -= mPopupWidth - anchorWidth;
1275            }
1276
1277            // Determine whether there is more space above or below the anchor.
1278            anchor.getLocationOnScreen(mScreenLocation);
1279            onTop = (displayFrame.bottom - mScreenLocation[1] - anchorHeight - yoff) <
1280                    (mScreenLocation[1] - yoff - displayFrame.top);
1281            if (onTop) {
1282                p.gravity = Gravity.LEFT | Gravity.BOTTOM;
1283                p.y = root.getHeight() - mDrawingLocation[1] + yoff;
1284            } else {
1285                p.y = mDrawingLocation[1] + anchorHeight + yoff;
1286            }
1287        }
1288
1289        if (mClipToScreen) {
1290            final int displayFrameWidth = displayFrame.right - displayFrame.left;
1291            final int right = p.x + p.width;
1292            if (right > displayFrameWidth) {
1293                p.x -= right - displayFrameWidth;
1294            }
1295
1296            if (p.x < displayFrame.left) {
1297                p.x = displayFrame.left;
1298                p.width = Math.min(p.width, displayFrameWidth);
1299            }
1300
1301            if (onTop) {
1302                final int popupTop = mScreenLocation[1] + yoff - mPopupHeight;
1303                if (popupTop < 0) {
1304                    p.y += popupTop;
1305                }
1306            } else {
1307                p.y = Math.max(p.y, displayFrame.top);
1308            }
1309        }
1310
1311        p.gravity |= Gravity.DISPLAY_CLIP_VERTICAL;
1312
1313        // Compute the position of the anchor relative to the popup.
1314        mAnchorRelativeX = mDrawingLocation[0] - p.x + anchorHeight / 2;
1315        mAnchorRelativeY = mDrawingLocation[1] - p.y + anchorWidth / 2;
1316
1317        return onTop;
1318    }
1319
1320    /**
1321     * Returns the maximum height that is available for the popup to be
1322     * completely shown. It is recommended that this height be the maximum for
1323     * the popup's height, otherwise it is possible that the popup will be
1324     * clipped.
1325     *
1326     * @param anchor The view on which the popup window must be anchored.
1327     * @return The maximum available height for the popup to be completely
1328     *         shown.
1329     */
1330    public int getMaxAvailableHeight(View anchor) {
1331        return getMaxAvailableHeight(anchor, 0);
1332    }
1333
1334    /**
1335     * Returns the maximum height that is available for the popup to be
1336     * completely shown. It is recommended that this height be the maximum for
1337     * the popup's height, otherwise it is possible that the popup will be
1338     * clipped.
1339     *
1340     * @param anchor The view on which the popup window must be anchored.
1341     * @param yOffset y offset from the view's bottom edge
1342     * @return The maximum available height for the popup to be completely
1343     *         shown.
1344     */
1345    public int getMaxAvailableHeight(View anchor, int yOffset) {
1346        return getMaxAvailableHeight(anchor, yOffset, false);
1347    }
1348
1349    /**
1350     * Returns the maximum height that is available for the popup to be
1351     * completely shown, optionally ignoring any bottom decorations such as
1352     * the input method. It is recommended that this height be the maximum for
1353     * the popup's height, otherwise it is possible that the popup will be
1354     * clipped.
1355     *
1356     * @param anchor The view on which the popup window must be anchored.
1357     * @param yOffset y offset from the view's bottom edge
1358     * @param ignoreBottomDecorations if true, the height returned will be
1359     *        all the way to the bottom of the display, ignoring any
1360     *        bottom decorations
1361     * @return The maximum available height for the popup to be completely
1362     *         shown.
1363     *
1364     * @hide Pending API council approval.
1365     */
1366    public int getMaxAvailableHeight(View anchor, int yOffset, boolean ignoreBottomDecorations) {
1367        final Rect displayFrame = new Rect();
1368        anchor.getWindowVisibleDisplayFrame(displayFrame);
1369
1370        final int[] anchorPos = mDrawingLocation;
1371        anchor.getLocationOnScreen(anchorPos);
1372
1373        int bottomEdge = displayFrame.bottom;
1374        if (ignoreBottomDecorations) {
1375            Resources res = anchor.getContext().getResources();
1376            bottomEdge = res.getDisplayMetrics().heightPixels;
1377        }
1378        final int distanceToBottom = bottomEdge - (anchorPos[1] + anchor.getHeight()) - yOffset;
1379        final int distanceToTop = anchorPos[1] - displayFrame.top + yOffset;
1380
1381        // anchorPos[1] is distance from anchor to top of screen
1382        int returnedHeight = Math.max(distanceToBottom, distanceToTop);
1383        if (mBackground != null) {
1384            mBackground.getPadding(mTempRect);
1385            returnedHeight -= mTempRect.top + mTempRect.bottom;
1386        }
1387
1388        return returnedHeight;
1389    }
1390
1391    /**
1392     * <p>Dispose of the popup window. This method can be invoked only after
1393     * {@link #showAsDropDown(android.view.View)} has been executed. Failing that, calling
1394     * this method will have no effect.</p>
1395     *
1396     * @see #showAsDropDown(android.view.View)
1397     */
1398    public void dismiss() {
1399        if (isShowing() && mPopupView != null) {
1400            mIsShowing = false;
1401
1402            unregisterForScrollChanged();
1403
1404            try {
1405                mWindowManager.removeViewImmediate(mPopupView);
1406            } finally {
1407                if (mPopupView != mContentView && mPopupView instanceof ViewGroup) {
1408                    ((ViewGroup) mPopupView).removeView(mContentView);
1409                }
1410                mPopupView = null;
1411
1412                if (mOnDismissListener != null) {
1413                    mOnDismissListener.onDismiss();
1414                }
1415            }
1416        }
1417    }
1418
1419    /**
1420     * Sets the listener to be called when the window is dismissed.
1421     *
1422     * @param onDismissListener The listener.
1423     */
1424    public void setOnDismissListener(OnDismissListener onDismissListener) {
1425        mOnDismissListener = onDismissListener;
1426    }
1427
1428    /**
1429     * Updates the state of the popup window, if it is currently being displayed,
1430     * from the currently set state.  This includes:
1431     * {@link #setClippingEnabled(boolean)}, {@link #setFocusable(boolean)},
1432     * {@link #setIgnoreCheekPress()}, {@link #setInputMethodMode(int)},
1433     * {@link #setTouchable(boolean)}, and {@link #setAnimationStyle(int)}.
1434     */
1435    public void update() {
1436        if (!isShowing() || mContentView == null) {
1437            return;
1438        }
1439
1440        WindowManager.LayoutParams p = (WindowManager.LayoutParams)
1441                mPopupView.getLayoutParams();
1442
1443        boolean update = false;
1444
1445        final int newAnim = computeAnimationResource();
1446        if (newAnim != p.windowAnimations) {
1447            p.windowAnimations = newAnim;
1448            update = true;
1449        }
1450
1451        final int newFlags = computeFlags(p.flags);
1452        if (newFlags != p.flags) {
1453            p.flags = newFlags;
1454            update = true;
1455        }
1456
1457        if (update) {
1458            setLayoutDirectionFromAnchor();
1459            mWindowManager.updateViewLayout(mPopupView, p);
1460        }
1461    }
1462
1463    /**
1464     * <p>Updates the dimension of the popup window. Calling this function
1465     * also updates the window with the current popup state as described
1466     * for {@link #update()}.</p>
1467     *
1468     * @param width the new width
1469     * @param height the new height
1470     */
1471    public void update(int width, int height) {
1472        WindowManager.LayoutParams p = (WindowManager.LayoutParams)
1473                mPopupView.getLayoutParams();
1474        update(p.x, p.y, width, height, false);
1475    }
1476
1477    /**
1478     * <p>Updates the position and the dimension of the popup window. Width and
1479     * height can be set to -1 to update location only.  Calling this function
1480     * also updates the window with the current popup state as
1481     * described for {@link #update()}.</p>
1482     *
1483     * @param x the new x location
1484     * @param y the new y location
1485     * @param width the new width, can be -1 to ignore
1486     * @param height the new height, can be -1 to ignore
1487     */
1488    public void update(int x, int y, int width, int height) {
1489        update(x, y, width, height, false);
1490    }
1491
1492    /**
1493     * <p>Updates the position and the dimension of the popup window. Width and
1494     * height can be set to -1 to update location only.  Calling this function
1495     * also updates the window with the current popup state as
1496     * described for {@link #update()}.</p>
1497     *
1498     * @param x the new x location
1499     * @param y the new y location
1500     * @param width the new width, can be -1 to ignore
1501     * @param height the new height, can be -1 to ignore
1502     * @param force reposition the window even if the specified position
1503     *              already seems to correspond to the LayoutParams
1504     */
1505    public void update(int x, int y, int width, int height, boolean force) {
1506        if (width != -1) {
1507            mLastWidth = width;
1508            setWidth(width);
1509        }
1510
1511        if (height != -1) {
1512            mLastHeight = height;
1513            setHeight(height);
1514        }
1515
1516        if (!isShowing() || mContentView == null) {
1517            return;
1518        }
1519
1520        WindowManager.LayoutParams p = (WindowManager.LayoutParams) mPopupView.getLayoutParams();
1521
1522        boolean update = force;
1523
1524        final int finalWidth = mWidthMode < 0 ? mWidthMode : mLastWidth;
1525        if (width != -1 && p.width != finalWidth) {
1526            p.width = mLastWidth = finalWidth;
1527            update = true;
1528        }
1529
1530        final int finalHeight = mHeightMode < 0 ? mHeightMode : mLastHeight;
1531        if (height != -1 && p.height != finalHeight) {
1532            p.height = mLastHeight = finalHeight;
1533            update = true;
1534        }
1535
1536        if (p.x != x) {
1537            p.x = x;
1538            update = true;
1539        }
1540
1541        if (p.y != y) {
1542            p.y = y;
1543            update = true;
1544        }
1545
1546        final int newAnim = computeAnimationResource();
1547        if (newAnim != p.windowAnimations) {
1548            p.windowAnimations = newAnim;
1549            update = true;
1550        }
1551
1552        final int newFlags = computeFlags(p.flags);
1553        if (newFlags != p.flags) {
1554            p.flags = newFlags;
1555            update = true;
1556        }
1557
1558        if (update) {
1559            setLayoutDirectionFromAnchor();
1560            mWindowManager.updateViewLayout(mPopupView, p);
1561        }
1562    }
1563
1564    /**
1565     * <p>Updates the position and the dimension of the popup window. Calling this
1566     * function also updates the window with the current popup state as described
1567     * for {@link #update()}.</p>
1568     *
1569     * @param anchor the popup's anchor view
1570     * @param width the new width, can be -1 to ignore
1571     * @param height the new height, can be -1 to ignore
1572     */
1573    public void update(View anchor, int width, int height) {
1574        update(anchor, false, 0, 0, true, width, height, mAnchoredGravity);
1575    }
1576
1577    /**
1578     * <p>Updates the position and the dimension of the popup window. Width and
1579     * height can be set to -1 to update location only.  Calling this function
1580     * also updates the window with the current popup state as
1581     * described for {@link #update()}.</p>
1582     *
1583     * <p>If the view later scrolls to move <code>anchor</code> to a different
1584     * location, the popup will be moved correspondingly.</p>
1585     *
1586     * @param anchor the popup's anchor view
1587     * @param xoff x offset from the view's left edge
1588     * @param yoff y offset from the view's bottom edge
1589     * @param width the new width, can be -1 to ignore
1590     * @param height the new height, can be -1 to ignore
1591     */
1592    public void update(View anchor, int xoff, int yoff, int width, int height) {
1593        update(anchor, true, xoff, yoff, true, width, height, mAnchoredGravity);
1594    }
1595
1596    private void update(View anchor, boolean updateLocation, int xoff, int yoff,
1597            boolean updateDimension, int width, int height, int gravity) {
1598
1599        if (!isShowing() || mContentView == null) {
1600            return;
1601        }
1602
1603        WeakReference<View> oldAnchor = mAnchor;
1604        final boolean needsUpdate = updateLocation
1605                && (mAnchorXoff != xoff || mAnchorYoff != yoff);
1606        if (oldAnchor == null || oldAnchor.get() != anchor || (needsUpdate && !mIsDropdown)) {
1607            registerForScrollChanged(anchor, xoff, yoff, gravity);
1608        } else if (needsUpdate) {
1609            // No need to register again if this is a DropDown, showAsDropDown already did.
1610            mAnchorXoff = xoff;
1611            mAnchorYoff = yoff;
1612            mAnchoredGravity = gravity;
1613        }
1614
1615        WindowManager.LayoutParams p = (WindowManager.LayoutParams) mPopupView.getLayoutParams();
1616
1617        if (updateDimension) {
1618            if (width == -1) {
1619                width = mPopupWidth;
1620            } else {
1621                mPopupWidth = width;
1622            }
1623            if (height == -1) {
1624                height = mPopupHeight;
1625            } else {
1626                mPopupHeight = height;
1627            }
1628        }
1629
1630        int x = p.x;
1631        int y = p.y;
1632
1633        if (updateLocation) {
1634            updateAboveAnchor(findDropDownPosition(anchor, p, xoff, yoff, gravity));
1635        } else {
1636            updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff,
1637                    mAnchoredGravity));
1638        }
1639
1640        update(p.x, p.y, width, height, x != p.x || y != p.y);
1641    }
1642
1643    /**
1644     * Listener that is called when this popup window is dismissed.
1645     */
1646    public interface OnDismissListener {
1647        /**
1648         * Called when this popup window is dismissed.
1649         */
1650        public void onDismiss();
1651    }
1652
1653    private void unregisterForScrollChanged() {
1654        WeakReference<View> anchorRef = mAnchor;
1655        View anchor = null;
1656        if (anchorRef != null) {
1657            anchor = anchorRef.get();
1658        }
1659        if (anchor != null) {
1660            ViewTreeObserver vto = anchor.getViewTreeObserver();
1661            vto.removeOnScrollChangedListener(mOnScrollChangedListener);
1662        }
1663        mAnchor = null;
1664    }
1665
1666    private void registerForScrollChanged(View anchor, int xoff, int yoff, int gravity) {
1667        unregisterForScrollChanged();
1668
1669        mAnchor = new WeakReference<View>(anchor);
1670        ViewTreeObserver vto = anchor.getViewTreeObserver();
1671        if (vto != null) {
1672            vto.addOnScrollChangedListener(mOnScrollChangedListener);
1673        }
1674
1675        mAnchorXoff = xoff;
1676        mAnchorYoff = yoff;
1677        mAnchoredGravity = gravity;
1678    }
1679
1680    private class PopupViewContainer extends FrameLayout {
1681        private static final String TAG = "PopupWindow.PopupViewContainer";
1682
1683        public PopupViewContainer(Context context) {
1684            super(context);
1685        }
1686
1687        @Override
1688        protected int[] onCreateDrawableState(int extraSpace) {
1689            if (mAboveAnchor) {
1690                // 1 more needed for the above anchor state
1691                final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
1692                View.mergeDrawableStates(drawableState, ABOVE_ANCHOR_STATE_SET);
1693                return drawableState;
1694            } else {
1695                return super.onCreateDrawableState(extraSpace);
1696            }
1697        }
1698
1699        @Override
1700        public boolean dispatchKeyEvent(KeyEvent event) {
1701            if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
1702                if (getKeyDispatcherState() == null) {
1703                    return super.dispatchKeyEvent(event);
1704                }
1705
1706                if (event.getAction() == KeyEvent.ACTION_DOWN
1707                        && event.getRepeatCount() == 0) {
1708                    KeyEvent.DispatcherState state = getKeyDispatcherState();
1709                    if (state != null) {
1710                        state.startTracking(event, this);
1711                    }
1712                    return true;
1713                } else if (event.getAction() == KeyEvent.ACTION_UP) {
1714                    KeyEvent.DispatcherState state = getKeyDispatcherState();
1715                    if (state != null && state.isTracking(event) && !event.isCanceled()) {
1716                        dismiss();
1717                        return true;
1718                    }
1719                }
1720                return super.dispatchKeyEvent(event);
1721            } else {
1722                return super.dispatchKeyEvent(event);
1723            }
1724        }
1725
1726        @Override
1727        public boolean dispatchTouchEvent(MotionEvent ev) {
1728            if (mTouchInterceptor != null && mTouchInterceptor.onTouch(this, ev)) {
1729                return true;
1730            }
1731            return super.dispatchTouchEvent(ev);
1732        }
1733
1734        @Override
1735        public boolean onTouchEvent(MotionEvent event) {
1736            final int x = (int) event.getX();
1737            final int y = (int) event.getY();
1738
1739            if ((event.getAction() == MotionEvent.ACTION_DOWN)
1740                    && ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) {
1741                dismiss();
1742                return true;
1743            } else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
1744                dismiss();
1745                return true;
1746            } else {
1747                return super.onTouchEvent(event);
1748            }
1749        }
1750
1751        @Override
1752        public void sendAccessibilityEvent(int eventType) {
1753            // clinets are interested in the content not the container, make it event source
1754            if (mContentView != null) {
1755                mContentView.sendAccessibilityEvent(eventType);
1756            } else {
1757                super.sendAccessibilityEvent(eventType);
1758            }
1759        }
1760    }
1761
1762}
1763