1/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.widget;
18
19import com.android.internal.R;
20
21import android.content.Context;
22import android.content.res.Resources;
23import android.content.res.TypedArray;
24import android.graphics.PixelFormat;
25import android.graphics.Rect;
26import android.graphics.drawable.Drawable;
27import android.graphics.drawable.StateListDrawable;
28import android.os.Build;
29import android.os.IBinder;
30import android.util.AttributeSet;
31import android.view.Gravity;
32import android.view.KeyEvent;
33import android.view.MotionEvent;
34import android.view.View;
35import android.view.View.OnTouchListener;
36import android.view.ViewGroup;
37import android.view.ViewTreeObserver;
38import android.view.ViewTreeObserver.OnScrollChangedListener;
39import android.view.WindowManager;
40
41import java.lang.ref.WeakReference;
42
43/**
44 * <p>A popup window that can be used to display an arbitrary view. The popup
45 * window is a floating container that appears on top of the current
46 * activity.</p>
47 *
48 * @see android.widget.AutoCompleteTextView
49 * @see android.widget.Spinner
50 */
51public class PopupWindow {
52    /**
53     * Mode for {@link #setInputMethodMode(int)}: the requirements for the
54     * input method should be based on the focusability of the popup.  That is
55     * if it is focusable than it needs to work with the input method, else
56     * it doesn't.
57     */
58    public static final int INPUT_METHOD_FROM_FOCUSABLE = 0;
59
60    /**
61     * Mode for {@link #setInputMethodMode(int)}: this popup always needs to
62     * work with an input method, regardless of whether it is focusable.  This
63     * means that it will always be displayed so that the user can also operate
64     * the input method while it is shown.
65     */
66    public static final int INPUT_METHOD_NEEDED = 1;
67
68    /**
69     * Mode for {@link #setInputMethodMode(int)}: this popup never needs to
70     * work with an input method, regardless of whether it is focusable.  This
71     * means that it will always be displayed to use as much space on the
72     * screen as needed, regardless of whether this covers the input method.
73     */
74    public static final int INPUT_METHOD_NOT_NEEDED = 2;
75
76    private static final int DEFAULT_ANCHORED_GRAVITY = Gravity.TOP | Gravity.START;
77
78    private Context mContext;
79    private WindowManager mWindowManager;
80
81    private boolean mIsShowing;
82    private boolean mIsDropdown;
83
84    private View mContentView;
85    private View mPopupView;
86    private boolean mFocusable;
87    private int mInputMethodMode = INPUT_METHOD_FROM_FOCUSABLE;
88    private int mSoftInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED;
89    private boolean mTouchable = true;
90    private boolean mOutsideTouchable = false;
91    private boolean mClippingEnabled = true;
92    private int mSplitTouchEnabled = -1;
93    private boolean mLayoutInScreen;
94    private boolean mClipToScreen;
95    private boolean mAllowScrollingAnchorParent = true;
96    private boolean mLayoutInsetDecor = false;
97    private boolean mNotTouchModal;
98
99    private OnTouchListener mTouchInterceptor;
100
101    private int mWidthMode;
102    private int mWidth;
103    private int mLastWidth;
104    private int mHeightMode;
105    private int mHeight;
106    private int mLastHeight;
107
108    private int mPopupWidth;
109    private int mPopupHeight;
110
111    private int[] mDrawingLocation = new int[2];
112    private int[] mScreenLocation = new int[2];
113    private Rect mTempRect = new Rect();
114
115    private Drawable mBackground;
116    private Drawable mAboveAnchorBackgroundDrawable;
117    private Drawable mBelowAnchorBackgroundDrawable;
118
119    private boolean mAboveAnchor;
120    private int mWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
121
122    private OnDismissListener mOnDismissListener;
123    private boolean mIgnoreCheekPress = false;
124
125    private int mAnimationStyle = -1;
126
127    private static final int[] ABOVE_ANCHOR_STATE_SET = new int[] {
128        com.android.internal.R.attr.state_above_anchor
129    };
130
131    private WeakReference<View> mAnchor;
132    private OnScrollChangedListener mOnScrollChangedListener =
133        new OnScrollChangedListener() {
134            public void onScrollChanged() {
135                View anchor = mAnchor != null ? mAnchor.get() : null;
136                if (anchor != null && mPopupView != null) {
137                    WindowManager.LayoutParams p = (WindowManager.LayoutParams)
138                            mPopupView.getLayoutParams();
139
140                    updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff,
141                            mAnchoredGravity));
142                    update(p.x, p.y, -1, -1, true);
143                }
144            }
145        };
146    private int mAnchorXoff, mAnchorYoff, mAnchoredGravity;
147
148    private boolean mPopupViewInitialLayoutDirectionInherited;
149
150    /**
151     * <p>Create a new empty, non focusable popup window of dimension (0,0).</p>
152     *
153     * <p>The popup does provide a background.</p>
154     */
155    public PopupWindow(Context context) {
156        this(context, null);
157    }
158
159    /**
160     * <p>Create a new empty, non focusable popup window of dimension (0,0).</p>
161     *
162     * <p>The popup does provide a background.</p>
163     */
164    public PopupWindow(Context context, AttributeSet attrs) {
165        this(context, attrs, com.android.internal.R.attr.popupWindowStyle);
166    }
167
168    /**
169     * <p>Create a new empty, non focusable popup window of dimension (0,0).</p>
170     *
171     * <p>The popup does provide a background.</p>
172     */
173    public PopupWindow(Context context, AttributeSet attrs, int defStyle) {
174        this(context, attrs, defStyle, 0);
175    }
176
177    /**
178     * <p>Create a new, empty, non focusable popup window of dimension (0,0).</p>
179     *
180     * <p>The popup does not provide a background.</p>
181     */
182    public PopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
183        mContext = context;
184        mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
185
186        TypedArray a =
187            context.obtainStyledAttributes(
188                attrs, com.android.internal.R.styleable.PopupWindow, defStyleAttr, defStyleRes);
189
190        mBackground = a.getDrawable(R.styleable.PopupWindow_popupBackground);
191
192        final int animStyle = a.getResourceId(R.styleable.PopupWindow_popupAnimationStyle, -1);
193        mAnimationStyle = animStyle == com.android.internal.R.style.Animation_PopupWindow ? -1 :
194                animStyle;
195
196        // If this is a StateListDrawable, try to find and store the drawable to be
197        // used when the drop-down is placed above its anchor view, and the one to be
198        // used when the drop-down is placed below its anchor view. We extract
199        // the drawables ourselves to work around a problem with using refreshDrawableState
200        // that it will take into account the padding of all drawables specified in a
201        // StateListDrawable, thus adding superfluous padding to drop-down views.
202        //
203        // We assume a StateListDrawable will have a drawable for ABOVE_ANCHOR_STATE_SET and
204        // at least one other drawable, intended for the 'below-anchor state'.
205        if (mBackground instanceof StateListDrawable) {
206            StateListDrawable background = (StateListDrawable) mBackground;
207
208            // Find the above-anchor view - this one's easy, it should be labeled as such.
209            int aboveAnchorStateIndex = background.getStateDrawableIndex(ABOVE_ANCHOR_STATE_SET);
210
211            // Now, for the below-anchor view, look for any other drawable specified in the
212            // StateListDrawable which is not for the above-anchor state and use that.
213            int count = background.getStateCount();
214            int belowAnchorStateIndex = -1;
215            for (int i = 0; i < count; i++) {
216                if (i != aboveAnchorStateIndex) {
217                    belowAnchorStateIndex = i;
218                    break;
219                }
220            }
221
222            // Store the drawables we found, if we found them. Otherwise, set them both
223            // to null so that we'll just use refreshDrawableState.
224            if (aboveAnchorStateIndex != -1 && belowAnchorStateIndex != -1) {
225                mAboveAnchorBackgroundDrawable = background.getStateDrawable(aboveAnchorStateIndex);
226                mBelowAnchorBackgroundDrawable = background.getStateDrawable(belowAnchorStateIndex);
227            } else {
228                mBelowAnchorBackgroundDrawable = null;
229                mAboveAnchorBackgroundDrawable = null;
230            }
231        }
232
233        a.recycle();
234    }
235
236    /**
237     * <p>Create a new empty, non focusable popup window of dimension (0,0).</p>
238     *
239     * <p>The popup does not provide any background. This should be handled
240     * by the content view.</p>
241     */
242    public PopupWindow() {
243        this(null, 0, 0);
244    }
245
246    /**
247     * <p>Create a new non focusable popup window which can display the
248     * <tt>contentView</tt>. The dimension of the window are (0,0).</p>
249     *
250     * <p>The popup does not provide any background. This should be handled
251     * by the content view.</p>
252     *
253     * @param contentView the popup's content
254     */
255    public PopupWindow(View contentView) {
256        this(contentView, 0, 0);
257    }
258
259    /**
260     * <p>Create a new empty, non focusable popup window. The dimension of the
261     * window must be passed to this constructor.</p>
262     *
263     * <p>The popup does not provide any background. This should be handled
264     * by the content view.</p>
265     *
266     * @param width the popup's width
267     * @param height the popup's height
268     */
269    public PopupWindow(int width, int height) {
270        this(null, width, height);
271    }
272
273    /**
274     * <p>Create a new non focusable popup window which can display the
275     * <tt>contentView</tt>. The dimension of the window must be passed to
276     * this constructor.</p>
277     *
278     * <p>The popup does not provide any background. This should be handled
279     * by the content view.</p>
280     *
281     * @param contentView the popup's content
282     * @param width the popup's width
283     * @param height the popup's height
284     */
285    public PopupWindow(View contentView, int width, int height) {
286        this(contentView, width, height, false);
287    }
288
289    /**
290     * <p>Create a new popup window which can display the <tt>contentView</tt>.
291     * The dimension of the window must be passed to this constructor.</p>
292     *
293     * <p>The popup does not provide any background. This should be handled
294     * by the content view.</p>
295     *
296     * @param contentView the popup's content
297     * @param width the popup's width
298     * @param height the popup's height
299     * @param focusable true if the popup can be focused, false otherwise
300     */
301    public PopupWindow(View contentView, int width, int height, boolean focusable) {
302        if (contentView != null) {
303            mContext = contentView.getContext();
304            mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
305        }
306        setContentView(contentView);
307        setWidth(width);
308        setHeight(height);
309        setFocusable(focusable);
310    }
311
312    /**
313     * <p>Return the drawable used as the popup window's background.</p>
314     *
315     * @return the background drawable or null
316     */
317    public Drawable getBackground() {
318        return mBackground;
319    }
320
321    /**
322     * <p>Change the background drawable for this popup window. The background
323     * can be set to null.</p>
324     *
325     * @param background the popup's background
326     */
327    public void setBackgroundDrawable(Drawable background) {
328        mBackground = background;
329    }
330
331    /**
332     * <p>Return the animation style to use the popup appears and disappears</p>
333     *
334     * @return the animation style to use the popup appears and disappears
335     */
336    public int getAnimationStyle() {
337        return mAnimationStyle;
338    }
339
340    /**
341     * Set the flag on popup to ignore cheek press eventt; by default this flag
342     * is set to false
343     * which means the pop wont ignore cheek press dispatch events.
344     *
345     * <p>If the popup is showing, calling this method will take effect only
346     * the next time the popup is shown or through a manual call to one of
347     * the {@link #update()} methods.</p>
348     *
349     * @see #update()
350     */
351    public void setIgnoreCheekPress() {
352        mIgnoreCheekPress = true;
353    }
354
355
356    /**
357     * <p>Change the animation style resource for this popup.</p>
358     *
359     * <p>If the popup is showing, calling this method will take effect only
360     * the next time the popup is shown or through a manual call to one of
361     * the {@link #update()} methods.</p>
362     *
363     * @param animationStyle animation style to use when the popup appears
364     *      and disappears.  Set to -1 for the default animation, 0 for no
365     *      animation, or a resource identifier for an explicit animation.
366     *
367     * @see #update()
368     */
369    public void setAnimationStyle(int animationStyle) {
370        mAnimationStyle = animationStyle;
371    }
372
373    /**
374     * <p>Return the view used as the content of the popup window.</p>
375     *
376     * @return a {@link android.view.View} representing the popup's content
377     *
378     * @see #setContentView(android.view.View)
379     */
380    public View getContentView() {
381        return mContentView;
382    }
383
384    /**
385     * <p>Change the popup's content. The content is represented by an instance
386     * of {@link android.view.View}.</p>
387     *
388     * <p>This method has no effect if called when the popup is showing.</p>
389     *
390     * @param contentView the new content for the popup
391     *
392     * @see #getContentView()
393     * @see #isShowing()
394     */
395    public void setContentView(View contentView) {
396        if (isShowing()) {
397            return;
398        }
399
400        mContentView = contentView;
401
402        if (mContext == null && mContentView != null) {
403            mContext = mContentView.getContext();
404        }
405
406        if (mWindowManager == null && mContentView != null) {
407            mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
408        }
409    }
410
411    /**
412     * Set a callback for all touch events being dispatched to the popup
413     * window.
414     */
415    public void setTouchInterceptor(OnTouchListener l) {
416        mTouchInterceptor = l;
417    }
418
419    /**
420     * <p>Indicate whether the popup window can grab the focus.</p>
421     *
422     * @return true if the popup is focusable, false otherwise
423     *
424     * @see #setFocusable(boolean)
425     */
426    public boolean isFocusable() {
427        return mFocusable;
428    }
429
430    /**
431     * <p>Changes the focusability of the popup window. When focusable, the
432     * window will grab the focus from the current focused widget if the popup
433     * contains a focusable {@link android.view.View}.  By default a popup
434     * window is not focusable.</p>
435     *
436     * <p>If the popup is showing, calling this method will take effect only
437     * the next time the popup is shown or through a manual call to one of
438     * the {@link #update()} methods.</p>
439     *
440     * @param focusable true if the popup should grab focus, false otherwise.
441     *
442     * @see #isFocusable()
443     * @see #isShowing()
444     * @see #update()
445     */
446    public void setFocusable(boolean focusable) {
447        mFocusable = focusable;
448    }
449
450    /**
451     * Return the current value in {@link #setInputMethodMode(int)}.
452     *
453     * @see #setInputMethodMode(int)
454     */
455    public int getInputMethodMode() {
456        return mInputMethodMode;
457
458    }
459
460    /**
461     * Control how the popup operates with an input method: one of
462     * {@link #INPUT_METHOD_FROM_FOCUSABLE}, {@link #INPUT_METHOD_NEEDED},
463     * or {@link #INPUT_METHOD_NOT_NEEDED}.
464     *
465     * <p>If the popup is showing, calling this method will take effect only
466     * the next time the popup is shown or through a manual call to one of
467     * the {@link #update()} methods.</p>
468     *
469     * @see #getInputMethodMode()
470     * @see #update()
471     */
472    public void setInputMethodMode(int mode) {
473        mInputMethodMode = mode;
474    }
475
476    /**
477     * Sets the operating mode for the soft input area.
478     *
479     * @param mode The desired mode, see
480     *        {@link android.view.WindowManager.LayoutParams#softInputMode}
481     *        for the full list
482     *
483     * @see android.view.WindowManager.LayoutParams#softInputMode
484     * @see #getSoftInputMode()
485     */
486    public void setSoftInputMode(int mode) {
487        mSoftInputMode = mode;
488    }
489
490    /**
491     * Returns the current value in {@link #setSoftInputMode(int)}.
492     *
493     * @see #setSoftInputMode(int)
494     * @see android.view.WindowManager.LayoutParams#softInputMode
495     */
496    public int getSoftInputMode() {
497        return mSoftInputMode;
498    }
499
500    /**
501     * <p>Indicates whether the popup window receives touch events.</p>
502     *
503     * @return true if the popup is touchable, false otherwise
504     *
505     * @see #setTouchable(boolean)
506     */
507    public boolean isTouchable() {
508        return mTouchable;
509    }
510
511    /**
512     * <p>Changes the touchability of the popup window. When touchable, the
513     * window will receive touch events, otherwise touch events will go to the
514     * window below it. By default the window is touchable.</p>
515     *
516     * <p>If the popup is showing, calling this method will take effect only
517     * the next time the popup is shown or through a manual call to one of
518     * the {@link #update()} methods.</p>
519     *
520     * @param touchable true if the popup should receive touch events, false otherwise
521     *
522     * @see #isTouchable()
523     * @see #isShowing()
524     * @see #update()
525     */
526    public void setTouchable(boolean touchable) {
527        mTouchable = touchable;
528    }
529
530    /**
531     * <p>Indicates whether the popup window will be informed of touch events
532     * outside of its window.</p>
533     *
534     * @return true if the popup is outside touchable, false otherwise
535     *
536     * @see #setOutsideTouchable(boolean)
537     */
538    public boolean isOutsideTouchable() {
539        return mOutsideTouchable;
540    }
541
542    /**
543     * <p>Controls whether the pop-up will be informed of touch events outside
544     * of its window.  This only makes sense for pop-ups that are touchable
545     * but not focusable, which means touches outside of the window will
546     * be delivered to the window behind.  The default is false.</p>
547     *
548     * <p>If the popup is showing, calling this method will take effect only
549     * the next time the popup is shown or through a manual call to one of
550     * the {@link #update()} methods.</p>
551     *
552     * @param touchable true if the popup should receive outside
553     * touch events, false otherwise
554     *
555     * @see #isOutsideTouchable()
556     * @see #isShowing()
557     * @see #update()
558     */
559    public void setOutsideTouchable(boolean touchable) {
560        mOutsideTouchable = touchable;
561    }
562
563    /**
564     * <p>Indicates whether clipping of the popup window is enabled.</p>
565     *
566     * @return true if the clipping is enabled, false otherwise
567     *
568     * @see #setClippingEnabled(boolean)
569     */
570    public boolean isClippingEnabled() {
571        return mClippingEnabled;
572    }
573
574    /**
575     * <p>Allows the popup window to extend beyond the bounds of the screen. By default the
576     * window is clipped to the screen boundaries. Setting this to false will allow windows to be
577     * accurately positioned.</p>
578     *
579     * <p>If the popup is showing, calling this method will take effect only
580     * the next time the popup is shown or through a manual call to one of
581     * the {@link #update()} methods.</p>
582     *
583     * @param enabled false if the window should be allowed to extend outside of the screen
584     * @see #isShowing()
585     * @see #isClippingEnabled()
586     * @see #update()
587     */
588    public void setClippingEnabled(boolean enabled) {
589        mClippingEnabled = enabled;
590    }
591
592    /**
593     * Clip this popup window to the screen, but not to the containing window.
594     *
595     * @param enabled True to clip to the screen.
596     * @hide
597     */
598    public void setClipToScreenEnabled(boolean enabled) {
599        mClipToScreen = enabled;
600        setClippingEnabled(!enabled);
601    }
602
603    /**
604     * Allow PopupWindow to scroll the anchor's parent to provide more room
605     * for the popup. Enabled by default.
606     *
607     * @param enabled True to scroll the anchor's parent when more room is desired by the popup.
608     */
609    void setAllowScrollingAnchorParent(boolean enabled) {
610        mAllowScrollingAnchorParent = enabled;
611    }
612
613    /**
614     * <p>Indicates whether the popup window supports splitting touches.</p>
615     *
616     * @return true if the touch splitting is enabled, false otherwise
617     *
618     * @see #setSplitTouchEnabled(boolean)
619     */
620    public boolean isSplitTouchEnabled() {
621        if (mSplitTouchEnabled < 0 && mContext != null) {
622            return mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB;
623        }
624        return mSplitTouchEnabled == 1;
625    }
626
627    /**
628     * <p>Allows the popup window to split touches across other windows that also
629     * support split touch.  When this flag is false, the first pointer
630     * that goes down determines the window to which all subsequent touches
631     * go until all pointers go up.  When this flag is true, each pointer
632     * (not necessarily the first) that goes down determines the window
633     * to which all subsequent touches of that pointer will go until that
634     * pointer goes up thereby enabling touches with multiple pointers
635     * to be split across multiple windows.</p>
636     *
637     * @param enabled true if the split touches should be enabled, false otherwise
638     * @see #isSplitTouchEnabled()
639     */
640    public void setSplitTouchEnabled(boolean enabled) {
641        mSplitTouchEnabled = enabled ? 1 : 0;
642    }
643
644    /**
645     * <p>Indicates whether the popup window will be forced into using absolute screen coordinates
646     * for positioning.</p>
647     *
648     * @return true if the window will always be positioned in screen coordinates.
649     * @hide
650     */
651    public boolean isLayoutInScreenEnabled() {
652        return mLayoutInScreen;
653    }
654
655    /**
656     * <p>Allows the popup window to force the flag
657     * {@link WindowManager.LayoutParams#FLAG_LAYOUT_IN_SCREEN}, overriding default behavior.
658     * This will cause the popup to be positioned in absolute screen coordinates.</p>
659     *
660     * @param enabled true if the popup should always be positioned in screen coordinates
661     * @hide
662     */
663    public void setLayoutInScreenEnabled(boolean enabled) {
664        mLayoutInScreen = enabled;
665    }
666
667    /**
668     * Allows the popup window to force the flag
669     * {@link WindowManager.LayoutParams#FLAG_LAYOUT_INSET_DECOR}, overriding default behavior.
670     * This will cause the popup to inset its content to account for system windows overlaying
671     * the screen, such as the status bar.
672     *
673     * <p>This will often be combined with {@link #setLayoutInScreenEnabled(boolean)}.
674     *
675     * @param enabled true if the popup's views should inset content to account for system windows,
676     *                the way that decor views behave for full-screen windows.
677     * @hide
678     */
679    public void setLayoutInsetDecor(boolean enabled) {
680        mLayoutInsetDecor = enabled;
681    }
682
683    /**
684     * Set the layout type for this window. Should be one of the TYPE constants defined in
685     * {@link WindowManager.LayoutParams}.
686     *
687     * @param layoutType Layout type for this window.
688     * @hide
689     */
690    public void setWindowLayoutType(int layoutType) {
691        mWindowLayoutType = layoutType;
692    }
693
694    /**
695     * @return The layout type for this window.
696     * @hide
697     */
698    public int getWindowLayoutType() {
699        return mWindowLayoutType;
700    }
701
702    /**
703     * Set whether this window is touch modal or if outside touches will be sent to
704     * other windows behind it.
705     * @hide
706     */
707    public void setTouchModal(boolean touchModal) {
708        mNotTouchModal = !touchModal;
709    }
710
711    /**
712     * <p>Change the width and height measure specs that are given to the
713     * window manager by the popup.  By default these are 0, meaning that
714     * the current width or height is requested as an explicit size from
715     * the window manager.  You can supply
716     * {@link ViewGroup.LayoutParams#WRAP_CONTENT} or
717     * {@link ViewGroup.LayoutParams#MATCH_PARENT} to have that measure
718     * spec supplied instead, replacing the absolute width and height that
719     * has been set in the popup.</p>
720     *
721     * <p>If the popup is showing, calling this method will take effect only
722     * the next time the popup is shown.</p>
723     *
724     * @param widthSpec an explicit width measure spec mode, either
725     * {@link ViewGroup.LayoutParams#WRAP_CONTENT},
726     * {@link ViewGroup.LayoutParams#MATCH_PARENT}, or 0 to use the absolute
727     * width.
728     * @param heightSpec an explicit height measure spec mode, either
729     * {@link ViewGroup.LayoutParams#WRAP_CONTENT},
730     * {@link ViewGroup.LayoutParams#MATCH_PARENT}, or 0 to use the absolute
731     * height.
732     */
733    public void setWindowLayoutMode(int widthSpec, int heightSpec) {
734        mWidthMode = widthSpec;
735        mHeightMode = heightSpec;
736    }
737
738    /**
739     * <p>Return this popup's height MeasureSpec</p>
740     *
741     * @return the height MeasureSpec of the popup
742     *
743     * @see #setHeight(int)
744     */
745    public int getHeight() {
746        return mHeight;
747    }
748
749    /**
750     * <p>Change the popup's height MeasureSpec</p>
751     *
752     * <p>If the popup is showing, calling this method will take effect only
753     * the next time the popup is shown.</p>
754     *
755     * @param height the height MeasureSpec of the popup
756     *
757     * @see #getHeight()
758     * @see #isShowing()
759     */
760    public void setHeight(int height) {
761        mHeight = height;
762    }
763
764    /**
765     * <p>Return this popup's width MeasureSpec</p>
766     *
767     * @return the width MeasureSpec of the popup
768     *
769     * @see #setWidth(int)
770     */
771    public int getWidth() {
772        return mWidth;
773    }
774
775    /**
776     * <p>Change the popup's width MeasureSpec</p>
777     *
778     * <p>If the popup is showing, calling this method will take effect only
779     * the next time the popup is shown.</p>
780     *
781     * @param width the width MeasureSpec of the popup
782     *
783     * @see #getWidth()
784     * @see #isShowing()
785     */
786    public void setWidth(int width) {
787        mWidth = width;
788    }
789
790    /**
791     * <p>Indicate whether this popup window is showing on screen.</p>
792     *
793     * @return true if the popup is showing, false otherwise
794     */
795    public boolean isShowing() {
796        return mIsShowing;
797    }
798
799    /**
800     * <p>
801     * Display the content view in a popup window at the specified location. If the popup window
802     * cannot fit on screen, it will be clipped. See {@link android.view.WindowManager.LayoutParams}
803     * for more information on how gravity and the x and y parameters are related. Specifying
804     * a gravity of {@link android.view.Gravity#NO_GRAVITY} is similar to specifying
805     * <code>Gravity.LEFT | Gravity.TOP</code>.
806     * </p>
807     *
808     * @param parent a parent view to get the {@link android.view.View#getWindowToken()} token from
809     * @param gravity the gravity which controls the placement of the popup window
810     * @param x the popup's x location offset
811     * @param y the popup's y location offset
812     */
813    public void showAtLocation(View parent, int gravity, int x, int y) {
814        showAtLocation(parent.getWindowToken(), gravity, x, y);
815    }
816
817    /**
818     * Display the content view in a popup window at the specified location.
819     *
820     * @param token Window token to use for creating the new window
821     * @param gravity the gravity which controls the placement of the popup window
822     * @param x the popup's x location offset
823     * @param y the popup's y location offset
824     *
825     * @hide Internal use only. Applications should use
826     *       {@link #showAtLocation(View, int, int, int)} instead.
827     */
828    public void showAtLocation(IBinder token, int gravity, int x, int y) {
829        if (isShowing() || mContentView == null) {
830            return;
831        }
832
833        unregisterForScrollChanged();
834
835        mIsShowing = true;
836        mIsDropdown = false;
837
838        WindowManager.LayoutParams p = createPopupLayout(token);
839        p.windowAnimations = computeAnimationResource();
840
841        preparePopup(p);
842        if (gravity == Gravity.NO_GRAVITY) {
843            gravity = Gravity.TOP | Gravity.START;
844        }
845        p.gravity = gravity;
846        p.x = x;
847        p.y = y;
848        if (mHeightMode < 0) p.height = mLastHeight = mHeightMode;
849        if (mWidthMode < 0) p.width = mLastWidth = mWidthMode;
850        invokePopup(p);
851    }
852
853    /**
854     * <p>Display the content view in a popup window anchored to the bottom-left
855     * corner of the anchor view. If there is not enough room on screen to show
856     * the popup in its entirety, this method tries to find a parent scroll
857     * view to scroll. If no parent scroll view can be scrolled, the bottom-left
858     * corner of the popup is pinned at the top left corner of the anchor view.</p>
859     *
860     * @param anchor the view on which to pin the popup window
861     *
862     * @see #dismiss()
863     */
864    public void showAsDropDown(View anchor) {
865        showAsDropDown(anchor, 0, 0);
866    }
867
868    /**
869     * <p>Display the content view in a popup window anchored to the bottom-left
870     * corner of the anchor view offset by the specified x and y coordinates.
871     * If there is not enough room on screen to show
872     * the popup in its entirety, this method tries to find a parent scroll
873     * view to scroll. If no parent scroll view can be scrolled, the bottom-left
874     * corner of the popup is pinned at the top left corner of the anchor view.</p>
875     * <p>If the view later scrolls to move <code>anchor</code> to a different
876     * location, the popup will be moved correspondingly.</p>
877     *
878     * @param anchor the view on which to pin the popup window
879     * @param xoff A horizontal offset from the anchor in pixels
880     * @param yoff A vertical offset from the anchor in pixels
881     *
882     * @see #dismiss()
883     */
884    public void showAsDropDown(View anchor, int xoff, int yoff) {
885        showAsDropDown(anchor, xoff, yoff, DEFAULT_ANCHORED_GRAVITY);
886    }
887
888    /**
889     * <p>Display the content view in a popup window anchored to the bottom-left
890     * corner of the anchor view offset by the specified x and y coordinates.
891     * If there is not enough room on screen to show
892     * the popup in its entirety, this method tries to find a parent scroll
893     * view to scroll. If no parent scroll view can be scrolled, the bottom-left
894     * corner of the popup is pinned at the top left corner of the anchor view.</p>
895     * <p>If the view later scrolls to move <code>anchor</code> to a different
896     * location, the popup will be moved correspondingly.</p>
897     *
898     * @param anchor the view on which to pin the popup window
899     * @param xoff A horizontal offset from the anchor in pixels
900     * @param yoff A vertical offset from the anchor in pixels
901     * @param gravity Alignment of the popup relative to the anchor
902     *
903     * @see #dismiss()
904     */
905    public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
906        if (isShowing() || mContentView == null) {
907            return;
908        }
909
910        registerForScrollChanged(anchor, xoff, yoff, gravity);
911
912        mIsShowing = true;
913        mIsDropdown = true;
914
915        WindowManager.LayoutParams p = createPopupLayout(anchor.getWindowToken());
916        preparePopup(p);
917
918        updateAboveAnchor(findDropDownPosition(anchor, p, xoff, yoff, gravity));
919
920        if (mHeightMode < 0) p.height = mLastHeight = mHeightMode;
921        if (mWidthMode < 0) p.width = mLastWidth = mWidthMode;
922
923        p.windowAnimations = computeAnimationResource();
924
925        invokePopup(p);
926    }
927
928    private void updateAboveAnchor(boolean aboveAnchor) {
929        if (aboveAnchor != mAboveAnchor) {
930            mAboveAnchor = aboveAnchor;
931
932            if (mBackground != null) {
933                // If the background drawable provided was a StateListDrawable with above-anchor
934                // and below-anchor states, use those. Otherwise rely on refreshDrawableState to
935                // do the job.
936                if (mAboveAnchorBackgroundDrawable != null) {
937                    if (mAboveAnchor) {
938                        mPopupView.setBackgroundDrawable(mAboveAnchorBackgroundDrawable);
939                    } else {
940                        mPopupView.setBackgroundDrawable(mBelowAnchorBackgroundDrawable);
941                    }
942                } else {
943                    mPopupView.refreshDrawableState();
944                }
945            }
946        }
947    }
948
949    /**
950     * Indicates whether the popup is showing above (the y coordinate of the popup's bottom
951     * is less than the y coordinate of the anchor) or below the anchor view (the y coordinate
952     * of the popup is greater than y coordinate of the anchor's bottom).
953     *
954     * The value returned
955     * by this method is meaningful only after {@link #showAsDropDown(android.view.View)}
956     * or {@link #showAsDropDown(android.view.View, int, int)} was invoked.
957     *
958     * @return True if this popup is showing above the anchor view, false otherwise.
959     */
960    public boolean isAboveAnchor() {
961        return mAboveAnchor;
962    }
963
964    /**
965     * <p>Prepare the popup by embedding in into a new ViewGroup if the
966     * background drawable is not null. If embedding is required, the layout
967     * parameters' height is mnodified to take into account the background's
968     * padding.</p>
969     *
970     * @param p the layout parameters of the popup's content view
971     */
972    private void preparePopup(WindowManager.LayoutParams p) {
973        if (mContentView == null || mContext == null || mWindowManager == null) {
974            throw new IllegalStateException("You must specify a valid content view by "
975                    + "calling setContentView() before attempting to show the popup.");
976        }
977
978        if (mBackground != null) {
979            final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();
980            int height = ViewGroup.LayoutParams.MATCH_PARENT;
981            if (layoutParams != null &&
982                    layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
983                height = ViewGroup.LayoutParams.WRAP_CONTENT;
984            }
985
986            // when a background is available, we embed the content view
987            // within another view that owns the background drawable
988            PopupViewContainer popupViewContainer = new PopupViewContainer(mContext);
989            PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams(
990                    ViewGroup.LayoutParams.MATCH_PARENT, height
991            );
992            popupViewContainer.setBackgroundDrawable(mBackground);
993            popupViewContainer.addView(mContentView, listParams);
994
995            mPopupView = popupViewContainer;
996        } else {
997            mPopupView = mContentView;
998        }
999        mPopupViewInitialLayoutDirectionInherited =
1000                (mPopupView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT);
1001        mPopupWidth = p.width;
1002        mPopupHeight = p.height;
1003    }
1004
1005    /**
1006     * <p>Invoke the popup window by adding the content view to the window
1007     * manager.</p>
1008     *
1009     * <p>The content view must be non-null when this method is invoked.</p>
1010     *
1011     * @param p the layout parameters of the popup's content view
1012     */
1013    private void invokePopup(WindowManager.LayoutParams p) {
1014        if (mContext != null) {
1015            p.packageName = mContext.getPackageName();
1016        }
1017        mPopupView.setFitsSystemWindows(mLayoutInsetDecor);
1018        setLayoutDirectionFromAnchor();
1019        mWindowManager.addView(mPopupView, p);
1020    }
1021
1022    private void setLayoutDirectionFromAnchor() {
1023        if (mAnchor != null) {
1024            View anchor = mAnchor.get();
1025            if (anchor != null && mPopupViewInitialLayoutDirectionInherited) {
1026                mPopupView.setLayoutDirection(anchor.getLayoutDirection());
1027            }
1028        }
1029    }
1030
1031    /**
1032     * <p>Generate the layout parameters for the popup window.</p>
1033     *
1034     * @param token the window token used to bind the popup's window
1035     *
1036     * @return the layout parameters to pass to the window manager
1037     */
1038    private WindowManager.LayoutParams createPopupLayout(IBinder token) {
1039        // generates the layout parameters for the drop down
1040        // we want a fixed size view located at the bottom left of the anchor
1041        WindowManager.LayoutParams p = new WindowManager.LayoutParams();
1042        // these gravity settings put the view at the top left corner of the
1043        // screen. The view is then positioned to the appropriate location
1044        // by setting the x and y offsets to match the anchor's bottom
1045        // left corner
1046        p.gravity = Gravity.START | Gravity.TOP;
1047        p.width = mLastWidth = mWidth;
1048        p.height = mLastHeight = mHeight;
1049        if (mBackground != null) {
1050            p.format = mBackground.getOpacity();
1051        } else {
1052            p.format = PixelFormat.TRANSLUCENT;
1053        }
1054        p.flags = computeFlags(p.flags);
1055        p.type = mWindowLayoutType;
1056        p.token = token;
1057        p.softInputMode = mSoftInputMode;
1058        p.setTitle("PopupWindow:" + Integer.toHexString(hashCode()));
1059
1060        return p;
1061    }
1062
1063    private int computeFlags(int curFlags) {
1064        curFlags &= ~(
1065                WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES |
1066                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
1067                WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE |
1068                WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH |
1069                WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS |
1070                WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM |
1071                WindowManager.LayoutParams.FLAG_SPLIT_TOUCH);
1072        if(mIgnoreCheekPress) {
1073            curFlags |= WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
1074        }
1075        if (!mFocusable) {
1076            curFlags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
1077            if (mInputMethodMode == INPUT_METHOD_NEEDED) {
1078                curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
1079            }
1080        } else if (mInputMethodMode == INPUT_METHOD_NOT_NEEDED) {
1081            curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
1082        }
1083        if (!mTouchable) {
1084            curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
1085        }
1086        if (mOutsideTouchable) {
1087            curFlags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
1088        }
1089        if (!mClippingEnabled) {
1090            curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
1091        }
1092        if (isSplitTouchEnabled()) {
1093            curFlags |= WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
1094        }
1095        if (mLayoutInScreen) {
1096            curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
1097        }
1098        if (mLayoutInsetDecor) {
1099            curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
1100        }
1101        if (mNotTouchModal) {
1102            curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
1103        }
1104        return curFlags;
1105    }
1106
1107    private int computeAnimationResource() {
1108        if (mAnimationStyle == -1) {
1109            if (mIsDropdown) {
1110                return mAboveAnchor
1111                        ? com.android.internal.R.style.Animation_DropDownUp
1112                        : com.android.internal.R.style.Animation_DropDownDown;
1113            }
1114            return 0;
1115        }
1116        return mAnimationStyle;
1117    }
1118
1119    /**
1120     * <p>Positions the popup window on screen. When the popup window is too
1121     * tall to fit under the anchor, a parent scroll view is seeked and scrolled
1122     * up to reclaim space. If scrolling is not possible or not enough, the
1123     * popup window gets moved on top of the anchor.</p>
1124     *
1125     * <p>The height must have been set on the layout parameters prior to
1126     * calling this method.</p>
1127     *
1128     * @param anchor the view on which the popup window must be anchored
1129     * @param p the layout parameters used to display the drop down
1130     *
1131     * @return true if the popup is translated upwards to fit on screen
1132     */
1133    private boolean findDropDownPosition(View anchor, WindowManager.LayoutParams p,
1134            int xoff, int yoff, int gravity) {
1135
1136        final int anchorHeight = anchor.getHeight();
1137        anchor.getLocationInWindow(mDrawingLocation);
1138        p.x = mDrawingLocation[0] + xoff;
1139        p.y = mDrawingLocation[1] + anchorHeight + yoff;
1140
1141        final int hgrav = Gravity.getAbsoluteGravity(gravity, anchor.getLayoutDirection()) &
1142                Gravity.HORIZONTAL_GRAVITY_MASK;
1143        if (hgrav == Gravity.RIGHT) {
1144            // Flip the location to align the right sides of the popup and anchor instead of left
1145            p.x -= mPopupWidth - anchor.getWidth();
1146        }
1147
1148        boolean onTop = false;
1149
1150        p.gravity = Gravity.LEFT | Gravity.TOP;
1151
1152        anchor.getLocationOnScreen(mScreenLocation);
1153        final Rect displayFrame = new Rect();
1154        anchor.getWindowVisibleDisplayFrame(displayFrame);
1155
1156        int screenY = mScreenLocation[1] + anchorHeight + yoff;
1157
1158        final View root = anchor.getRootView();
1159        if (screenY + mPopupHeight > displayFrame.bottom ||
1160                p.x + mPopupWidth - root.getWidth() > 0) {
1161            // if the drop down disappears at the bottom of the screen. we try to
1162            // scroll a parent scrollview or move the drop down back up on top of
1163            // the edit box
1164            if (mAllowScrollingAnchorParent) {
1165                int scrollX = anchor.getScrollX();
1166                int scrollY = anchor.getScrollY();
1167                Rect r = new Rect(scrollX, scrollY,  scrollX + mPopupWidth + xoff,
1168                        scrollY + mPopupHeight + anchor.getHeight() + yoff);
1169                anchor.requestRectangleOnScreen(r, true);
1170            }
1171
1172            // now we re-evaluate the space available, and decide from that
1173            // whether the pop-up will go above or below the anchor.
1174            anchor.getLocationInWindow(mDrawingLocation);
1175            p.x = mDrawingLocation[0] + xoff;
1176            p.y = mDrawingLocation[1] + anchor.getHeight() + yoff;
1177
1178            // Preserve the gravity adjustment
1179            if (hgrav == Gravity.RIGHT) {
1180                p.x -= mPopupWidth - anchor.getWidth();
1181            }
1182
1183            // determine whether there is more space above or below the anchor
1184            anchor.getLocationOnScreen(mScreenLocation);
1185
1186            onTop = (displayFrame.bottom - mScreenLocation[1] - anchor.getHeight() - yoff) <
1187                    (mScreenLocation[1] - yoff - displayFrame.top);
1188            if (onTop) {
1189                p.gravity = Gravity.LEFT | Gravity.BOTTOM;
1190                p.y = root.getHeight() - mDrawingLocation[1] + yoff;
1191            } else {
1192                p.y = mDrawingLocation[1] + anchor.getHeight() + yoff;
1193            }
1194        }
1195
1196        if (mClipToScreen) {
1197            final int displayFrameWidth = displayFrame.right - displayFrame.left;
1198
1199            int right = p.x + p.width;
1200            if (right > displayFrameWidth) {
1201                p.x -= right - displayFrameWidth;
1202            }
1203            if (p.x < displayFrame.left) {
1204                p.x = displayFrame.left;
1205                p.width = Math.min(p.width, displayFrameWidth);
1206            }
1207
1208            if (onTop) {
1209                int popupTop = mScreenLocation[1] + yoff - mPopupHeight;
1210                if (popupTop < 0) {
1211                    p.y += popupTop;
1212                }
1213            } else {
1214                p.y = Math.max(p.y, displayFrame.top);
1215            }
1216        }
1217
1218        p.gravity |= Gravity.DISPLAY_CLIP_VERTICAL;
1219
1220        return onTop;
1221    }
1222
1223    /**
1224     * Returns the maximum height that is available for the popup to be
1225     * completely shown. It is recommended that this height be the maximum for
1226     * the popup's height, otherwise it is possible that the popup will be
1227     * clipped.
1228     *
1229     * @param anchor The view on which the popup window must be anchored.
1230     * @return The maximum available height for the popup to be completely
1231     *         shown.
1232     */
1233    public int getMaxAvailableHeight(View anchor) {
1234        return getMaxAvailableHeight(anchor, 0);
1235    }
1236
1237    /**
1238     * Returns the maximum height that is available for the popup to be
1239     * completely shown. It is recommended that this height be the maximum for
1240     * the popup's height, otherwise it is possible that the popup will be
1241     * clipped.
1242     *
1243     * @param anchor The view on which the popup window must be anchored.
1244     * @param yOffset y offset from the view's bottom edge
1245     * @return The maximum available height for the popup to be completely
1246     *         shown.
1247     */
1248    public int getMaxAvailableHeight(View anchor, int yOffset) {
1249        return getMaxAvailableHeight(anchor, yOffset, false);
1250    }
1251
1252    /**
1253     * Returns the maximum height that is available for the popup to be
1254     * completely shown, optionally ignoring any bottom decorations such as
1255     * the input method. It is recommended that this height be the maximum for
1256     * the popup's height, otherwise it is possible that the popup will be
1257     * clipped.
1258     *
1259     * @param anchor The view on which the popup window must be anchored.
1260     * @param yOffset y offset from the view's bottom edge
1261     * @param ignoreBottomDecorations if true, the height returned will be
1262     *        all the way to the bottom of the display, ignoring any
1263     *        bottom decorations
1264     * @return The maximum available height for the popup to be completely
1265     *         shown.
1266     *
1267     * @hide Pending API council approval.
1268     */
1269    public int getMaxAvailableHeight(View anchor, int yOffset, boolean ignoreBottomDecorations) {
1270        final Rect displayFrame = new Rect();
1271        anchor.getWindowVisibleDisplayFrame(displayFrame);
1272
1273        final int[] anchorPos = mDrawingLocation;
1274        anchor.getLocationOnScreen(anchorPos);
1275
1276        int bottomEdge = displayFrame.bottom;
1277        if (ignoreBottomDecorations) {
1278            Resources res = anchor.getContext().getResources();
1279            bottomEdge = res.getDisplayMetrics().heightPixels;
1280        }
1281        final int distanceToBottom = bottomEdge - (anchorPos[1] + anchor.getHeight()) - yOffset;
1282        final int distanceToTop = anchorPos[1] - displayFrame.top + yOffset;
1283
1284        // anchorPos[1] is distance from anchor to top of screen
1285        int returnedHeight = Math.max(distanceToBottom, distanceToTop);
1286        if (mBackground != null) {
1287            mBackground.getPadding(mTempRect);
1288            returnedHeight -= mTempRect.top + mTempRect.bottom;
1289        }
1290
1291        return returnedHeight;
1292    }
1293
1294    /**
1295     * <p>Dispose of the popup window. This method can be invoked only after
1296     * {@link #showAsDropDown(android.view.View)} has been executed. Failing that, calling
1297     * this method will have no effect.</p>
1298     *
1299     * @see #showAsDropDown(android.view.View)
1300     */
1301    public void dismiss() {
1302        if (isShowing() && mPopupView != null) {
1303            mIsShowing = false;
1304
1305            unregisterForScrollChanged();
1306
1307            try {
1308                mWindowManager.removeViewImmediate(mPopupView);
1309            } finally {
1310                if (mPopupView != mContentView && mPopupView instanceof ViewGroup) {
1311                    ((ViewGroup) mPopupView).removeView(mContentView);
1312                }
1313                mPopupView = null;
1314
1315                if (mOnDismissListener != null) {
1316                    mOnDismissListener.onDismiss();
1317                }
1318            }
1319        }
1320    }
1321
1322    /**
1323     * Sets the listener to be called when the window is dismissed.
1324     *
1325     * @param onDismissListener The listener.
1326     */
1327    public void setOnDismissListener(OnDismissListener onDismissListener) {
1328        mOnDismissListener = onDismissListener;
1329    }
1330
1331    /**
1332     * Updates the state of the popup window, if it is currently being displayed,
1333     * from the currently set state.  This include:
1334     * {@link #setClippingEnabled(boolean)}, {@link #setFocusable(boolean)},
1335     * {@link #setIgnoreCheekPress()}, {@link #setInputMethodMode(int)},
1336     * {@link #setTouchable(boolean)}, and {@link #setAnimationStyle(int)}.
1337     */
1338    public void update() {
1339        if (!isShowing() || mContentView == null) {
1340            return;
1341        }
1342
1343        WindowManager.LayoutParams p = (WindowManager.LayoutParams)
1344                mPopupView.getLayoutParams();
1345
1346        boolean update = false;
1347
1348        final int newAnim = computeAnimationResource();
1349        if (newAnim != p.windowAnimations) {
1350            p.windowAnimations = newAnim;
1351            update = true;
1352        }
1353
1354        final int newFlags = computeFlags(p.flags);
1355        if (newFlags != p.flags) {
1356            p.flags = newFlags;
1357            update = true;
1358        }
1359
1360        if (update) {
1361            setLayoutDirectionFromAnchor();
1362            mWindowManager.updateViewLayout(mPopupView, p);
1363        }
1364    }
1365
1366    /**
1367     * <p>Updates the dimension of the popup window. Calling this function
1368     * also updates the window with the current popup state as described
1369     * for {@link #update()}.</p>
1370     *
1371     * @param width the new width
1372     * @param height the new height
1373     */
1374    public void update(int width, int height) {
1375        WindowManager.LayoutParams p = (WindowManager.LayoutParams)
1376                mPopupView.getLayoutParams();
1377        update(p.x, p.y, width, height, false);
1378    }
1379
1380    /**
1381     * <p>Updates the position and the dimension of the popup window. Width and
1382     * height can be set to -1 to update location only.  Calling this function
1383     * also updates the window with the current popup state as
1384     * described for {@link #update()}.</p>
1385     *
1386     * @param x the new x location
1387     * @param y the new y location
1388     * @param width the new width, can be -1 to ignore
1389     * @param height the new height, can be -1 to ignore
1390     */
1391    public void update(int x, int y, int width, int height) {
1392        update(x, y, width, height, false);
1393    }
1394
1395    /**
1396     * <p>Updates the position and the dimension of the popup window. Width and
1397     * height can be set to -1 to update location only.  Calling this function
1398     * also updates the window with the current popup state as
1399     * described for {@link #update()}.</p>
1400     *
1401     * @param x the new x location
1402     * @param y the new y location
1403     * @param width the new width, can be -1 to ignore
1404     * @param height the new height, can be -1 to ignore
1405     * @param force reposition the window even if the specified position
1406     *              already seems to correspond to the LayoutParams
1407     */
1408    public void update(int x, int y, int width, int height, boolean force) {
1409        if (width != -1) {
1410            mLastWidth = width;
1411            setWidth(width);
1412        }
1413
1414        if (height != -1) {
1415            mLastHeight = height;
1416            setHeight(height);
1417        }
1418
1419        if (!isShowing() || mContentView == null) {
1420            return;
1421        }
1422
1423        WindowManager.LayoutParams p = (WindowManager.LayoutParams) mPopupView.getLayoutParams();
1424
1425        boolean update = force;
1426
1427        final int finalWidth = mWidthMode < 0 ? mWidthMode : mLastWidth;
1428        if (width != -1 && p.width != finalWidth) {
1429            p.width = mLastWidth = finalWidth;
1430            update = true;
1431        }
1432
1433        final int finalHeight = mHeightMode < 0 ? mHeightMode : mLastHeight;
1434        if (height != -1 && p.height != finalHeight) {
1435            p.height = mLastHeight = finalHeight;
1436            update = true;
1437        }
1438
1439        if (p.x != x) {
1440            p.x = x;
1441            update = true;
1442        }
1443
1444        if (p.y != y) {
1445            p.y = y;
1446            update = true;
1447        }
1448
1449        final int newAnim = computeAnimationResource();
1450        if (newAnim != p.windowAnimations) {
1451            p.windowAnimations = newAnim;
1452            update = true;
1453        }
1454
1455        final int newFlags = computeFlags(p.flags);
1456        if (newFlags != p.flags) {
1457            p.flags = newFlags;
1458            update = true;
1459        }
1460
1461        if (update) {
1462            setLayoutDirectionFromAnchor();
1463            mWindowManager.updateViewLayout(mPopupView, p);
1464        }
1465    }
1466
1467    /**
1468     * <p>Updates the position and the dimension of the popup window. Calling this
1469     * function also updates the window with the current popup state as described
1470     * for {@link #update()}.</p>
1471     *
1472     * @param anchor the popup's anchor view
1473     * @param width the new width, can be -1 to ignore
1474     * @param height the new height, can be -1 to ignore
1475     */
1476    public void update(View anchor, int width, int height) {
1477        update(anchor, false, 0, 0, true, width, height, mAnchoredGravity);
1478    }
1479
1480    /**
1481     * <p>Updates the position and the dimension of the popup window. Width and
1482     * height can be set to -1 to update location only.  Calling this function
1483     * also updates the window with the current popup state as
1484     * described for {@link #update()}.</p>
1485     *
1486     * <p>If the view later scrolls to move <code>anchor</code> to a different
1487     * location, the popup will be moved correspondingly.</p>
1488     *
1489     * @param anchor the popup's anchor view
1490     * @param xoff x offset from the view's left edge
1491     * @param yoff y offset from the view's bottom edge
1492     * @param width the new width, can be -1 to ignore
1493     * @param height the new height, can be -1 to ignore
1494     */
1495    public void update(View anchor, int xoff, int yoff, int width, int height) {
1496        update(anchor, true, xoff, yoff, true, width, height, mAnchoredGravity);
1497    }
1498
1499    private void update(View anchor, boolean updateLocation, int xoff, int yoff,
1500            boolean updateDimension, int width, int height, int gravity) {
1501
1502        if (!isShowing() || mContentView == null) {
1503            return;
1504        }
1505
1506        WeakReference<View> oldAnchor = mAnchor;
1507        final boolean needsUpdate = updateLocation && (mAnchorXoff != xoff || mAnchorYoff != yoff);
1508        if (oldAnchor == null || oldAnchor.get() != anchor || (needsUpdate && !mIsDropdown)) {
1509            registerForScrollChanged(anchor, xoff, yoff, gravity);
1510        } else if (needsUpdate) {
1511            // No need to register again if this is a DropDown, showAsDropDown already did.
1512            mAnchorXoff = xoff;
1513            mAnchorYoff = yoff;
1514            mAnchoredGravity = gravity;
1515        }
1516
1517        WindowManager.LayoutParams p = (WindowManager.LayoutParams) mPopupView.getLayoutParams();
1518
1519        if (updateDimension) {
1520            if (width == -1) {
1521                width = mPopupWidth;
1522            } else {
1523                mPopupWidth = width;
1524            }
1525            if (height == -1) {
1526                height = mPopupHeight;
1527            } else {
1528                mPopupHeight = height;
1529            }
1530        }
1531
1532        int x = p.x;
1533        int y = p.y;
1534
1535        if (updateLocation) {
1536            updateAboveAnchor(findDropDownPosition(anchor, p, xoff, yoff, gravity));
1537        } else {
1538            updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff,
1539                    mAnchoredGravity));
1540        }
1541
1542        update(p.x, p.y, width, height, x != p.x || y != p.y);
1543    }
1544
1545    /**
1546     * Listener that is called when this popup window is dismissed.
1547     */
1548    public interface OnDismissListener {
1549        /**
1550         * Called when this popup window is dismissed.
1551         */
1552        public void onDismiss();
1553    }
1554
1555    private void unregisterForScrollChanged() {
1556        WeakReference<View> anchorRef = mAnchor;
1557        View anchor = null;
1558        if (anchorRef != null) {
1559            anchor = anchorRef.get();
1560        }
1561        if (anchor != null) {
1562            ViewTreeObserver vto = anchor.getViewTreeObserver();
1563            vto.removeOnScrollChangedListener(mOnScrollChangedListener);
1564        }
1565        mAnchor = null;
1566    }
1567
1568    private void registerForScrollChanged(View anchor, int xoff, int yoff, int gravity) {
1569        unregisterForScrollChanged();
1570
1571        mAnchor = new WeakReference<View>(anchor);
1572        ViewTreeObserver vto = anchor.getViewTreeObserver();
1573        if (vto != null) {
1574            vto.addOnScrollChangedListener(mOnScrollChangedListener);
1575        }
1576
1577        mAnchorXoff = xoff;
1578        mAnchorYoff = yoff;
1579        mAnchoredGravity = gravity;
1580    }
1581
1582    private class PopupViewContainer extends FrameLayout {
1583        private static final String TAG = "PopupWindow.PopupViewContainer";
1584
1585        public PopupViewContainer(Context context) {
1586            super(context);
1587        }
1588
1589        @Override
1590        protected int[] onCreateDrawableState(int extraSpace) {
1591            if (mAboveAnchor) {
1592                // 1 more needed for the above anchor state
1593                final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
1594                View.mergeDrawableStates(drawableState, ABOVE_ANCHOR_STATE_SET);
1595                return drawableState;
1596            } else {
1597                return super.onCreateDrawableState(extraSpace);
1598            }
1599        }
1600
1601        @Override
1602        public boolean dispatchKeyEvent(KeyEvent event) {
1603            if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
1604                if (getKeyDispatcherState() == null) {
1605                    return super.dispatchKeyEvent(event);
1606                }
1607
1608                if (event.getAction() == KeyEvent.ACTION_DOWN
1609                        && event.getRepeatCount() == 0) {
1610                    KeyEvent.DispatcherState state = getKeyDispatcherState();
1611                    if (state != null) {
1612                        state.startTracking(event, this);
1613                    }
1614                    return true;
1615                } else if (event.getAction() == KeyEvent.ACTION_UP) {
1616                    KeyEvent.DispatcherState state = getKeyDispatcherState();
1617                    if (state != null && state.isTracking(event) && !event.isCanceled()) {
1618                        dismiss();
1619                        return true;
1620                    }
1621                }
1622                return super.dispatchKeyEvent(event);
1623            } else {
1624                return super.dispatchKeyEvent(event);
1625            }
1626        }
1627
1628        @Override
1629        public boolean dispatchTouchEvent(MotionEvent ev) {
1630            if (mTouchInterceptor != null && mTouchInterceptor.onTouch(this, ev)) {
1631                return true;
1632            }
1633            return super.dispatchTouchEvent(ev);
1634        }
1635
1636        @Override
1637        public boolean onTouchEvent(MotionEvent event) {
1638            final int x = (int) event.getX();
1639            final int y = (int) event.getY();
1640
1641            if ((event.getAction() == MotionEvent.ACTION_DOWN)
1642                    && ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) {
1643                dismiss();
1644                return true;
1645            } else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
1646                dismiss();
1647                return true;
1648            } else {
1649                return super.onTouchEvent(event);
1650            }
1651        }
1652
1653        @Override
1654        public void sendAccessibilityEvent(int eventType) {
1655            // clinets are interested in the content not the container, make it event source
1656            if (mContentView != null) {
1657                mContentView.sendAccessibilityEvent(eventType);
1658            } else {
1659                super.sendAccessibilityEvent(eventType);
1660            }
1661        }
1662    }
1663
1664}
1665