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