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