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