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