• 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.Resources;
23 import android.content.res.TypedArray;
24 import android.graphics.PixelFormat;
25 import android.graphics.Rect;
26 import android.graphics.drawable.Drawable;
27 import android.graphics.drawable.StateListDrawable;
28 import android.os.Build;
29 import android.os.IBinder;
30 import android.util.AttributeSet;
31 import android.view.Gravity;
32 import android.view.KeyEvent;
33 import android.view.MotionEvent;
34 import android.view.View;
35 import android.view.View.OnTouchListener;
36 import android.view.ViewGroup;
37 import android.view.ViewTreeObserver;
38 import android.view.ViewTreeObserver.OnScrollChangedListener;
39 import android.view.WindowManager;
40 
41 import 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  */
51 public 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      */
PopupWindow(Context context)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      */
PopupWindow(Context context, AttributeSet attrs)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      */
PopupWindow(Context context, AttributeSet attrs, int defStyle)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      */
PopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)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      */
PopupWindow()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      */
PopupWindow(View contentView)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      */
PopupWindow(int width, int height)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      */
PopupWindow(View contentView, int width, int height)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      */
PopupWindow(View contentView, int width, int height, boolean focusable)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      */
getBackground()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      */
setBackgroundDrawable(Drawable background)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      */
getAnimationStyle()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      */
setIgnoreCheekPress()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      */
setAnimationStyle(int animationStyle)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      */
getContentView()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      */
setContentView(View contentView)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      */
setTouchInterceptor(OnTouchListener l)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      */
isFocusable()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      */
setFocusable(boolean focusable)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      */
getInputMethodMode()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      */
setInputMethodMode(int mode)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      */
setSoftInputMode(int mode)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      */
getSoftInputMode()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      */
isTouchable()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      */
setTouchable(boolean touchable)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      */
isOutsideTouchable()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      */
setOutsideTouchable(boolean touchable)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      */
isClippingEnabled()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      */
setClippingEnabled(boolean enabled)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      */
setClipToScreenEnabled(boolean enabled)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      */
setAllowScrollingAnchorParent(boolean enabled)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      */
isSplitTouchEnabled()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      */
setSplitTouchEnabled(boolean enabled)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      */
isLayoutInScreenEnabled()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      */
setLayoutInScreenEnabled(boolean enabled)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      */
setLayoutInsetDecor(boolean enabled)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      */
setWindowLayoutType(int layoutType)685     public void setWindowLayoutType(int layoutType) {
686         mWindowLayoutType = layoutType;
687     }
688 
689     /**
690      * @return The layout type for this window.
691      * @hide
692      */
getWindowLayoutType()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      */
setTouchModal(boolean touchModal)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      */
setWindowLayoutMode(int widthSpec, int heightSpec)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      */
getHeight()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      */
setHeight(int height)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      */
getWidth()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      */
setWidth(int width)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      */
isShowing()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      */
showAtLocation(View parent, int gravity, int x, int y)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      */
showAtLocation(IBinder token, int gravity, int x, int y)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.LEFT;
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      */
showAsDropDown(View anchor)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      */
showAsDropDown(View anchor, int xoff, int yoff)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 
updateAboveAnchor(boolean aboveAnchor)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      */
isAboveAnchor()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      */
preparePopup(WindowManager.LayoutParams p)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      */
invokePopup(WindowManager.LayoutParams p)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      */
createPopupLayout(IBinder token)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.LEFT | 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 
computeFlags(int curFlags)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 
computeAnimationResource()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      */
findDropDownPosition(View anchor, WindowManager.LayoutParams p, int xoff, int yoff)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.LEFT | 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.LEFT | 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      */
getMaxAvailableHeight(View anchor)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      */
getMaxAvailableHeight(View anchor, int yOffset)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      */
getMaxAvailableHeight(View anchor, int yOffset, boolean ignoreBottomDecorations)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      */
dismiss()1249     public void dismiss() {
1250         if (isShowing() && mPopupView != null) {
1251             unregisterForScrollChanged();
1252 
1253             try {
1254                 mWindowManager.removeView(mPopupView);
1255             } finally {
1256                 if (mPopupView != mContentView && mPopupView instanceof ViewGroup) {
1257                     ((ViewGroup) mPopupView).removeView(mContentView);
1258                 }
1259                 mPopupView = null;
1260                 mIsShowing = false;
1261 
1262                 if (mOnDismissListener != null) {
1263                     mOnDismissListener.onDismiss();
1264                 }
1265             }
1266         }
1267     }
1268 
1269     /**
1270      * Sets the listener to be called when the window is dismissed.
1271      *
1272      * @param onDismissListener The listener.
1273      */
setOnDismissListener(OnDismissListener onDismissListener)1274     public void setOnDismissListener(OnDismissListener onDismissListener) {
1275         mOnDismissListener = onDismissListener;
1276     }
1277 
1278     /**
1279      * Updates the state of the popup window, if it is currently being displayed,
1280      * from the currently set state.  This include:
1281      * {@link #setClippingEnabled(boolean)}, {@link #setFocusable(boolean)},
1282      * {@link #setIgnoreCheekPress()}, {@link #setInputMethodMode(int)},
1283      * {@link #setTouchable(boolean)}, and {@link #setAnimationStyle(int)}.
1284      */
update()1285     public void update() {
1286         if (!isShowing() || mContentView == null) {
1287             return;
1288         }
1289 
1290         WindowManager.LayoutParams p = (WindowManager.LayoutParams)
1291                 mPopupView.getLayoutParams();
1292 
1293         boolean update = false;
1294 
1295         final int newAnim = computeAnimationResource();
1296         if (newAnim != p.windowAnimations) {
1297             p.windowAnimations = newAnim;
1298             update = true;
1299         }
1300 
1301         final int newFlags = computeFlags(p.flags);
1302         if (newFlags != p.flags) {
1303             p.flags = newFlags;
1304             update = true;
1305         }
1306 
1307         if (update) {
1308             mWindowManager.updateViewLayout(mPopupView, p);
1309         }
1310     }
1311 
1312     /**
1313      * <p>Updates the dimension of the popup window. Calling this function
1314      * also updates the window with the current popup state as described
1315      * for {@link #update()}.</p>
1316      *
1317      * @param width the new width
1318      * @param height the new height
1319      */
update(int width, int height)1320     public void update(int width, int height) {
1321         WindowManager.LayoutParams p = (WindowManager.LayoutParams)
1322                 mPopupView.getLayoutParams();
1323         update(p.x, p.y, width, height, false);
1324     }
1325 
1326     /**
1327      * <p>Updates the position and the dimension of the popup window. Width and
1328      * height can be set to -1 to update location only.  Calling this function
1329      * also updates the window with the current popup state as
1330      * described for {@link #update()}.</p>
1331      *
1332      * @param x the new x location
1333      * @param y the new y location
1334      * @param width the new width, can be -1 to ignore
1335      * @param height the new height, can be -1 to ignore
1336      */
update(int x, int y, int width, int height)1337     public void update(int x, int y, int width, int height) {
1338         update(x, y, width, height, false);
1339     }
1340 
1341     /**
1342      * <p>Updates the position and the dimension of the popup window. Width and
1343      * height can be set to -1 to update location only.  Calling this function
1344      * also updates the window with the current popup state as
1345      * described for {@link #update()}.</p>
1346      *
1347      * @param x the new x location
1348      * @param y the new y location
1349      * @param width the new width, can be -1 to ignore
1350      * @param height the new height, can be -1 to ignore
1351      * @param force reposition the window even if the specified position
1352      *              already seems to correspond to the LayoutParams
1353      */
update(int x, int y, int width, int height, boolean force)1354     public void update(int x, int y, int width, int height, boolean force) {
1355         if (width != -1) {
1356             mLastWidth = width;
1357             setWidth(width);
1358         }
1359 
1360         if (height != -1) {
1361             mLastHeight = height;
1362             setHeight(height);
1363         }
1364 
1365         if (!isShowing() || mContentView == null) {
1366             return;
1367         }
1368 
1369         WindowManager.LayoutParams p = (WindowManager.LayoutParams) mPopupView.getLayoutParams();
1370 
1371         boolean update = force;
1372 
1373         final int finalWidth = mWidthMode < 0 ? mWidthMode : mLastWidth;
1374         if (width != -1 && p.width != finalWidth) {
1375             p.width = mLastWidth = finalWidth;
1376             update = true;
1377         }
1378 
1379         final int finalHeight = mHeightMode < 0 ? mHeightMode : mLastHeight;
1380         if (height != -1 && p.height != finalHeight) {
1381             p.height = mLastHeight = finalHeight;
1382             update = true;
1383         }
1384 
1385         if (p.x != x) {
1386             p.x = x;
1387             update = true;
1388         }
1389 
1390         if (p.y != y) {
1391             p.y = y;
1392             update = true;
1393         }
1394 
1395         final int newAnim = computeAnimationResource();
1396         if (newAnim != p.windowAnimations) {
1397             p.windowAnimations = newAnim;
1398             update = true;
1399         }
1400 
1401         final int newFlags = computeFlags(p.flags);
1402         if (newFlags != p.flags) {
1403             p.flags = newFlags;
1404             update = true;
1405         }
1406 
1407         if (update) {
1408             mWindowManager.updateViewLayout(mPopupView, p);
1409         }
1410     }
1411 
1412     /**
1413      * <p>Updates the position and the dimension of the popup window. Calling this
1414      * function also updates the window with the current popup state as described
1415      * for {@link #update()}.</p>
1416      *
1417      * @param anchor the popup's anchor view
1418      * @param width the new width, can be -1 to ignore
1419      * @param height the new height, can be -1 to ignore
1420      */
1421     public void update(View anchor, int width, int height) {
1422         update(anchor, false, 0, 0, true, width, height);
1423     }
1424 
1425     /**
1426      * <p>Updates the position and the dimension of the popup window. Width and
1427      * height can be set to -1 to update location only.  Calling this function
1428      * also updates the window with the current popup state as
1429      * described for {@link #update()}.</p>
1430      *
1431      * <p>If the view later scrolls to move <code>anchor</code> to a different
1432      * location, the popup will be moved correspondingly.</p>
1433      *
1434      * @param anchor the popup's anchor view
1435      * @param xoff x offset from the view's left edge
1436      * @param yoff y offset from the view's bottom edge
1437      * @param width the new width, can be -1 to ignore
1438      * @param height the new height, can be -1 to ignore
1439      */
1440     public void update(View anchor, int xoff, int yoff, int width, int height) {
1441         update(anchor, true, xoff, yoff, true, width, height);
1442     }
1443 
1444     private void update(View anchor, boolean updateLocation, int xoff, int yoff,
1445             boolean updateDimension, int width, int height) {
1446 
1447         if (!isShowing() || mContentView == null) {
1448             return;
1449         }
1450 
1451         WeakReference<View> oldAnchor = mAnchor;
1452         final boolean needsUpdate = updateLocation && (mAnchorXoff != xoff || mAnchorYoff != yoff);
1453         if (oldAnchor == null || oldAnchor.get() != anchor || (needsUpdate && !mIsDropdown)) {
1454             registerForScrollChanged(anchor, xoff, yoff);
1455         } else if (needsUpdate) {
1456             // No need to register again if this is a DropDown, showAsDropDown already did.
1457             mAnchorXoff = xoff;
1458             mAnchorYoff = yoff;
1459         }
1460 
1461         WindowManager.LayoutParams p = (WindowManager.LayoutParams) mPopupView.getLayoutParams();
1462 
1463         if (updateDimension) {
1464             if (width == -1) {
1465                 width = mPopupWidth;
1466             } else {
1467                 mPopupWidth = width;
1468             }
1469             if (height == -1) {
1470                 height = mPopupHeight;
1471             } else {
1472                 mPopupHeight = height;
1473             }
1474         }
1475 
1476         int x = p.x;
1477         int y = p.y;
1478 
1479         if (updateLocation) {
1480             updateAboveAnchor(findDropDownPosition(anchor, p, xoff, yoff));
1481         } else {
1482             updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff));
1483         }
1484 
1485         update(p.x, p.y, width, height, x != p.x || y != p.y);
1486     }
1487 
1488     /**
1489      * Listener that is called when this popup window is dismissed.
1490      */
1491     public interface OnDismissListener {
1492         /**
1493          * Called when this popup window is dismissed.
1494          */
1495         public void onDismiss();
1496     }
1497 
1498     private void unregisterForScrollChanged() {
1499         WeakReference<View> anchorRef = mAnchor;
1500         View anchor = null;
1501         if (anchorRef != null) {
1502             anchor = anchorRef.get();
1503         }
1504         if (anchor != null) {
1505             ViewTreeObserver vto = anchor.getViewTreeObserver();
1506             vto.removeOnScrollChangedListener(mOnScrollChangedListener);
1507         }
1508         mAnchor = null;
1509     }
1510 
1511     private void registerForScrollChanged(View anchor, int xoff, int yoff) {
1512         unregisterForScrollChanged();
1513 
1514         mAnchor = new WeakReference<View>(anchor);
1515         ViewTreeObserver vto = anchor.getViewTreeObserver();
1516         if (vto != null) {
1517             vto.addOnScrollChangedListener(mOnScrollChangedListener);
1518         }
1519 
1520         mAnchorXoff = xoff;
1521         mAnchorYoff = yoff;
1522     }
1523 
1524     private class PopupViewContainer extends FrameLayout {
1525         private static final String TAG = "PopupWindow.PopupViewContainer";
1526 
1527         public PopupViewContainer(Context context) {
1528             super(context);
1529         }
1530 
1531         @Override
1532         protected int[] onCreateDrawableState(int extraSpace) {
1533             if (mAboveAnchor) {
1534                 // 1 more needed for the above anchor state
1535                 final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
1536                 View.mergeDrawableStates(drawableState, ABOVE_ANCHOR_STATE_SET);
1537                 return drawableState;
1538             } else {
1539                 return super.onCreateDrawableState(extraSpace);
1540             }
1541         }
1542 
1543         @Override
1544         public boolean dispatchKeyEvent(KeyEvent event) {
1545             if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
1546                 if (getKeyDispatcherState() == null) {
1547                     return super.dispatchKeyEvent(event);
1548                 }
1549 
1550                 if (event.getAction() == KeyEvent.ACTION_DOWN
1551                         && event.getRepeatCount() == 0) {
1552                     KeyEvent.DispatcherState state = getKeyDispatcherState();
1553                     if (state != null) {
1554                         state.startTracking(event, this);
1555                     }
1556                     return true;
1557                 } else if (event.getAction() == KeyEvent.ACTION_UP) {
1558                     KeyEvent.DispatcherState state = getKeyDispatcherState();
1559                     if (state != null && state.isTracking(event) && !event.isCanceled()) {
1560                         dismiss();
1561                         return true;
1562                     }
1563                 }
1564                 return super.dispatchKeyEvent(event);
1565             } else {
1566                 return super.dispatchKeyEvent(event);
1567             }
1568         }
1569 
1570         @Override
1571         public boolean dispatchTouchEvent(MotionEvent ev) {
1572             if (mTouchInterceptor != null && mTouchInterceptor.onTouch(this, ev)) {
1573                 return true;
1574             }
1575             return super.dispatchTouchEvent(ev);
1576         }
1577 
1578         @Override
1579         public boolean onTouchEvent(MotionEvent event) {
1580             final int x = (int) event.getX();
1581             final int y = (int) event.getY();
1582 
1583             if ((event.getAction() == MotionEvent.ACTION_DOWN)
1584                     && ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) {
1585                 dismiss();
1586                 return true;
1587             } else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
1588                 dismiss();
1589                 return true;
1590             } else {
1591                 return super.onTouchEvent(event);
1592             }
1593         }
1594 
1595         @Override
1596         public void sendAccessibilityEvent(int eventType) {
1597             // clinets are interested in the content not the container, make it event source
1598             if (mContentView != null) {
1599                 mContentView.sendAccessibilityEvent(eventType);
1600             } else {
1601                 super.sendAccessibilityEvent(eventType);
1602             }
1603         }
1604     }
1605 
1606 }
1607