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