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