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