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