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