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