PopupWindow.java revision 9266c558bf1d21ff647525ff99f7dadbca417309
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.view.KeyEvent;
22import android.view.MotionEvent;
23import android.view.View;
24import android.view.WindowManager;
25import android.view.Gravity;
26import android.view.ViewGroup;
27import android.view.View.OnTouchListener;
28import android.graphics.PixelFormat;
29import android.graphics.Rect;
30import android.graphics.drawable.Drawable;
31import android.os.IBinder;
32import android.content.Context;
33import android.content.res.TypedArray;
34import android.util.AttributeSet;
35
36/**
37 * <p>A popup window that can be used to display an arbitrary view. The popup
38 * windows is a floating container that appears on top of the current
39 * activity.</p>
40 *
41 * @see android.widget.AutoCompleteTextView
42 * @see android.widget.Spinner
43 */
44public class PopupWindow {
45    /**
46     * Mode for {@link #setInputMethodMode(int): the requirements for the
47     * input method should be based on the focusability of the popup.  That is
48     * if it is focusable than it needs to work with the input method, else
49     * it doesn't.
50     */
51    public static final int INPUT_METHOD_FROM_FOCUSABLE = 0;
52
53    /**
54     * Mode for {@link #setInputMethodMode(int): this popup always needs to
55     * work with an input method, regardless of whether it is focusable.  This
56     * means that it will always be displayed so that the user can also operate
57     * the input method while it is shown.
58     */
59
60    public static final int INPUT_METHOD_NEEDED = 1;
61
62    /**
63     * Mode for {@link #setInputMethodMode(int): this popup never needs to
64     * work with an input method, regardless of whether it is focusable.  This
65     * means that it will always be displayed to use as much space on the
66     * screen as needed, regardless of whether this covers the input method.
67     */
68    public static final int INPUT_METHOD_NOT_NEEDED = 2;
69
70    private final Context mContext;
71    private final WindowManager mWindowManager;
72
73    private boolean mIsShowing;
74    private boolean mIsDropdown;
75
76    private View mContentView;
77    private View mPopupView;
78    private boolean mFocusable;
79    private int mInputMethodMode = INPUT_METHOD_FROM_FOCUSABLE;
80    private boolean mTouchable = true;
81    private boolean mOutsideTouchable = false;
82    private boolean mClippingEnabled = true;
83
84    private OnTouchListener mTouchInterceptor;
85
86    private int mWidthMode;
87    private int mWidth;
88    private int mLastWidth;
89    private int mHeightMode;
90    private int mHeight;
91    private int mLastHeight;
92
93    private int mPopupWidth;
94    private int mPopupHeight;
95
96    private int[] mDrawingLocation = new int[2];
97    private int[] mScreenLocation = new int[2];
98    private Rect mTempRect = new Rect();
99
100    private Drawable mBackground;
101
102    private boolean mAboveAnchor;
103
104    private OnDismissListener mOnDismissListener;
105    private boolean mIgnoreCheekPress = false;
106
107    private int mAnimationStyle = -1;
108
109    private static final int[] ABOVE_ANCHOR_STATE_SET = new int[] {
110        com.android.internal.R.attr.state_above_anchor
111    };
112
113    /**
114     * <p>Create a new empty, non focusable popup window of dimension (0,0).</p>
115     *
116     * <p>The popup does provide a background.</p>
117     */
118    public PopupWindow(Context context) {
119        this(context, null);
120    }
121
122    /**
123     * <p>Create a new empty, non focusable popup window of dimension (0,0).</p>
124     *
125     * <p>The popup does provide a background.</p>
126     */
127    public PopupWindow(Context context, AttributeSet attrs) {
128        this(context, attrs, com.android.internal.R.attr.popupWindowStyle);
129    }
130
131    /**
132     * <p>Create a new empty, non focusable popup window of dimension (0,0).</p>
133     *
134     * <p>The popup does provide a background.</p>
135     */
136    public PopupWindow(Context context, AttributeSet attrs, int defStyle) {
137        mContext = context;
138        mWindowManager = (WindowManager)context.getSystemService(
139                Context.WINDOW_SERVICE);
140
141        TypedArray a =
142            context.obtainStyledAttributes(
143                attrs, com.android.internal.R.styleable.PopupWindow, defStyle, 0);
144
145        mBackground = a.getDrawable(R.styleable.PopupWindow_popupBackground);
146
147        a.recycle();
148    }
149
150    /**
151     * <p>Create a new empty, non focusable popup window of dimension (0,0).</p>
152     *
153     * <p>The popup does not provide any background. This should be handled
154     * by the content view.</p>
155     */
156    public PopupWindow() {
157        this(null, 0, 0);
158    }
159
160    /**
161     * <p>Create a new non focusable popup window which can display the
162     * <tt>contentView</tt>. The dimension of the window are (0,0).</p>
163     *
164     * <p>The popup does not provide any background. This should be handled
165     * by the content view.</p>
166     *
167     * @param contentView the popup's content
168     */
169    public PopupWindow(View contentView) {
170        this(contentView, 0, 0);
171    }
172
173    /**
174     * <p>Create a new empty, non focusable popup window. The dimension of the
175     * window must be passed to this constructor.</p>
176     *
177     * <p>The popup does not provide any background. This should be handled
178     * by the content view.</p>
179     *
180     * @param width the popup's width
181     * @param height the popup's height
182     */
183    public PopupWindow(int width, int height) {
184        this(null, width, height);
185    }
186
187    /**
188     * <p>Create a new non focusable popup window which can display the
189     * <tt>contentView</tt>. The dimension of the window must be passed to
190     * this constructor.</p>
191     *
192     * <p>The popup does not provide any background. This should be handled
193     * by the content view.</p>
194     *
195     * @param contentView the popup's content
196     * @param width the popup's width
197     * @param height the popup's height
198     */
199    public PopupWindow(View contentView, int width, int height) {
200        this(contentView, width, height, false);
201    }
202
203    /**
204     * <p>Create a new popup window which can display the <tt>contentView</tt>.
205     * The dimension of the window must be passed to this constructor.</p>
206     *
207     * <p>The popup does not provide any background. This should be handled
208     * by the content view.</p>
209     *
210     * @param contentView the popup's content
211     * @param width the popup's width
212     * @param height the popup's height
213     * @param focusable true if the popup can be focused, false otherwise
214     */
215    public PopupWindow(View contentView, int width, int height,
216            boolean focusable) {
217        mContext = contentView.getContext();
218        mWindowManager = (WindowManager)mContext.getSystemService(
219                Context.WINDOW_SERVICE);
220        setContentView(contentView);
221        setWidth(width);
222        setHeight(height);
223        setFocusable(focusable);
224    }
225
226    /**
227     * <p>Return the drawable used as the popup window's background.</p>
228     *
229     * @return the background drawable or null
230     */
231    public Drawable getBackground() {
232        return mBackground;
233    }
234
235    /**
236     * <p>Change the background drawable for this popup window. The background
237     * can be set to null.</p>
238     *
239     * @param background the popup's background
240     */
241    public void setBackgroundDrawable(Drawable background) {
242        mBackground = background;
243    }
244
245    /**
246     * <p>Return the animation style to use the popup appears and disappears</p>
247     *
248     * @return the animation style to use the popup appears and disappears
249     */
250    public int getAnimationStyle() {
251        return mAnimationStyle;
252    }
253
254    /**
255     * Set the flag on popup to ignore cheek press eventt; by default this flag
256     * is set to false
257     * which means the pop wont ignore cheek press dispatch events.
258     *
259     * <p>If the popup is showing, calling this method will take effect only
260     * the next time the popup is shown or through a manual call to one of
261     * the {@link #update()} methods.</p>
262     *
263     * @see #update()
264     */
265    public void setIgnoreCheekPress() {
266        mIgnoreCheekPress = true;
267    }
268
269
270    /**
271     * <p>Change the animation style resource for this popup.</p>
272     *
273     * <p>If the popup is showing, calling this method will take effect only
274     * the next time the popup is shown or through a manual call to one of
275     * the {@link #update()} methods.</p>
276     *
277     * @param animationStyle animation style to use when the popup appears
278     *      and disappears.  Set to -1 for the default animation, 0 for no
279     *      animation, or a resource identifier for an explicit animation.
280     *
281     * @see #update()
282     */
283    public void setAnimationStyle(int animationStyle) {
284        mAnimationStyle = animationStyle;
285    }
286
287    /**
288     * <p>Return the view used as the content of the popup window.</p>
289     *
290     * @return a {@link android.view.View} representing the popup's content
291     *
292     * @see #setContentView(android.view.View)
293     */
294    public View getContentView() {
295        return mContentView;
296    }
297
298    /**
299     * <p>Change the popup's content. The content is represented by an instance
300     * of {@link android.view.View}.</p>
301     *
302     * <p>This method has no effect if called when the popup is showing.  To
303     * apply it while a popup is showing, call </p>
304     *
305     * @param contentView the new content for the popup
306     *
307     * @see #getContentView()
308     * @see #isShowing()
309     */
310    public void setContentView(View contentView) {
311        if (isShowing()) {
312            return;
313        }
314
315        mContentView = contentView;
316    }
317
318    /**
319     * Set a callback for all touch events being dispatched to the popup
320     * window.
321     */
322    public void setTouchInterceptor(OnTouchListener l) {
323        mTouchInterceptor = l;
324    }
325
326    /**
327     * <p>Indicate whether the popup window can grab the focus.</p>
328     *
329     * @return true if the popup is focusable, false otherwise
330     *
331     * @see #setFocusable(boolean)
332     */
333    public boolean isFocusable() {
334        return mFocusable;
335    }
336
337    /**
338     * <p>Changes the focusability of the popup window. When focusable, the
339     * window will grab the focus from the current focused widget if the popup
340     * contains a focusable {@link android.view.View}.  By default a popup
341     * window is not focusable.</p>
342     *
343     * <p>If the popup is showing, calling this method will take effect only
344     * the next time the popup is shown or through a manual call to one of
345     * the {@link #update()} methods.</p>
346     *
347     * @param focusable true if the popup should grab focus, false otherwise.
348     *
349     * @see #isFocusable()
350     * @see #isShowing()
351     * @see #update()
352     */
353    public void setFocusable(boolean focusable) {
354        mFocusable = focusable;
355    }
356
357    /**
358     * Return the current value in {@link #setInputMethodMode(int)}.
359     *
360     * @see #setInputMethodMode(int)
361     */
362    public int getInputMethodMode() {
363        return mInputMethodMode;
364
365    }
366
367    /**
368     * Control how the popup operates with an input method: one of
369     * {@link #INPUT_METHOD_FROM_FOCUSABLE}, {@link #INPUT_METHOD_NEEDED},
370     * or {@link #INPUT_METHOD_NOT_NEEDED}.
371     *
372     * <p>If the popup is showing, calling this method will take effect only
373     * the next time the popup is shown or through a manual call to one of
374     * the {@link #update()} methods.</p>
375     *
376     * @see #getInputMethodMode()
377     * @see #update()
378     */
379    public void setInputMethodMode(int mode) {
380        mInputMethodMode = mode;
381    }
382
383    /**
384     * <p>Indicates whether the popup window receives touch events.</p>
385     *
386     * @return true if the popup is touchable, false otherwise
387     *
388     * @see #setTouchable(boolean)
389     */
390    public boolean isTouchable() {
391        return mTouchable;
392    }
393
394    /**
395     * <p>Changes the touchability of the popup window. When touchable, the
396     * window will receive touch events, otherwise touch events will go to the
397     * window below it. By default the window is touchable.</p>
398     *
399     * <p>If the popup is showing, calling this method will take effect only
400     * the next time the popup is shown or through a manual call to one of
401     * the {@link #update()} methods.</p>
402     *
403     * @param touchable true if the popup should receive touch events, false otherwise
404     *
405     * @see #isTouchable()
406     * @see #isShowing()
407     * @see #update()
408     */
409    public void setTouchable(boolean touchable) {
410        mTouchable = touchable;
411    }
412
413    /**
414     * <p>Indicates whether the popup window will be informed of touch events
415     * outside of its window.</p>
416     *
417     * @return true if the popup is outside touchable, false otherwise
418     *
419     * @see #setOutsideTouchable(boolean)
420     */
421    public boolean isOutsideTouchable() {
422        return mOutsideTouchable;
423    }
424
425    /**
426     * <p>Controls whether the pop-up will be informed of touch events outside
427     * of its window.  This only makes sense for pop-ups that are touchable
428     * but not focusable, which means touches outside of the window will
429     * be delivered to the window behind.  The default is false.</p>
430     *
431     * <p>If the popup is showing, calling this method will take effect only
432     * the next time the popup is shown or through a manual call to one of
433     * the {@link #update()} methods.</p>
434     *
435     * @param touchable true if the popup should receive outside
436     * touch events, false otherwise
437     *
438     * @see #isOutsideTouchable()
439     * @see #isShowing()
440     * @see #update()
441     */
442    public void setOutsideTouchable(boolean touchable) {
443        mOutsideTouchable = touchable;
444    }
445
446    /**
447     * <p>Indicates whether clipping of the popup window is enabled.</p>
448     *
449     * @return true if the clipping is enabled, false otherwise
450     *
451     * @see #setClippingEnabled(boolean)
452     */
453    public boolean isClippingEnabled() {
454        return mClippingEnabled;
455    }
456
457    /**
458     * <p>Allows the popup window to extend beyond the bounds of the screen. By default the
459     * window is clipped to the screen boundaries. Setting this to false will allow windows to be
460     * accurately positioned.</p>
461     *
462     * <p>If the popup is showing, calling this method will take effect only
463     * the next time the popup is shown or through a manual call to one of
464     * the {@link #update()} methods.</p>
465     *
466     * @param enabled false if the window should be allowed to extend outside of the screen
467     * @see #isShowing()
468     * @see #isClippingEnabled()
469     * @see #update()
470     */
471    public void setClippingEnabled(boolean enabled) {
472        mClippingEnabled = enabled;
473    }
474
475    /**
476     * <p>Change the width and height measure specs that are given to the
477     * window manager by the popup.  By default these are 0, meaning that
478     * the current width or height is requested as an explicit size from
479     * the window manager.  You can supply
480     * {@link ViewGroup.LayoutParams#WRAP_CONTENT} or
481     * {@link ViewGroup.LayoutParams#FILL_PARENT} to have that measure
482     * spec supplied instead, replacing the absolute width and height that
483     * has been set in the popup.</p>
484     *
485     * <p>If the popup is showing, calling this method will take effect only
486     * the next time the popup is shown.</p>
487     *
488     * @param widthSpec an explicit width measure spec mode, either
489     * {@link ViewGroup.LayoutParams#WRAP_CONTENT},
490     * {@link ViewGroup.LayoutParams#FILL_PARENT}, or 0 to use the absolute
491     * width.
492     * @param heightSpec an explicit height measure spec mode, either
493     * {@link ViewGroup.LayoutParams#WRAP_CONTENT},
494     * {@link ViewGroup.LayoutParams#FILL_PARENT}, or 0 to use the absolute
495     * height.
496     */
497    public void setWindowLayoutMode(int widthSpec, int heightSpec) {
498        mWidthMode = widthSpec;
499        mHeightMode = heightSpec;
500    }
501
502    /**
503     * <p>Return this popup's height MeasureSpec</p>
504     *
505     * @return the height MeasureSpec of the popup
506     *
507     * @see #setHeight(int)
508     */
509    public int getHeight() {
510        return mHeight;
511    }
512
513    /**
514     * <p>Change the popup's height MeasureSpec</p>
515     *
516     * <p>If the popup is showing, calling this method will take effect only
517     * the next time the popup is shown.</p>
518     *
519     * @param height the height MeasureSpec of the popup
520     *
521     * @see #getHeight()
522     * @see #isShowing()
523     */
524    public void setHeight(int height) {
525        mHeight = height;
526    }
527
528    /**
529     * <p>Return this popup's width MeasureSpec</p>
530     *
531     * @return the width MeasureSpec of the popup
532     *
533     * @see #setWidth(int)
534     */
535    public int getWidth() {
536        return mWidth;
537    }
538
539    /**
540     * <p>Change the popup's width MeasureSpec</p>
541     *
542     * <p>If the popup is showing, calling this method will take effect only
543     * the next time the popup is shown.</p>
544     *
545     * @param width the width MeasureSpec of the popup
546     *
547     * @see #getWidth()
548     * @see #isShowing()
549     */
550    public void setWidth(int width) {
551        mWidth = width;
552    }
553
554    /**
555     * <p>Indicate whether this popup window is showing on screen.</p>
556     *
557     * @return true if the popup is showing, false otherwise
558     */
559    public boolean isShowing() {
560        return mIsShowing;
561    }
562
563    /**
564     * <p>
565     * Display the content view in a popup window at the specified location. If the popup window
566     * cannot fit on screen, it will be clipped. See {@link android.view.WindowManager.LayoutParams}
567     * for more information on how gravity and the x and y parameters are related. Specifying
568     * a gravity of {@link android.view.Gravity#NO_GRAVITY} is similar to specifying
569     * <code>Gravity.LEFT | Gravity.TOP</code>.
570     * </p>
571     *
572     * @param parent a parent view to get the {@link android.view.View#getWindowToken()} token from
573     * @param gravity the gravity which controls the placement of the popup window
574     * @param x the popup's x location offset
575     * @param y the popup's y location offset
576     */
577    public void showAtLocation(View parent, int gravity, int x, int y) {
578        if (isShowing() || mContentView == null) {
579            return;
580        }
581
582        mIsShowing = true;
583        mIsDropdown = false;
584
585        WindowManager.LayoutParams p = createPopupLayout(parent.getWindowToken());
586        p.windowAnimations = computeAnimationResource();
587
588        preparePopup(p);
589        if (gravity == Gravity.NO_GRAVITY) {
590            gravity = Gravity.TOP | Gravity.LEFT;
591        }
592        p.gravity = gravity;
593        p.x = x;
594        p.y = y;
595        invokePopup(p);
596    }
597
598    /**
599     * <p>Display the content view in a popup window anchored to the bottom-left
600     * corner of the anchor view. If there is not enough room on screen to show
601     * the popup in its entirety, this method tries to find a parent scroll
602     * view to scroll. If no parent scroll view can be scrolled, the bottom-left
603     * corner of the popup is pinned at the top left corner of the anchor view.</p>
604     *
605     * @param anchor the view on which to pin the popup window
606     *
607     * @see #dismiss()
608     */
609    public void showAsDropDown(View anchor) {
610        showAsDropDown(anchor, 0, 0);
611    }
612
613    /**
614     * <p>Display the content view in a popup window anchored to the bottom-left
615     * corner of the anchor view offset by the specified x and y coordinates.
616     * If there is not enough room on screen to show
617     * the popup in its entirety, this method tries to find a parent scroll
618     * view to scroll. If no parent scroll view can be scrolled, the bottom-left
619     * corner of the popup is pinned at the top left corner of the anchor view.</p>
620     *
621     * @param anchor the view on which to pin the popup window
622     *
623     * @see #dismiss()
624     */
625    public void showAsDropDown(View anchor, int xoff, int yoff) {
626        if (isShowing() || mContentView == null) {
627            return;
628        }
629
630        mIsShowing = true;
631        mIsDropdown = true;
632
633        WindowManager.LayoutParams p = createPopupLayout(anchor.getWindowToken());
634        preparePopup(p);
635        if (mBackground != null) {
636            mPopupView.refreshDrawableState();
637        }
638        mAboveAnchor = findDropDownPosition(anchor, p, xoff, yoff);
639        if (mHeightMode < 0) p.height = mLastHeight = mHeightMode;
640        if (mWidthMode < 0) p.width = mLastWidth = mWidthMode;
641        p.windowAnimations = computeAnimationResource();
642        invokePopup(p);
643    }
644
645    /**
646     * <p>Prepare the popup by embedding in into a new ViewGroup if the
647     * background drawable is not null. If embedding is required, the layout
648     * parameters' height is mnodified to take into account the background's
649     * padding.</p>
650     *
651     * @param p the layout parameters of the popup's content view
652     */
653    private void preparePopup(WindowManager.LayoutParams p) {
654        if (mBackground != null) {
655            // when a background is available, we embed the content view
656            // within another view that owns the background drawable
657            PopupViewContainer popupViewContainer = new PopupViewContainer(mContext);
658            PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams(
659                    ViewGroup.LayoutParams.FILL_PARENT,
660                    ViewGroup.LayoutParams.FILL_PARENT
661            );
662            popupViewContainer.setBackgroundDrawable(mBackground);
663            popupViewContainer.addView(mContentView, listParams);
664
665            if (p.height >= 0) {
666                // accomodate the popup's height to take into account the
667                // background's padding
668                p.height += popupViewContainer.getPaddingTop() +
669                        popupViewContainer.getPaddingBottom();
670            }
671            if (p.width >= 0) {
672                // accomodate the popup's width to take into account the
673                // background's padding
674                p.width += popupViewContainer.getPaddingLeft() +
675                        popupViewContainer.getPaddingRight();
676            }
677            mPopupView = popupViewContainer;
678        } else {
679            mPopupView = mContentView;
680        }
681        mPopupWidth = p.width;
682        mPopupHeight = p.height;
683    }
684
685    /**
686     * <p>Invoke the popup window by adding the content view to the window
687     * manager.</p>
688     *
689     * <p>The content view must be non-null when this method is invoked.</p>
690     *
691     * @param p the layout parameters of the popup's content view
692     */
693    private void invokePopup(WindowManager.LayoutParams p) {
694        mWindowManager.addView(mPopupView, p);
695    }
696
697    /**
698     * <p>Generate the layout parameters for the popup window.</p>
699     *
700     * @param token the window token used to bind the popup's window
701     *
702     * @return the layout parameters to pass to the window manager
703     */
704    private WindowManager.LayoutParams createPopupLayout(IBinder token) {
705        // generates the layout parameters for the drop down
706        // we want a fixed size view located at the bottom left of the anchor
707        WindowManager.LayoutParams p = new WindowManager.LayoutParams();
708        // these gravity settings put the view at the top left corner of the
709        // screen. The view is then positioned to the appropriate location
710        // by setting the x and y offsets to match the anchor's bottom
711        // left corner
712        p.gravity = Gravity.LEFT | Gravity.TOP;
713        p.width = mLastWidth = mWidth;
714        p.height = mLastHeight = mHeight;
715        if (mBackground != null) {
716            p.format = mBackground.getOpacity();
717        } else {
718            p.format = PixelFormat.TRANSLUCENT;
719        }
720        p.flags = computeFlags(p.flags);
721        p.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
722        p.token = token;
723
724        return p;
725    }
726
727    private int computeFlags(int curFlags) {
728        curFlags &= ~(
729                WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES |
730                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
731                WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE |
732                WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH |
733                WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS |
734                WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
735        if(mIgnoreCheekPress) {
736            curFlags |= WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
737        }
738        if (!mFocusable) {
739            curFlags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
740            if (mInputMethodMode == INPUT_METHOD_NEEDED) {
741                curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
742            }
743        } else if (mInputMethodMode == INPUT_METHOD_NOT_NEEDED) {
744            curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
745        }
746        if (!mTouchable) {
747            curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
748        }
749        if (mTouchable) {
750            curFlags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
751        }
752        if (!mClippingEnabled) {
753            curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
754        }
755        return curFlags;
756    }
757
758    private int computeAnimationResource() {
759        if (mAnimationStyle == -1) {
760            if (mIsDropdown) {
761                return mAboveAnchor
762                        ? com.android.internal.R.style.Animation_DropDownUp
763                        : com.android.internal.R.style.Animation_DropDownDown;
764            }
765            return 0;
766        }
767        return mAnimationStyle;
768    }
769
770    /**
771     * <p>Positions the popup window on screen. When the popup window is too
772     * tall to fit under the anchor, a parent scroll view is seeked and scrolled
773     * up to reclaim space. If scrolling is not possible or not enough, the
774     * popup window gets moved on top of the anchor.</p>
775     *
776     * <p>The height must have been set on the layout parameters prior to
777     * calling this method.</p>
778     *
779     * @param anchor the view on which the popup window must be anchored
780     * @param p the layout parameters used to display the drop down
781     *
782     * @return true if the popup is translated upwards to fit on screen
783     */
784    private boolean findDropDownPosition(View anchor, WindowManager.LayoutParams p, int xoff, int yoff) {
785        anchor.getLocationInWindow(mDrawingLocation);
786        p.x = mDrawingLocation[0] + xoff;
787        p.y = mDrawingLocation[1] + anchor.getMeasuredHeight() + yoff;
788
789        boolean onTop = false;
790
791        p.gravity = Gravity.LEFT | Gravity.TOP;
792
793        anchor.getLocationOnScreen(mScreenLocation);
794        final Rect displayFrame = new Rect();
795        anchor.getWindowVisibleDisplayFrame(displayFrame);
796
797        final View root = anchor.getRootView();
798        if (mScreenLocation[1] + anchor.getMeasuredHeight() + yoff + mPopupHeight > displayFrame.bottom
799                || p.x + mPopupWidth - root.getWidth() > 0) {
800            // if the drop down disappears at the bottom of the screen. we try to
801            // scroll a parent scrollview or move the drop down back up on top of
802            // the edit box
803            int scrollX = anchor.getScrollX();
804            int scrollY = anchor.getScrollY();
805            Rect r = new Rect(scrollX, scrollY,  scrollX + mPopupWidth,
806                    scrollY + mPopupHeight + anchor.getMeasuredHeight());
807            anchor.requestRectangleOnScreen(r, true);
808
809            // now we re-evaluate the space available, and decide from that
810            // whether the pop-up will go above or below the anchor.
811            anchor.getLocationInWindow(mDrawingLocation);
812            p.x = mDrawingLocation[0] + xoff;
813            p.y = mDrawingLocation[1] + anchor.getMeasuredHeight() + yoff;
814
815            // determine whether there is more space above or below the anchor
816            anchor.getLocationOnScreen(mScreenLocation);
817
818            onTop = (displayFrame.bottom - mScreenLocation[1] - anchor.getMeasuredHeight() - yoff)
819                    < (mScreenLocation[1] - yoff - displayFrame.top);
820            if (onTop) {
821                p.gravity = Gravity.LEFT | Gravity.BOTTOM;
822                p.y = root.getHeight() - mDrawingLocation[1] - yoff;
823            } else {
824                p.y = mDrawingLocation[1] + anchor.getMeasuredHeight() + yoff;
825            }
826        }
827
828        p.gravity |= Gravity.DISPLAY_CLIP_VERTICAL;
829
830        return onTop;
831    }
832
833    /**
834     * Returns the maximum height that is available for the popup to be
835     * completely shown. It is recommended that this height be the maximum for
836     * the popup's height, otherwise it is possible that the popup will be
837     * clipped.
838     *
839     * @param anchor The view on which the popup window must be anchored.
840     * @return The maximum available height for the popup to be completely
841     *         shown.
842     */
843    public int getMaxAvailableHeight(View anchor) {
844        final Rect displayFrame = new Rect();
845        anchor.getWindowVisibleDisplayFrame(displayFrame);
846
847        final int[] anchorPos = mDrawingLocation;
848        anchor.getLocationOnScreen(anchorPos);
849
850        final int distanceToBottom = displayFrame.bottom
851                - (anchorPos[1] + anchor.getHeight());
852        final int distanceToTop = anchorPos[1] - displayFrame.top;
853
854        // anchorPos[1] is distance from anchor to top of screen
855        int returnedHeight = Math.max(distanceToBottom, distanceToTop);
856        if (mBackground != null) {
857            mBackground.getPadding(mTempRect);
858            returnedHeight -= mTempRect.top + mTempRect.bottom;
859        }
860
861        return returnedHeight;
862    }
863
864    /**
865     * <p>Dispose of the popup window. This method can be invoked only after
866     * {@link #showAsDropDown(android.view.View)} has been executed. Failing that, calling
867     * this method will have no effect.</p>
868     *
869     * @see #showAsDropDown(android.view.View)
870     */
871    public void dismiss() {
872        if (isShowing() && mPopupView != null) {
873            mWindowManager.removeView(mPopupView);
874            if (mPopupView != mContentView && mPopupView instanceof ViewGroup) {
875                ((ViewGroup) mPopupView).removeView(mContentView);
876            }
877            mPopupView = null;
878            mIsShowing = false;
879
880            if (mOnDismissListener != null) {
881                mOnDismissListener.onDismiss();
882            }
883        }
884    }
885
886    /**
887     * Sets the listener to be called when the window is dismissed.
888     *
889     * @param onDismissListener The listener.
890     */
891    public void setOnDismissListener(OnDismissListener onDismissListener) {
892        mOnDismissListener = onDismissListener;
893    }
894
895    /**
896     * Updates the state of the popup window, if it is currently being displayed,
897     * from the currently set state.  This include:
898     * {@link #setClippingEnabled(boolean)}, {@link #setFocusable(boolean)},
899     * {@link #setIgnoreCheekPress()}, {@link #setInputMethodMode(int)},
900     * {@link #setTouchable(boolean)}, and {@link #setAnimationStyle(int)}.
901     */
902    public void update() {
903        if (!isShowing() || mContentView == null) {
904            return;
905        }
906
907        WindowManager.LayoutParams p = (WindowManager.LayoutParams)
908                mPopupView.getLayoutParams();
909
910        boolean update = false;
911
912        final int newAnim = computeAnimationResource();
913        if (newAnim != p.windowAnimations) {
914            p.windowAnimations = newAnim;
915            update = true;
916        }
917
918        final int newFlags = computeFlags(p.flags);
919        if (newFlags != p.flags) {
920            p.flags = newFlags;
921            update = true;
922        }
923
924        if (update) {
925            mWindowManager.updateViewLayout(mPopupView, p);
926        }
927    }
928
929    /**
930     * <p>Updates the position and the dimension of the popup window. Width and
931     * height can be set to -1 to update location only.  Calling this function
932     * also updates the window with the current popup state as
933     * described for {@link #update()}.</p>
934     *
935     * @param x the new x location
936     * @param y the new y location
937     * @param width the new width, can be -1 to ignore
938     * @param height the new height, can be -1 to ignore
939     */
940    public void update(int x, int y, int width, int height) {
941        if (width != -1) {
942            setWidth(width);
943        }
944
945        if (height != -1) {
946            setHeight(height);
947        }
948
949        if (!isShowing() || mContentView == null) {
950            return;
951        }
952
953        WindowManager.LayoutParams p = (WindowManager.LayoutParams)
954                mPopupView.getLayoutParams();
955
956        boolean update = false;
957
958        final int finalWidth = mWidthMode < 0 ? mWidthMode : mLastWidth;
959        if (width != -1 && p.width != finalWidth) {
960            p.width = mLastWidth = finalWidth;
961            update = true;
962        }
963
964        final int finalHeight = mHeightMode < 0 ? mHeightMode : mLastHeight;
965        if (height != -1 && p.height != finalHeight) {
966            p.height = mLastHeight = finalHeight;
967            update = true;
968        }
969
970        if (p.x != x) {
971            p.x = x;
972            update = true;
973        }
974
975        if (p.y != y) {
976            p.y = y;
977            update = true;
978        }
979
980        final int newAnim = computeAnimationResource();
981        if (newAnim != p.windowAnimations) {
982            p.windowAnimations = newAnim;
983            update = true;
984        }
985
986        final int newFlags = computeFlags(p.flags);
987        if (newFlags != p.flags) {
988            p.flags = newFlags;
989            update = true;
990        }
991
992        if (update) {
993            if (mPopupView != mContentView) {
994                final View popupViewContainer = mPopupView;
995                if (p.height >= 0) {
996                    // accomodate the popup's height to take into account the
997                    // background's padding
998                    p.height += popupViewContainer.getPaddingTop() +
999                            popupViewContainer.getPaddingBottom();
1000                }
1001                if (p.width >= 0) {
1002                    // accomodate the popup's width to take into account the
1003                    // background's padding
1004                    p.width += popupViewContainer.getPaddingLeft() +
1005                            popupViewContainer.getPaddingRight();
1006                }
1007            }
1008
1009            mWindowManager.updateViewLayout(mPopupView, p);
1010        }
1011    }
1012
1013    /**
1014     * <p>Updates the position and the dimension of the popup window. Width and
1015     * height can be set to -1 to update location only.  Calling this function
1016     * also updates the window with the current popup state as
1017     * described for {@link #update()}.</p>
1018     *
1019     * @param anchor the popup's anchor view
1020     * @param width the new width, can be -1 to ignore
1021     * @param height the new height, can be -1 to ignore
1022     */
1023    public void update(View anchor, int width, int height) {
1024        update(anchor, 0, 0, width, height);
1025    }
1026
1027    /**
1028     * <p>Updates the position and the dimension of the popup window. Width and
1029     * height can be set to -1 to update location only.  Calling this function
1030     * also updates the window with the current popup state as
1031     * described for {@link #update()}.</p>
1032     *
1033     * @param anchor the popup's anchor view
1034     * @param xoff x offset from the view's left edge
1035     * @param yoff y offset from the view's bottom edge
1036     * @param width the new width, can be -1 to ignore
1037     * @param height the new height, can be -1 to ignore
1038     */
1039    public void update(View anchor, int xoff, int yoff, int width, int height) {
1040        if (!isShowing() || mContentView == null) {
1041            return;
1042        }
1043
1044        WindowManager.LayoutParams p = (WindowManager.LayoutParams)
1045                mPopupView.getLayoutParams();
1046
1047        if (width == -1) {
1048            width = mPopupWidth;
1049        } else {
1050            mPopupWidth = width;
1051        }
1052        if (height == -1) {
1053            height = mPopupHeight;
1054        } else {
1055            mPopupHeight = height;
1056        }
1057
1058        findDropDownPosition(anchor, p, xoff, yoff);
1059        update(p.x, p.y, width, height);
1060    }
1061
1062    /**
1063     * Listener that is called when this popup window is dismissed.
1064     */
1065    public interface OnDismissListener {
1066        /**
1067         * Called when this popup window is dismissed.
1068         */
1069        public void onDismiss();
1070    }
1071
1072    private class PopupViewContainer extends FrameLayout {
1073
1074        public PopupViewContainer(Context context) {
1075            super(context);
1076        }
1077
1078        @Override
1079        protected int[] onCreateDrawableState(int extraSpace) {
1080            if (mAboveAnchor) {
1081                // 1 more needed for the above anchor state
1082                final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
1083                View.mergeDrawableStates(drawableState, ABOVE_ANCHOR_STATE_SET);
1084                return drawableState;
1085            } else {
1086                return super.onCreateDrawableState(extraSpace);
1087            }
1088        }
1089
1090        @Override
1091        public boolean dispatchKeyEvent(KeyEvent event) {
1092            if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
1093                dismiss();
1094                return true;
1095            } else {
1096                return super.dispatchKeyEvent(event);
1097            }
1098        }
1099
1100        @Override
1101        public boolean dispatchTouchEvent(MotionEvent ev) {
1102            if (mTouchInterceptor != null && mTouchInterceptor.onTouch(this, ev)) {
1103                return true;
1104            }
1105            return super.dispatchTouchEvent(ev);
1106        }
1107
1108        @Override
1109        public boolean onTouchEvent(MotionEvent event) {
1110            final int x = (int) event.getX();
1111            final int y = (int) event.getY();
1112
1113            if ((event.getAction() == MotionEvent.ACTION_DOWN)
1114                    && ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) {
1115                dismiss();
1116                return true;
1117            } else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
1118                dismiss();
1119                return true;
1120            } else {
1121                return super.onTouchEvent(event);
1122            }
1123        }
1124
1125    }
1126
1127}
1128