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