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