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