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