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