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