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.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.content.Context;
24 import android.content.res.TypedArray;
25 import android.graphics.PixelFormat;
26 import android.graphics.Rect;
27 import android.graphics.drawable.Drawable;
28 import android.graphics.drawable.StateListDrawable;
29 import android.os.Build;
30 import android.os.IBinder;
31 import android.transition.Transition;
32 import android.transition.Transition.EpicenterCallback;
33 import android.transition.Transition.TransitionListener;
34 import android.transition.Transition.TransitionListenerAdapter;
35 import android.transition.TransitionInflater;
36 import android.transition.TransitionManager;
37 import android.transition.TransitionSet;
38 import android.util.AttributeSet;
39 import android.view.Gravity;
40 import android.view.KeyEvent;
41 import android.view.MotionEvent;
42 import android.view.View;
43 import android.view.View.OnAttachStateChangeListener;
44 import android.view.View.OnTouchListener;
45 import android.view.ViewGroup;
46 import android.view.ViewParent;
47 import android.view.ViewTreeObserver;
48 import android.view.ViewTreeObserver.OnGlobalLayoutListener;
49 import android.view.ViewTreeObserver.OnScrollChangedListener;
50 import android.view.WindowManager;
51 import android.view.WindowManager.LayoutParams;
52 
53 import java.lang.ref.WeakReference;
54 
55 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME;
56 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH;
57 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
58 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
59 
60 /**
61  * <p>
62  * This class represents a popup window that can be used to display an
63  * arbitrary view. The popup window is a floating container that appears on top
64  * of the current activity.
65  * </p>
66  * <a name="Animation"></a>
67  * <h3>Animation</h3>
68  * <p>
69  * On all versions of Android, popup window enter and exit animations may be
70  * specified by calling {@link #setAnimationStyle(int)} and passing the
71  * resource ID for an animation style that defines {@code windowEnterAnimation}
72  * and {@code windowExitAnimation}. For example, passing
73  * {@link android.R.style#Animation_Dialog} will give a scale and alpha
74  * animation.
75  * </br>
76  * A window animation style may also be specified in the popup window's style
77  * XML via the {@link android.R.styleable#PopupWindow_popupAnimationStyle popupAnimationStyle}
78  * attribute.
79  * </p>
80  * <p>
81  * Starting with API 23, more complex popup window enter and exit transitions
82  * may be specified by calling either {@link #setEnterTransition(Transition)}
83  * or {@link #setExitTransition(Transition)} and passing a  {@link Transition}.
84  * </br>
85  * Popup enter and exit transitions may also be specified in the popup window's
86  * style XML via the {@link android.R.styleable#PopupWindow_popupEnterTransition popupEnterTransition}
87  * and {@link android.R.styleable#PopupWindow_popupExitTransition popupExitTransition}
88  * attributes, respectively.
89  * </p>
90  *
91  * @attr ref android.R.styleable#PopupWindow_overlapAnchor
92  * @attr ref android.R.styleable#PopupWindow_popupAnimationStyle
93  * @attr ref android.R.styleable#PopupWindow_popupBackground
94  * @attr ref android.R.styleable#PopupWindow_popupElevation
95  * @attr ref android.R.styleable#PopupWindow_popupEnterTransition
96  * @attr ref android.R.styleable#PopupWindow_popupExitTransition
97  *
98  * @see android.widget.AutoCompleteTextView
99  * @see android.widget.Spinner
100  */
101 public class PopupWindow {
102     /**
103      * Mode for {@link #setInputMethodMode(int)}: the requirements for the
104      * input method should be based on the focusability of the popup.  That is
105      * if it is focusable than it needs to work with the input method, else
106      * it doesn't.
107      */
108     public static final int INPUT_METHOD_FROM_FOCUSABLE = 0;
109 
110     /**
111      * Mode for {@link #setInputMethodMode(int)}: this popup always needs to
112      * work with an input method, regardless of whether it is focusable.  This
113      * means that it will always be displayed so that the user can also operate
114      * the input method while it is shown.
115      */
116     public static final int INPUT_METHOD_NEEDED = 1;
117 
118     /**
119      * Mode for {@link #setInputMethodMode(int)}: this popup never needs to
120      * work with an input method, regardless of whether it is focusable.  This
121      * means that it will always be displayed to use as much space on the
122      * screen as needed, regardless of whether this covers the input method.
123      */
124     public static final int INPUT_METHOD_NOT_NEEDED = 2;
125 
126     private static final int DEFAULT_ANCHORED_GRAVITY = Gravity.TOP | Gravity.START;
127 
128     /**
129      * Default animation style indicating that separate animations should be
130      * used for top/bottom anchoring states.
131      */
132     private static final int ANIMATION_STYLE_DEFAULT = -1;
133 
134     private final int[] mTmpDrawingLocation = new int[2];
135     private final int[] mTmpScreenLocation = new int[2];
136     private final Rect mTempRect = new Rect();
137 
138     private Context mContext;
139     private WindowManager mWindowManager;
140 
141     private boolean mIsShowing;
142     private boolean mIsTransitioningToDismiss;
143     private boolean mIsDropdown;
144 
145     /** View that handles event dispatch and content transitions. */
146     private PopupDecorView mDecorView;
147 
148     /** View that holds the background and may animate during a transition. */
149     private View mBackgroundView;
150 
151     /** The contents of the popup. May be identical to the background view. */
152     private View mContentView;
153 
154     private boolean mFocusable;
155     private int mInputMethodMode = INPUT_METHOD_FROM_FOCUSABLE;
156     private int mSoftInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED;
157     private boolean mTouchable = true;
158     private boolean mOutsideTouchable = false;
159     private boolean mClippingEnabled = true;
160     private int mSplitTouchEnabled = -1;
161     private boolean mLayoutInScreen;
162     private boolean mClipToScreen;
163     private boolean mAllowScrollingAnchorParent = true;
164     private boolean mLayoutInsetDecor = false;
165     private boolean mNotTouchModal;
166     private boolean mAttachedInDecor = true;
167     private boolean mAttachedInDecorSet = false;
168 
169     private OnTouchListener mTouchInterceptor;
170 
171     private int mWidthMode;
172     private int mWidth = LayoutParams.WRAP_CONTENT;
173     private int mLastWidth;
174     private int mHeightMode;
175     private int mHeight = LayoutParams.WRAP_CONTENT;
176     private int mLastHeight;
177 
178     private float mElevation;
179 
180     private Drawable mBackground;
181     private Drawable mAboveAnchorBackgroundDrawable;
182     private Drawable mBelowAnchorBackgroundDrawable;
183 
184     private Transition mEnterTransition;
185     private Transition mExitTransition;
186     private Rect mEpicenterBounds;
187 
188     private boolean mAboveAnchor;
189     private int mWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
190 
191     private OnDismissListener mOnDismissListener;
192     private boolean mIgnoreCheekPress = false;
193 
194     private int mAnimationStyle = ANIMATION_STYLE_DEFAULT;
195 
196     private static final int[] ABOVE_ANCHOR_STATE_SET = new int[] {
197         com.android.internal.R.attr.state_above_anchor
198     };
199 
200     private final OnAttachStateChangeListener mOnAnchorRootDetachedListener =
201             new OnAttachStateChangeListener() {
202                 @Override
203                 public void onViewAttachedToWindow(View v) {}
204 
205                 @Override
206                 public void onViewDetachedFromWindow(View v) {
207                     mIsAnchorRootAttached = false;
208                 }
209             };
210 
211     private WeakReference<View> mAnchor;
212     private WeakReference<View> mAnchorRoot;
213     private boolean mIsAnchorRootAttached;
214 
215     private final OnScrollChangedListener mOnScrollChangedListener = new OnScrollChangedListener() {
216         @Override
217         public void onScrollChanged() {
218             final View anchor = mAnchor != null ? mAnchor.get() : null;
219             if (anchor != null && mDecorView != null) {
220                 final WindowManager.LayoutParams p = (WindowManager.LayoutParams)
221                         mDecorView.getLayoutParams();
222 
223                 updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff,
224                         p.width, p.height, mAnchoredGravity));
225                 update(p.x, p.y, -1, -1, true);
226             }
227         }
228     };
229 
230     private int mAnchorXoff;
231     private int mAnchorYoff;
232     private int mAnchoredGravity;
233     private boolean mOverlapAnchor;
234 
235     private boolean mPopupViewInitialLayoutDirectionInherited;
236 
237     /**
238      * <p>Create a new empty, non focusable popup window of dimension (0,0).</p>
239      *
240      * <p>The popup does provide a background.</p>
241      */
PopupWindow(Context context)242     public PopupWindow(Context context) {
243         this(context, null);
244     }
245 
246     /**
247      * <p>Create a new empty, non focusable popup window of dimension (0,0).</p>
248      *
249      * <p>The popup does provide a background.</p>
250      */
PopupWindow(Context context, AttributeSet attrs)251     public PopupWindow(Context context, AttributeSet attrs) {
252         this(context, attrs, com.android.internal.R.attr.popupWindowStyle);
253     }
254 
255     /**
256      * <p>Create a new empty, non focusable popup window of dimension (0,0).</p>
257      *
258      * <p>The popup does provide a background.</p>
259      */
PopupWindow(Context context, AttributeSet attrs, int defStyleAttr)260     public PopupWindow(Context context, AttributeSet attrs, int defStyleAttr) {
261         this(context, attrs, defStyleAttr, 0);
262     }
263 
264     /**
265      * <p>Create a new, empty, non focusable popup window of dimension (0,0).</p>
266      *
267      * <p>The popup does not provide a background.</p>
268      */
PopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)269     public PopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
270         mContext = context;
271         mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
272 
273         final TypedArray a = context.obtainStyledAttributes(
274                 attrs, R.styleable.PopupWindow, defStyleAttr, defStyleRes);
275         final Drawable bg = a.getDrawable(R.styleable.PopupWindow_popupBackground);
276         mElevation = a.getDimension(R.styleable.PopupWindow_popupElevation, 0);
277         mOverlapAnchor = a.getBoolean(R.styleable.PopupWindow_overlapAnchor, false);
278 
279         // Preserve default behavior from Gingerbread. If the animation is
280         // undefined or explicitly specifies the Gingerbread animation style,
281         // use a sentinel value.
282         if (a.hasValueOrEmpty(R.styleable.PopupWindow_popupAnimationStyle)) {
283             final int animStyle = a.getResourceId(R.styleable.PopupWindow_popupAnimationStyle, 0);
284             if (animStyle == R.style.Animation_PopupWindow) {
285                 mAnimationStyle = ANIMATION_STYLE_DEFAULT;
286             } else {
287                 mAnimationStyle = animStyle;
288             }
289         } else {
290             mAnimationStyle = ANIMATION_STYLE_DEFAULT;
291         }
292 
293         final Transition enterTransition = getTransition(a.getResourceId(
294                 R.styleable.PopupWindow_popupEnterTransition, 0));
295         final Transition exitTransition;
296         if (a.hasValueOrEmpty(R.styleable.PopupWindow_popupExitTransition)) {
297             exitTransition = getTransition(a.getResourceId(
298                     R.styleable.PopupWindow_popupExitTransition, 0));
299         } else {
300             exitTransition = enterTransition == null ? null : enterTransition.clone();
301         }
302 
303         a.recycle();
304 
305         setEnterTransition(enterTransition);
306         setExitTransition(exitTransition);
307         setBackgroundDrawable(bg);
308     }
309 
310     /**
311      * <p>Create a new empty, non focusable popup window of dimension (0,0).</p>
312      *
313      * <p>The popup does not provide any background. This should be handled
314      * by the content view.</p>
315      */
PopupWindow()316     public PopupWindow() {
317         this(null, 0, 0);
318     }
319 
320     /**
321      * <p>Create a new non focusable popup window which can display the
322      * <tt>contentView</tt>. The dimension of the window are (0,0).</p>
323      *
324      * <p>The popup does not provide any background. This should be handled
325      * by the content view.</p>
326      *
327      * @param contentView the popup's content
328      */
PopupWindow(View contentView)329     public PopupWindow(View contentView) {
330         this(contentView, 0, 0);
331     }
332 
333     /**
334      * <p>Create a new empty, non focusable popup window. The dimension of the
335      * window must be passed to this constructor.</p>
336      *
337      * <p>The popup does not provide any background. This should be handled
338      * by the content view.</p>
339      *
340      * @param width the popup's width
341      * @param height the popup's height
342      */
PopupWindow(int width, int height)343     public PopupWindow(int width, int height) {
344         this(null, width, height);
345     }
346 
347     /**
348      * <p>Create a new non focusable popup window which can display the
349      * <tt>contentView</tt>. The dimension of the window must be passed to
350      * this constructor.</p>
351      *
352      * <p>The popup does not provide any background. This should be handled
353      * by the content view.</p>
354      *
355      * @param contentView the popup's content
356      * @param width the popup's width
357      * @param height the popup's height
358      */
PopupWindow(View contentView, int width, int height)359     public PopupWindow(View contentView, int width, int height) {
360         this(contentView, width, height, false);
361     }
362 
363     /**
364      * <p>Create a new popup window which can display the <tt>contentView</tt>.
365      * The dimension of the window must be passed to this constructor.</p>
366      *
367      * <p>The popup does not provide any background. This should be handled
368      * by the content view.</p>
369      *
370      * @param contentView the popup's content
371      * @param width the popup's width
372      * @param height the popup's height
373      * @param focusable true if the popup can be focused, false otherwise
374      */
PopupWindow(View contentView, int width, int height, boolean focusable)375     public PopupWindow(View contentView, int width, int height, boolean focusable) {
376         if (contentView != null) {
377             mContext = contentView.getContext();
378             mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
379         }
380 
381         setContentView(contentView);
382         setWidth(width);
383         setHeight(height);
384         setFocusable(focusable);
385     }
386 
387     /**
388      * Sets the enter transition to be used when the popup window is shown.
389      *
390      * @param enterTransition the enter transition, or {@code null} to clear
391      * @see #getEnterTransition()
392      * @attr ref android.R.styleable#PopupWindow_popupEnterTransition
393      */
setEnterTransition(@ullable Transition enterTransition)394     public void setEnterTransition(@Nullable Transition enterTransition) {
395         mEnterTransition = enterTransition;
396     }
397 
398     /**
399      * Returns the enter transition to be used when the popup window is shown.
400      *
401      * @return the enter transition, or {@code null} if not set
402      * @see #setEnterTransition(Transition)
403      * @attr ref android.R.styleable#PopupWindow_popupEnterTransition
404      */
405     @Nullable
getEnterTransition()406     public Transition getEnterTransition() {
407         return mEnterTransition;
408     }
409 
410     /**
411      * Sets the exit transition to be used when the popup window is dismissed.
412      *
413      * @param exitTransition the exit transition, or {@code null} to clear
414      * @see #getExitTransition()
415      * @attr ref android.R.styleable#PopupWindow_popupExitTransition
416      */
setExitTransition(@ullable Transition exitTransition)417     public void setExitTransition(@Nullable Transition exitTransition) {
418         mExitTransition = exitTransition;
419     }
420 
421     /**
422      * Returns the exit transition to be used when the popup window is
423      * dismissed.
424      *
425      * @return the exit transition, or {@code null} if not set
426      * @see #setExitTransition(Transition)
427      * @attr ref android.R.styleable#PopupWindow_popupExitTransition
428      */
429     @Nullable
getExitTransition()430     public Transition getExitTransition() {
431         return mExitTransition;
432     }
433 
434     /**
435      * Sets the bounds used as the epicenter of the enter and exit transitions.
436      * <p>
437      * Transitions use a point or Rect, referred to as the epicenter, to orient
438      * the direction of travel. For popup windows, the anchor view bounds are
439      * used as the default epicenter.
440      * <p>
441      * See {@link Transition#setEpicenterCallback(EpicenterCallback)} for more
442      * information about how transition epicenters.
443      *
444      * @param bounds the epicenter bounds relative to the anchor view, or
445      *               {@code null} to use the default epicenter
446      * @see #getTransitionEpicenter()
447      * @hide
448      */
setEpicenterBounds(Rect bounds)449     public void setEpicenterBounds(Rect bounds) {
450         mEpicenterBounds = bounds;
451     }
452 
getTransition(int resId)453     private Transition getTransition(int resId) {
454         if (resId != 0 && resId != R.transition.no_transition) {
455             final TransitionInflater inflater = TransitionInflater.from(mContext);
456             final Transition transition = inflater.inflateTransition(resId);
457             if (transition != null) {
458                 final boolean isEmpty = transition instanceof TransitionSet
459                         && ((TransitionSet) transition).getTransitionCount() == 0;
460                 if (!isEmpty) {
461                     return transition;
462                 }
463             }
464         }
465         return null;
466     }
467 
468     /**
469      * Return the drawable used as the popup window's background.
470      *
471      * @return the background drawable or {@code null} if not set
472      * @see #setBackgroundDrawable(Drawable)
473      * @attr ref android.R.styleable#PopupWindow_popupBackground
474      */
getBackground()475     public Drawable getBackground() {
476         return mBackground;
477     }
478 
479     /**
480      * Specifies the background drawable for this popup window. The background
481      * can be set to {@code null}.
482      *
483      * @param background the popup's background
484      * @see #getBackground()
485      * @attr ref android.R.styleable#PopupWindow_popupBackground
486      */
setBackgroundDrawable(Drawable background)487     public void setBackgroundDrawable(Drawable background) {
488         mBackground = background;
489 
490         // If this is a StateListDrawable, try to find and store the drawable to be
491         // used when the drop-down is placed above its anchor view, and the one to be
492         // used when the drop-down is placed below its anchor view. We extract
493         // the drawables ourselves to work around a problem with using refreshDrawableState
494         // that it will take into account the padding of all drawables specified in a
495         // StateListDrawable, thus adding superfluous padding to drop-down views.
496         //
497         // We assume a StateListDrawable will have a drawable for ABOVE_ANCHOR_STATE_SET and
498         // at least one other drawable, intended for the 'below-anchor state'.
499         if (mBackground instanceof StateListDrawable) {
500             StateListDrawable stateList = (StateListDrawable) mBackground;
501 
502             // Find the above-anchor view - this one's easy, it should be labeled as such.
503             int aboveAnchorStateIndex = stateList.getStateDrawableIndex(ABOVE_ANCHOR_STATE_SET);
504 
505             // Now, for the below-anchor view, look for any other drawable specified in the
506             // StateListDrawable which is not for the above-anchor state and use that.
507             int count = stateList.getStateCount();
508             int belowAnchorStateIndex = -1;
509             for (int i = 0; i < count; i++) {
510                 if (i != aboveAnchorStateIndex) {
511                     belowAnchorStateIndex = i;
512                     break;
513                 }
514             }
515 
516             // Store the drawables we found, if we found them. Otherwise, set them both
517             // to null so that we'll just use refreshDrawableState.
518             if (aboveAnchorStateIndex != -1 && belowAnchorStateIndex != -1) {
519                 mAboveAnchorBackgroundDrawable = stateList.getStateDrawable(aboveAnchorStateIndex);
520                 mBelowAnchorBackgroundDrawable = stateList.getStateDrawable(belowAnchorStateIndex);
521             } else {
522                 mBelowAnchorBackgroundDrawable = null;
523                 mAboveAnchorBackgroundDrawable = null;
524             }
525         }
526     }
527 
528     /**
529      * @return the elevation for this popup window in pixels
530      * @see #setElevation(float)
531      * @attr ref android.R.styleable#PopupWindow_popupElevation
532      */
getElevation()533     public float getElevation() {
534         return mElevation;
535     }
536 
537     /**
538      * Specifies the elevation for this popup window.
539      *
540      * @param elevation the popup's elevation in pixels
541      * @see #getElevation()
542      * @attr ref android.R.styleable#PopupWindow_popupElevation
543      */
setElevation(float elevation)544     public void setElevation(float elevation) {
545         mElevation = elevation;
546     }
547 
548     /**
549      * <p>Return the animation style to use the popup appears and disappears</p>
550      *
551      * @return the animation style to use the popup appears and disappears
552      */
getAnimationStyle()553     public int getAnimationStyle() {
554         return mAnimationStyle;
555     }
556 
557     /**
558      * Set the flag on popup to ignore cheek press events; by default this flag
559      * is set to false
560      * which means the popup will not ignore cheek press dispatch events.
561      *
562      * <p>If the popup is showing, calling this method will take effect only
563      * the next time the popup is shown or through a manual call to one of
564      * the {@link #update()} methods.</p>
565      *
566      * @see #update()
567      */
setIgnoreCheekPress()568     public void setIgnoreCheekPress() {
569         mIgnoreCheekPress = true;
570     }
571 
572 
573     /**
574      * <p>Change the animation style resource for this popup.</p>
575      *
576      * <p>If the popup is showing, calling this method will take effect only
577      * the next time the popup is shown or through a manual call to one of
578      * the {@link #update()} methods.</p>
579      *
580      * @param animationStyle animation style to use when the popup appears
581      *      and disappears.  Set to -1 for the default animation, 0 for no
582      *      animation, or a resource identifier for an explicit animation.
583      *
584      * @see #update()
585      */
setAnimationStyle(int animationStyle)586     public void setAnimationStyle(int animationStyle) {
587         mAnimationStyle = animationStyle;
588     }
589 
590     /**
591      * <p>Return the view used as the content of the popup window.</p>
592      *
593      * @return a {@link android.view.View} representing the popup's content
594      *
595      * @see #setContentView(android.view.View)
596      */
getContentView()597     public View getContentView() {
598         return mContentView;
599     }
600 
601     /**
602      * <p>Change the popup's content. The content is represented by an instance
603      * of {@link android.view.View}.</p>
604      *
605      * <p>This method has no effect if called when the popup is showing.</p>
606      *
607      * @param contentView the new content for the popup
608      *
609      * @see #getContentView()
610      * @see #isShowing()
611      */
setContentView(View contentView)612     public void setContentView(View contentView) {
613         if (isShowing()) {
614             return;
615         }
616 
617         mContentView = contentView;
618 
619         if (mContext == null && mContentView != null) {
620             mContext = mContentView.getContext();
621         }
622 
623         if (mWindowManager == null && mContentView != null) {
624             mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
625         }
626 
627         // Setting the default for attachedInDecor based on SDK version here
628         // instead of in the constructor since we might not have the context
629         // object in the constructor. We only want to set default here if the
630         // app hasn't already set the attachedInDecor.
631         if (mContext != null && !mAttachedInDecorSet) {
632             // Attach popup window in decor frame of parent window by default for
633             // {@link Build.VERSION_CODES.LOLLIPOP_MR1} or greater. Keep current
634             // behavior of not attaching to decor frame for older SDKs.
635             setAttachedInDecor(mContext.getApplicationInfo().targetSdkVersion
636                     >= Build.VERSION_CODES.LOLLIPOP_MR1);
637         }
638 
639     }
640 
641     /**
642      * Set a callback for all touch events being dispatched to the popup
643      * window.
644      */
setTouchInterceptor(OnTouchListener l)645     public void setTouchInterceptor(OnTouchListener l) {
646         mTouchInterceptor = l;
647     }
648 
649     /**
650      * <p>Indicate whether the popup window can grab the focus.</p>
651      *
652      * @return true if the popup is focusable, false otherwise
653      *
654      * @see #setFocusable(boolean)
655      */
isFocusable()656     public boolean isFocusable() {
657         return mFocusable;
658     }
659 
660     /**
661      * <p>Changes the focusability of the popup window. When focusable, the
662      * window will grab the focus from the current focused widget if the popup
663      * contains a focusable {@link android.view.View}.  By default a popup
664      * window is not focusable.</p>
665      *
666      * <p>If the popup is showing, calling this method will take effect only
667      * the next time the popup is shown or through a manual call to one of
668      * the {@link #update()} methods.</p>
669      *
670      * @param focusable true if the popup should grab focus, false otherwise.
671      *
672      * @see #isFocusable()
673      * @see #isShowing()
674      * @see #update()
675      */
setFocusable(boolean focusable)676     public void setFocusable(boolean focusable) {
677         mFocusable = focusable;
678     }
679 
680     /**
681      * Return the current value in {@link #setInputMethodMode(int)}.
682      *
683      * @see #setInputMethodMode(int)
684      */
getInputMethodMode()685     public int getInputMethodMode() {
686         return mInputMethodMode;
687 
688     }
689 
690     /**
691      * Control how the popup operates with an input method: one of
692      * {@link #INPUT_METHOD_FROM_FOCUSABLE}, {@link #INPUT_METHOD_NEEDED},
693      * or {@link #INPUT_METHOD_NOT_NEEDED}.
694      *
695      * <p>If the popup is showing, calling this method will take effect only
696      * the next time the popup is shown or through a manual call to one of
697      * the {@link #update()} methods.</p>
698      *
699      * @see #getInputMethodMode()
700      * @see #update()
701      */
setInputMethodMode(int mode)702     public void setInputMethodMode(int mode) {
703         mInputMethodMode = mode;
704     }
705 
706     /**
707      * Sets the operating mode for the soft input area.
708      *
709      * @param mode The desired mode, see
710      *        {@link android.view.WindowManager.LayoutParams#softInputMode}
711      *        for the full list
712      *
713      * @see android.view.WindowManager.LayoutParams#softInputMode
714      * @see #getSoftInputMode()
715      */
setSoftInputMode(int mode)716     public void setSoftInputMode(int mode) {
717         mSoftInputMode = mode;
718     }
719 
720     /**
721      * Returns the current value in {@link #setSoftInputMode(int)}.
722      *
723      * @see #setSoftInputMode(int)
724      * @see android.view.WindowManager.LayoutParams#softInputMode
725      */
getSoftInputMode()726     public int getSoftInputMode() {
727         return mSoftInputMode;
728     }
729 
730     /**
731      * <p>Indicates whether the popup window receives touch events.</p>
732      *
733      * @return true if the popup is touchable, false otherwise
734      *
735      * @see #setTouchable(boolean)
736      */
isTouchable()737     public boolean isTouchable() {
738         return mTouchable;
739     }
740 
741     /**
742      * <p>Changes the touchability of the popup window. When touchable, the
743      * window will receive touch events, otherwise touch events will go to the
744      * window below it. By default the window is touchable.</p>
745      *
746      * <p>If the popup is showing, calling this method will take effect only
747      * the next time the popup is shown or through a manual call to one of
748      * the {@link #update()} methods.</p>
749      *
750      * @param touchable true if the popup should receive touch events, false otherwise
751      *
752      * @see #isTouchable()
753      * @see #isShowing()
754      * @see #update()
755      */
setTouchable(boolean touchable)756     public void setTouchable(boolean touchable) {
757         mTouchable = touchable;
758     }
759 
760     /**
761      * <p>Indicates whether the popup window will be informed of touch events
762      * outside of its window.</p>
763      *
764      * @return true if the popup is outside touchable, false otherwise
765      *
766      * @see #setOutsideTouchable(boolean)
767      */
isOutsideTouchable()768     public boolean isOutsideTouchable() {
769         return mOutsideTouchable;
770     }
771 
772     /**
773      * <p>Controls whether the pop-up will be informed of touch events outside
774      * of its window.  This only makes sense for pop-ups that are touchable
775      * but not focusable, which means touches outside of the window will
776      * be delivered to the window behind.  The default is false.</p>
777      *
778      * <p>If the popup is showing, calling this method will take effect only
779      * the next time the popup is shown or through a manual call to one of
780      * the {@link #update()} methods.</p>
781      *
782      * @param touchable true if the popup should receive outside
783      * touch events, false otherwise
784      *
785      * @see #isOutsideTouchable()
786      * @see #isShowing()
787      * @see #update()
788      */
setOutsideTouchable(boolean touchable)789     public void setOutsideTouchable(boolean touchable) {
790         mOutsideTouchable = touchable;
791     }
792 
793     /**
794      * <p>Indicates whether clipping of the popup window is enabled.</p>
795      *
796      * @return true if the clipping is enabled, false otherwise
797      *
798      * @see #setClippingEnabled(boolean)
799      */
isClippingEnabled()800     public boolean isClippingEnabled() {
801         return mClippingEnabled;
802     }
803 
804     /**
805      * <p>Allows the popup window to extend beyond the bounds of the screen. By default the
806      * window is clipped to the screen boundaries. Setting this to false will allow windows to be
807      * accurately positioned.</p>
808      *
809      * <p>If the popup is showing, calling this method will take effect only
810      * the next time the popup is shown or through a manual call to one of
811      * the {@link #update()} methods.</p>
812      *
813      * @param enabled false if the window should be allowed to extend outside of the screen
814      * @see #isShowing()
815      * @see #isClippingEnabled()
816      * @see #update()
817      */
setClippingEnabled(boolean enabled)818     public void setClippingEnabled(boolean enabled) {
819         mClippingEnabled = enabled;
820     }
821 
822     /**
823      * Clip this popup window to the screen, but not to the containing window.
824      *
825      * @param enabled True to clip to the screen.
826      * @hide
827      */
setClipToScreenEnabled(boolean enabled)828     public void setClipToScreenEnabled(boolean enabled) {
829         mClipToScreen = enabled;
830     }
831 
832     /**
833      * Allow PopupWindow to scroll the anchor's parent to provide more room
834      * for the popup. Enabled by default.
835      *
836      * @param enabled True to scroll the anchor's parent when more room is desired by the popup.
837      */
setAllowScrollingAnchorParent(boolean enabled)838     void setAllowScrollingAnchorParent(boolean enabled) {
839         mAllowScrollingAnchorParent = enabled;
840     }
841 
842     /**
843      * <p>Indicates whether the popup window supports splitting touches.</p>
844      *
845      * @return true if the touch splitting is enabled, false otherwise
846      *
847      * @see #setSplitTouchEnabled(boolean)
848      */
isSplitTouchEnabled()849     public boolean isSplitTouchEnabled() {
850         if (mSplitTouchEnabled < 0 && mContext != null) {
851             return mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB;
852         }
853         return mSplitTouchEnabled == 1;
854     }
855 
856     /**
857      * <p>Allows the popup window to split touches across other windows that also
858      * support split touch.  When this flag is false, the first pointer
859      * that goes down determines the window to which all subsequent touches
860      * go until all pointers go up.  When this flag is true, each pointer
861      * (not necessarily the first) that goes down determines the window
862      * to which all subsequent touches of that pointer will go until that
863      * pointer goes up thereby enabling touches with multiple pointers
864      * to be split across multiple windows.</p>
865      *
866      * @param enabled true if the split touches should be enabled, false otherwise
867      * @see #isSplitTouchEnabled()
868      */
setSplitTouchEnabled(boolean enabled)869     public void setSplitTouchEnabled(boolean enabled) {
870         mSplitTouchEnabled = enabled ? 1 : 0;
871     }
872 
873     /**
874      * <p>Indicates whether the popup window will be forced into using absolute screen coordinates
875      * for positioning.</p>
876      *
877      * @return true if the window will always be positioned in screen coordinates.
878      * @hide
879      */
isLayoutInScreenEnabled()880     public boolean isLayoutInScreenEnabled() {
881         return mLayoutInScreen;
882     }
883 
884     /**
885      * <p>Allows the popup window to force the flag
886      * {@link WindowManager.LayoutParams#FLAG_LAYOUT_IN_SCREEN}, overriding default behavior.
887      * This will cause the popup to be positioned in absolute screen coordinates.</p>
888      *
889      * @param enabled true if the popup should always be positioned in screen coordinates
890      * @hide
891      */
setLayoutInScreenEnabled(boolean enabled)892     public void setLayoutInScreenEnabled(boolean enabled) {
893         mLayoutInScreen = enabled;
894     }
895 
896     /**
897      * <p>Indicates whether the popup window will be attached in the decor frame of its parent
898      * window.
899      *
900      * @return true if the window will be attached to the decor frame of its parent window.
901      *
902      * @see #setAttachedInDecor(boolean)
903      * @see WindowManager.LayoutParams#FLAG_LAYOUT_ATTACHED_IN_DECOR
904      */
isAttachedInDecor()905     public boolean isAttachedInDecor() {
906         return mAttachedInDecor;
907     }
908 
909     /**
910      * <p>This will attach the popup window to the decor frame of the parent window to avoid
911      * overlaping with screen decorations like the navigation bar. Overrides the default behavior of
912      * the flag {@link WindowManager.LayoutParams#FLAG_LAYOUT_ATTACHED_IN_DECOR}.
913      *
914      * <p>By default the flag is set on SDK version {@link Build.VERSION_CODES#LOLLIPOP_MR1} or
915      * greater and cleared on lesser SDK versions.
916      *
917      * @param enabled true if the popup should be attached to the decor frame of its parent window.
918      *
919      * @see WindowManager.LayoutParams#FLAG_LAYOUT_ATTACHED_IN_DECOR
920      */
setAttachedInDecor(boolean enabled)921     public void setAttachedInDecor(boolean enabled) {
922         mAttachedInDecor = enabled;
923         mAttachedInDecorSet = true;
924     }
925 
926     /**
927      * Allows the popup window to force the flag
928      * {@link WindowManager.LayoutParams#FLAG_LAYOUT_INSET_DECOR}, overriding default behavior.
929      * This will cause the popup to inset its content to account for system windows overlaying
930      * the screen, such as the status bar.
931      *
932      * <p>This will often be combined with {@link #setLayoutInScreenEnabled(boolean)}.
933      *
934      * @param enabled true if the popup's views should inset content to account for system windows,
935      *                the way that decor views behave for full-screen windows.
936      * @hide
937      */
setLayoutInsetDecor(boolean enabled)938     public void setLayoutInsetDecor(boolean enabled) {
939         mLayoutInsetDecor = enabled;
940     }
941 
942     /**
943      * Set the layout type for this window.
944      * <p>
945      * See {@link WindowManager.LayoutParams#type} for possible values.
946      *
947      * @param layoutType Layout type for this window.
948      *
949      * @see WindowManager.LayoutParams#type
950      */
setWindowLayoutType(int layoutType)951     public void setWindowLayoutType(int layoutType) {
952         mWindowLayoutType = layoutType;
953     }
954 
955     /**
956      * Returns the layout type for this window.
957      *
958      * @see #setWindowLayoutType(int)
959      */
getWindowLayoutType()960     public int getWindowLayoutType() {
961         return mWindowLayoutType;
962     }
963 
964     /**
965      * Set whether this window is touch modal or if outside touches will be sent to
966      * other windows behind it.
967      * @hide
968      */
setTouchModal(boolean touchModal)969     public void setTouchModal(boolean touchModal) {
970         mNotTouchModal = !touchModal;
971     }
972 
973     /**
974      * <p>Change the width and height measure specs that are given to the
975      * window manager by the popup.  By default these are 0, meaning that
976      * the current width or height is requested as an explicit size from
977      * the window manager.  You can supply
978      * {@link ViewGroup.LayoutParams#WRAP_CONTENT} or
979      * {@link ViewGroup.LayoutParams#MATCH_PARENT} to have that measure
980      * spec supplied instead, replacing the absolute width and height that
981      * has been set in the popup.</p>
982      *
983      * <p>If the popup is showing, calling this method will take effect only
984      * the next time the popup is shown.</p>
985      *
986      * @param widthSpec an explicit width measure spec mode, either
987      * {@link ViewGroup.LayoutParams#WRAP_CONTENT},
988      * {@link ViewGroup.LayoutParams#MATCH_PARENT}, or 0 to use the absolute
989      * width.
990      * @param heightSpec an explicit height measure spec mode, either
991      * {@link ViewGroup.LayoutParams#WRAP_CONTENT},
992      * {@link ViewGroup.LayoutParams#MATCH_PARENT}, or 0 to use the absolute
993      * height.
994      *
995      * @deprecated Use {@link #setWidth(int)} and {@link #setHeight(int)}.
996      */
997     @Deprecated
setWindowLayoutMode(int widthSpec, int heightSpec)998     public void setWindowLayoutMode(int widthSpec, int heightSpec) {
999         mWidthMode = widthSpec;
1000         mHeightMode = heightSpec;
1001     }
1002 
1003     /**
1004      * Returns the popup's requested height. May be a layout constant such as
1005      * {@link LayoutParams#WRAP_CONTENT} or {@link LayoutParams#MATCH_PARENT}.
1006      * <p>
1007      * The actual size of the popup may depend on other factors such as
1008      * clipping and window layout.
1009      *
1010      * @return the popup height in pixels or a layout constant
1011      * @see #setHeight(int)
1012      */
getHeight()1013     public int getHeight() {
1014         return mHeight;
1015     }
1016 
1017     /**
1018      * Sets the popup's requested height. May be a layout constant such as
1019      * {@link LayoutParams#WRAP_CONTENT} or {@link LayoutParams#MATCH_PARENT}.
1020      * <p>
1021      * The actual size of the popup may depend on other factors such as
1022      * clipping and window layout.
1023      * <p>
1024      * If the popup is showing, calling this method will take effect the next
1025      * time the popup is shown.
1026      *
1027      * @param height the popup height in pixels or a layout constant
1028      * @see #getHeight()
1029      * @see #isShowing()
1030      */
setHeight(int height)1031     public void setHeight(int height) {
1032         mHeight = height;
1033     }
1034 
1035     /**
1036      * Returns the popup's requested width. May be a layout constant such as
1037      * {@link LayoutParams#WRAP_CONTENT} or {@link LayoutParams#MATCH_PARENT}.
1038      * <p>
1039      * The actual size of the popup may depend on other factors such as
1040      * clipping and window layout.
1041      *
1042      * @return the popup width in pixels or a layout constant
1043      * @see #setWidth(int)
1044      */
getWidth()1045     public int getWidth() {
1046         return mWidth;
1047     }
1048 
1049     /**
1050      * Sets the popup's requested width. May be a layout constant such as
1051      * {@link LayoutParams#WRAP_CONTENT} or {@link LayoutParams#MATCH_PARENT}.
1052      * <p>
1053      * The actual size of the popup may depend on other factors such as
1054      * clipping and window layout.
1055      * <p>
1056      * If the popup is showing, calling this method will take effect the next
1057      * time the popup is shown.
1058      *
1059      * @param width the popup width in pixels or a layout constant
1060      * @see #getWidth()
1061      * @see #isShowing()
1062      */
setWidth(int width)1063     public void setWidth(int width) {
1064         mWidth = width;
1065     }
1066 
1067     /**
1068      * Sets whether the popup window should overlap its anchor view when
1069      * displayed as a drop-down.
1070      * <p>
1071      * If the popup is showing, calling this method will take effect only
1072      * the next time the popup is shown.
1073      *
1074      * @param overlapAnchor Whether the popup should overlap its anchor.
1075      *
1076      * @see #getOverlapAnchor()
1077      * @see #isShowing()
1078      */
setOverlapAnchor(boolean overlapAnchor)1079     public void setOverlapAnchor(boolean overlapAnchor) {
1080         mOverlapAnchor = overlapAnchor;
1081     }
1082 
1083     /**
1084      * Returns whether the popup window should overlap its anchor view when
1085      * displayed as a drop-down.
1086      *
1087      * @return Whether the popup should overlap its anchor.
1088      *
1089      * @see #setOverlapAnchor(boolean)
1090      */
getOverlapAnchor()1091     public boolean getOverlapAnchor() {
1092         return mOverlapAnchor;
1093     }
1094 
1095     /**
1096      * <p>Indicate whether this popup window is showing on screen.</p>
1097      *
1098      * @return true if the popup is showing, false otherwise
1099      */
isShowing()1100     public boolean isShowing() {
1101         return mIsShowing;
1102     }
1103 
1104     /**
1105      * <p>
1106      * Display the content view in a popup window at the specified location. If the popup window
1107      * cannot fit on screen, it will be clipped. See {@link android.view.WindowManager.LayoutParams}
1108      * for more information on how gravity and the x and y parameters are related. Specifying
1109      * a gravity of {@link android.view.Gravity#NO_GRAVITY} is similar to specifying
1110      * <code>Gravity.LEFT | Gravity.TOP</code>.
1111      * </p>
1112      *
1113      * @param parent a parent view to get the {@link android.view.View#getWindowToken()} token from
1114      * @param gravity the gravity which controls the placement of the popup window
1115      * @param x the popup's x location offset
1116      * @param y the popup's y location offset
1117      */
showAtLocation(View parent, int gravity, int x, int y)1118     public void showAtLocation(View parent, int gravity, int x, int y) {
1119         showAtLocation(parent.getWindowToken(), gravity, x, y);
1120     }
1121 
1122     /**
1123      * Display the content view in a popup window at the specified location.
1124      *
1125      * @param token Window token to use for creating the new window
1126      * @param gravity the gravity which controls the placement of the popup window
1127      * @param x the popup's x location offset
1128      * @param y the popup's y location offset
1129      *
1130      * @hide Internal use only. Applications should use
1131      *       {@link #showAtLocation(View, int, int, int)} instead.
1132      */
showAtLocation(IBinder token, int gravity, int x, int y)1133     public void showAtLocation(IBinder token, int gravity, int x, int y) {
1134         if (isShowing() || mContentView == null) {
1135             return;
1136         }
1137 
1138         TransitionManager.endTransitions(mDecorView);
1139 
1140         detachFromAnchor();
1141 
1142         mIsShowing = true;
1143         mIsDropdown = false;
1144 
1145         final WindowManager.LayoutParams p = createPopupLayoutParams(token);
1146         preparePopup(p);
1147 
1148         // Only override the default if some gravity was specified.
1149         if (gravity != Gravity.NO_GRAVITY) {
1150             p.gravity = gravity;
1151         }
1152 
1153         p.x = x;
1154         p.y = y;
1155 
1156         invokePopup(p);
1157     }
1158 
1159     /**
1160      * Display the content view in a popup window anchored to the bottom-left
1161      * corner of the anchor view. If there is not enough room on screen to show
1162      * the popup in its entirety, this method tries to find a parent scroll
1163      * view to scroll. If no parent scroll view can be scrolled, the
1164      * bottom-left corner of the popup is pinned at the top left corner of the
1165      * anchor view.
1166      *
1167      * @param anchor the view on which to pin the popup window
1168      *
1169      * @see #dismiss()
1170      */
showAsDropDown(View anchor)1171     public void showAsDropDown(View anchor) {
1172         showAsDropDown(anchor, 0, 0);
1173     }
1174 
1175     /**
1176      * Display the content view in a popup window anchored to the bottom-left
1177      * corner of the anchor view offset by the specified x and y coordinates.
1178      * If there is not enough room on screen to show the popup in its entirety,
1179      * this method tries to find a parent scroll view to scroll. If no parent
1180      * scroll view can be scrolled, the bottom-left corner of the popup is
1181      * pinned at the top left corner of the anchor view.
1182      * <p>
1183      * If the view later scrolls to move <code>anchor</code> to a different
1184      * location, the popup will be moved correspondingly.
1185      *
1186      * @param anchor the view on which to pin the popup window
1187      * @param xoff A horizontal offset from the anchor in pixels
1188      * @param yoff A vertical offset from the anchor in pixels
1189      *
1190      * @see #dismiss()
1191      */
showAsDropDown(View anchor, int xoff, int yoff)1192     public void showAsDropDown(View anchor, int xoff, int yoff) {
1193         showAsDropDown(anchor, xoff, yoff, DEFAULT_ANCHORED_GRAVITY);
1194     }
1195 
1196     /**
1197      * Displays the content view in a popup window anchored to the corner of
1198      * another view. The window is positioned according to the specified
1199      * gravity and offset by the specified x and y coordinates.
1200      * <p>
1201      * If there is not enough room on screen to show the popup in its entirety,
1202      * this method tries to find a parent scroll view to scroll. If no parent
1203      * view can be scrolled, the specified vertical gravity will be ignored and
1204      * the popup will anchor itself such that it is visible.
1205      * <p>
1206      * If the view later scrolls to move <code>anchor</code> to a different
1207      * location, the popup will be moved correspondingly.
1208      *
1209      * @param anchor the view on which to pin the popup window
1210      * @param xoff A horizontal offset from the anchor in pixels
1211      * @param yoff A vertical offset from the anchor in pixels
1212      * @param gravity Alignment of the popup relative to the anchor
1213      *
1214      * @see #dismiss()
1215      */
showAsDropDown(View anchor, int xoff, int yoff, int gravity)1216     public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
1217         if (isShowing() || mContentView == null) {
1218             return;
1219         }
1220 
1221         TransitionManager.endTransitions(mDecorView);
1222 
1223         attachToAnchor(anchor, xoff, yoff, gravity);
1224 
1225         mIsShowing = true;
1226         mIsDropdown = true;
1227 
1228         final WindowManager.LayoutParams p = createPopupLayoutParams(anchor.getWindowToken());
1229         preparePopup(p);
1230 
1231         final boolean aboveAnchor = findDropDownPosition(anchor, p, xoff, yoff,
1232                 p.width, p.height, gravity);
1233         updateAboveAnchor(aboveAnchor);
1234         p.accessibilityIdOfAnchor = (anchor != null) ? anchor.getAccessibilityViewId() : -1;
1235 
1236         invokePopup(p);
1237     }
1238 
updateAboveAnchor(boolean aboveAnchor)1239     private void updateAboveAnchor(boolean aboveAnchor) {
1240         if (aboveAnchor != mAboveAnchor) {
1241             mAboveAnchor = aboveAnchor;
1242 
1243             if (mBackground != null && mBackgroundView != null) {
1244                 // If the background drawable provided was a StateListDrawable
1245                 // with above-anchor and below-anchor states, use those.
1246                 // Otherwise, rely on refreshDrawableState to do the job.
1247                 if (mAboveAnchorBackgroundDrawable != null) {
1248                     if (mAboveAnchor) {
1249                         mBackgroundView.setBackground(mAboveAnchorBackgroundDrawable);
1250                     } else {
1251                         mBackgroundView.setBackground(mBelowAnchorBackgroundDrawable);
1252                     }
1253                 } else {
1254                     mBackgroundView.refreshDrawableState();
1255                 }
1256             }
1257         }
1258     }
1259 
1260     /**
1261      * Indicates whether the popup is showing above (the y coordinate of the popup's bottom
1262      * is less than the y coordinate of the anchor) or below the anchor view (the y coordinate
1263      * of the popup is greater than y coordinate of the anchor's bottom).
1264      *
1265      * The value returned
1266      * by this method is meaningful only after {@link #showAsDropDown(android.view.View)}
1267      * or {@link #showAsDropDown(android.view.View, int, int)} was invoked.
1268      *
1269      * @return True if this popup is showing above the anchor view, false otherwise.
1270      */
isAboveAnchor()1271     public boolean isAboveAnchor() {
1272         return mAboveAnchor;
1273     }
1274 
1275     /**
1276      * Prepare the popup by embedding it into a new ViewGroup if the background
1277      * drawable is not null. If embedding is required, the layout parameters'
1278      * height is modified to take into account the background's padding.
1279      *
1280      * @param p the layout parameters of the popup's content view
1281      */
preparePopup(WindowManager.LayoutParams p)1282     private void preparePopup(WindowManager.LayoutParams p) {
1283         if (mContentView == null || mContext == null || mWindowManager == null) {
1284             throw new IllegalStateException("You must specify a valid content view by "
1285                     + "calling setContentView() before attempting to show the popup.");
1286         }
1287 
1288         // The old decor view may be transitioning out. Make sure it finishes
1289         // and cleans up before we try to create another one.
1290         if (mDecorView != null) {
1291             mDecorView.cancelTransitions();
1292         }
1293 
1294         // When a background is available, we embed the content view within
1295         // another view that owns the background drawable.
1296         if (mBackground != null) {
1297             mBackgroundView = createBackgroundView(mContentView);
1298             mBackgroundView.setBackground(mBackground);
1299         } else {
1300             mBackgroundView = mContentView;
1301         }
1302 
1303         mDecorView = createDecorView(mBackgroundView);
1304 
1305         // The background owner should be elevated so that it casts a shadow.
1306         mBackgroundView.setElevation(mElevation);
1307 
1308         // We may wrap that in another view, so we'll need to manually specify
1309         // the surface insets.
1310         p.setSurfaceInsets(mBackgroundView, true /*manual*/, true /*preservePrevious*/);
1311 
1312         mPopupViewInitialLayoutDirectionInherited =
1313                 (mContentView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT);
1314     }
1315 
1316     /**
1317      * Wraps a content view in a PopupViewContainer.
1318      *
1319      * @param contentView the content view to wrap
1320      * @return a PopupViewContainer that wraps the content view
1321      */
createBackgroundView(View contentView)1322     private PopupBackgroundView createBackgroundView(View contentView) {
1323         final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();
1324         final int height;
1325         if (layoutParams != null && layoutParams.height == WRAP_CONTENT) {
1326             height = WRAP_CONTENT;
1327         } else {
1328             height = MATCH_PARENT;
1329         }
1330 
1331         final PopupBackgroundView backgroundView = new PopupBackgroundView(mContext);
1332         final PopupBackgroundView.LayoutParams listParams = new PopupBackgroundView.LayoutParams(
1333                 MATCH_PARENT, height);
1334         backgroundView.addView(contentView, listParams);
1335 
1336         return backgroundView;
1337     }
1338 
1339     /**
1340      * Wraps a content view in a FrameLayout.
1341      *
1342      * @param contentView the content view to wrap
1343      * @return a FrameLayout that wraps the content view
1344      */
createDecorView(View contentView)1345     private PopupDecorView createDecorView(View contentView) {
1346         final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();
1347         final int height;
1348         if (layoutParams != null && layoutParams.height == WRAP_CONTENT) {
1349             height = WRAP_CONTENT;
1350         } else {
1351             height = MATCH_PARENT;
1352         }
1353 
1354         final PopupDecorView decorView = new PopupDecorView(mContext);
1355         decorView.addView(contentView, MATCH_PARENT, height);
1356         decorView.setClipChildren(false);
1357         decorView.setClipToPadding(false);
1358 
1359         return decorView;
1360     }
1361 
1362     /**
1363      * <p>Invoke the popup window by adding the content view to the window
1364      * manager.</p>
1365      *
1366      * <p>The content view must be non-null when this method is invoked.</p>
1367      *
1368      * @param p the layout parameters of the popup's content view
1369      */
invokePopup(WindowManager.LayoutParams p)1370     private void invokePopup(WindowManager.LayoutParams p) {
1371         if (mContext != null) {
1372             p.packageName = mContext.getPackageName();
1373         }
1374 
1375         final PopupDecorView decorView = mDecorView;
1376         decorView.setFitsSystemWindows(mLayoutInsetDecor);
1377 
1378         setLayoutDirectionFromAnchor();
1379 
1380         mWindowManager.addView(decorView, p);
1381 
1382         if (mEnterTransition != null) {
1383             decorView.requestEnterTransition(mEnterTransition);
1384         }
1385     }
1386 
setLayoutDirectionFromAnchor()1387     private void setLayoutDirectionFromAnchor() {
1388         if (mAnchor != null) {
1389             View anchor = mAnchor.get();
1390             if (anchor != null && mPopupViewInitialLayoutDirectionInherited) {
1391                 mDecorView.setLayoutDirection(anchor.getLayoutDirection());
1392             }
1393         }
1394     }
1395 
computeGravity()1396     private int computeGravity() {
1397         int gravity = Gravity.START | Gravity.TOP;
1398         if (mClipToScreen || mClippingEnabled) {
1399             gravity |= Gravity.DISPLAY_CLIP_VERTICAL;
1400         }
1401         return gravity;
1402     }
1403 
1404     /**
1405      * <p>Generate the layout parameters for the popup window.</p>
1406      *
1407      * @param token the window token used to bind the popup's window
1408      *
1409      * @return the layout parameters to pass to the window manager
1410      */
createPopupLayoutParams(IBinder token)1411     private WindowManager.LayoutParams createPopupLayoutParams(IBinder token) {
1412         final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
1413 
1414         // These gravity settings put the view at the top left corner of the
1415         // screen. The view is then positioned to the appropriate location by
1416         // setting the x and y offsets to match the anchor's bottom-left
1417         // corner.
1418         p.gravity = computeGravity();
1419         p.flags = computeFlags(p.flags);
1420         p.type = mWindowLayoutType;
1421         p.token = token;
1422         p.softInputMode = mSoftInputMode;
1423         p.windowAnimations = computeAnimationResource();
1424 
1425         if (mBackground != null) {
1426             p.format = mBackground.getOpacity();
1427         } else {
1428             p.format = PixelFormat.TRANSLUCENT;
1429         }
1430 
1431         if (mHeightMode < 0) {
1432             p.height = mLastHeight = mHeightMode;
1433         } else {
1434             p.height = mLastHeight = mHeight;
1435         }
1436 
1437         if (mWidthMode < 0) {
1438             p.width = mLastWidth = mWidthMode;
1439         } else {
1440             p.width = mLastWidth = mWidth;
1441         }
1442 
1443         p.privateFlags = PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH
1444                 | PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME;
1445 
1446         // Used for debugging.
1447         p.setTitle("PopupWindow:" + Integer.toHexString(hashCode()));
1448 
1449         return p;
1450     }
1451 
computeFlags(int curFlags)1452     private int computeFlags(int curFlags) {
1453         curFlags &= ~(
1454                 WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES |
1455                 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
1456                 WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE |
1457                 WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH |
1458                 WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS |
1459                 WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM |
1460                 WindowManager.LayoutParams.FLAG_SPLIT_TOUCH);
1461         if(mIgnoreCheekPress) {
1462             curFlags |= WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
1463         }
1464         if (!mFocusable) {
1465             curFlags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
1466             if (mInputMethodMode == INPUT_METHOD_NEEDED) {
1467                 curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
1468             }
1469         } else if (mInputMethodMode == INPUT_METHOD_NOT_NEEDED) {
1470             curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
1471         }
1472         if (!mTouchable) {
1473             curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
1474         }
1475         if (mOutsideTouchable) {
1476             curFlags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
1477         }
1478         if (!mClippingEnabled || mClipToScreen) {
1479             curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
1480         }
1481         if (isSplitTouchEnabled()) {
1482             curFlags |= WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
1483         }
1484         if (mLayoutInScreen) {
1485             curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
1486         }
1487         if (mLayoutInsetDecor) {
1488             curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
1489         }
1490         if (mNotTouchModal) {
1491             curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
1492         }
1493         if (mAttachedInDecor) {
1494           curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_ATTACHED_IN_DECOR;
1495         }
1496         return curFlags;
1497     }
1498 
computeAnimationResource()1499     private int computeAnimationResource() {
1500         if (mAnimationStyle == ANIMATION_STYLE_DEFAULT) {
1501             if (mIsDropdown) {
1502                 return mAboveAnchor
1503                         ? com.android.internal.R.style.Animation_DropDownUp
1504                         : com.android.internal.R.style.Animation_DropDownDown;
1505             }
1506             return 0;
1507         }
1508         return mAnimationStyle;
1509     }
1510 
1511     /**
1512      * Positions the popup window on screen. When the popup window is too tall
1513      * to fit under the anchor, a parent scroll view is seeked and scrolled up
1514      * to reclaim space. If scrolling is not possible or not enough, the popup
1515      * window gets moved on top of the anchor.
1516      * <p>
1517      * The results of positioning are placed in {@code outParams}.
1518      *
1519      * @param anchor the view on which the popup window must be anchored
1520      * @param outParams the layout parameters used to display the drop down
1521      * @param xOffset absolute horizontal offset from the left of the anchor
1522      * @param yOffset absolute vertical offset from the top of the anchor
1523      * @param gravity horizontal gravity specifying popup alignment
1524      * @return true if the popup is translated upwards to fit on screen
1525      */
findDropDownPosition(View anchor, WindowManager.LayoutParams outParams, int xOffset, int yOffset, int width, int height, int gravity)1526     private boolean findDropDownPosition(View anchor, WindowManager.LayoutParams outParams,
1527             int xOffset, int yOffset, int width, int height, int gravity) {
1528         final int anchorHeight = anchor.getHeight();
1529         final int anchorWidth = anchor.getWidth();
1530         if (mOverlapAnchor) {
1531             yOffset -= anchorHeight;
1532         }
1533 
1534         // Initially, align to the bottom-left corner of the anchor plus offsets.
1535         final int[] drawingLocation = mTmpDrawingLocation;
1536         anchor.getLocationInWindow(drawingLocation);
1537         outParams.x = drawingLocation[0] + xOffset;
1538         outParams.y = drawingLocation[1] + anchorHeight + yOffset;
1539 
1540         final Rect displayFrame = new Rect();
1541         anchor.getWindowVisibleDisplayFrame(displayFrame);
1542         if (width == MATCH_PARENT) {
1543             width = displayFrame.right - displayFrame.left;
1544         }
1545         if (height == MATCH_PARENT) {
1546             height = displayFrame.bottom - displayFrame.top;
1547         }
1548 
1549         // Let the window manager know to align the top to y.
1550         outParams.gravity = Gravity.LEFT | Gravity.TOP;
1551         outParams.width = width;
1552         outParams.height = height;
1553 
1554         // If we need to adjust for gravity RIGHT, align to the bottom-right
1555         // corner of the anchor (still accounting for offsets).
1556         final int hgrav = Gravity.getAbsoluteGravity(gravity, anchor.getLayoutDirection())
1557                 & Gravity.HORIZONTAL_GRAVITY_MASK;
1558         if (hgrav == Gravity.RIGHT) {
1559             outParams.x -= width - anchorWidth;
1560         }
1561 
1562         final int[] screenLocation = mTmpScreenLocation;
1563         anchor.getLocationOnScreen(screenLocation);
1564 
1565         // First, attempt to fit the popup vertically without resizing.
1566         final boolean fitsVertical = tryFitVertical(outParams, yOffset, height,
1567                 anchorHeight, drawingLocation[1], screenLocation[1], displayFrame.top,
1568                 displayFrame.bottom, false);
1569 
1570         // Next, attempt to fit the popup horizontally without resizing.
1571         final boolean fitsHorizontal = tryFitHorizontal(outParams, xOffset, width,
1572                 anchorWidth, drawingLocation[0], screenLocation[0], displayFrame.left,
1573                 displayFrame.right, false);
1574 
1575         // If the popup still doesn't fit, attempt to scroll the parent.
1576         if (!fitsVertical || !fitsHorizontal) {
1577             final int scrollX = anchor.getScrollX();
1578             final int scrollY = anchor.getScrollY();
1579             final Rect r = new Rect(scrollX, scrollY, scrollX + width + xOffset,
1580                     scrollY + height + anchorHeight + yOffset);
1581             if (mAllowScrollingAnchorParent && anchor.requestRectangleOnScreen(r, true)) {
1582                 // Reset for the new anchor position.
1583                 anchor.getLocationInWindow(drawingLocation);
1584                 outParams.x = drawingLocation[0] + xOffset;
1585                 outParams.y = drawingLocation[1] + anchorHeight + yOffset;
1586 
1587                 // Preserve the gravity adjustment.
1588                 if (hgrav == Gravity.RIGHT) {
1589                     outParams.x -= width - anchorWidth;
1590                 }
1591             }
1592 
1593             // Try to fit the popup again and allowing resizing.
1594             tryFitVertical(outParams, yOffset, height, anchorHeight, drawingLocation[1],
1595                     screenLocation[1], displayFrame.top, displayFrame.bottom, mClipToScreen);
1596             tryFitHorizontal(outParams, xOffset, width, anchorWidth, drawingLocation[0],
1597                     screenLocation[0], displayFrame.left, displayFrame.right, mClipToScreen);
1598         }
1599 
1600         // Return whether the popup's top edge is above the anchor's top edge.
1601         return outParams.y < drawingLocation[1];
1602     }
1603 
tryFitVertical(@onNull LayoutParams outParams, int yOffset, int height, int anchorHeight, int drawingLocationY, int screenLocationY, int displayFrameTop, int displayFrameBottom, boolean allowResize)1604     private boolean tryFitVertical(@NonNull LayoutParams outParams, int yOffset, int height,
1605             int anchorHeight, int drawingLocationY, int screenLocationY, int displayFrameTop,
1606             int displayFrameBottom, boolean allowResize) {
1607         final int winOffsetY = screenLocationY - drawingLocationY;
1608         final int anchorTopInScreen = outParams.y + winOffsetY;
1609         final int spaceBelow = displayFrameBottom - anchorTopInScreen;
1610         if (anchorTopInScreen >= 0 && height <= spaceBelow) {
1611             return true;
1612         }
1613 
1614         final int spaceAbove = anchorTopInScreen - anchorHeight - displayFrameTop;
1615         if (height <= spaceAbove) {
1616             // Move everything up.
1617             if (mOverlapAnchor) {
1618                 yOffset += anchorHeight;
1619             }
1620             outParams.y = drawingLocationY - height + yOffset;
1621 
1622             return true;
1623         }
1624 
1625         if (positionInDisplayVertical(outParams, height, drawingLocationY, screenLocationY,
1626                 displayFrameTop, displayFrameBottom, allowResize)) {
1627             return true;
1628         }
1629 
1630         return false;
1631     }
1632 
positionInDisplayVertical(@onNull LayoutParams outParams, int height, int drawingLocationY, int screenLocationY, int displayFrameTop, int displayFrameBottom, boolean canResize)1633     private boolean positionInDisplayVertical(@NonNull LayoutParams outParams, int height,
1634             int drawingLocationY, int screenLocationY, int displayFrameTop, int displayFrameBottom,
1635             boolean canResize) {
1636         boolean fitsInDisplay = true;
1637 
1638         final int winOffsetY = screenLocationY - drawingLocationY;
1639         outParams.y += winOffsetY;
1640         outParams.height = height;
1641 
1642         final int bottom = outParams.y + height;
1643         if (bottom > displayFrameBottom) {
1644             // The popup is too far down, move it back in.
1645             outParams.y -= bottom - displayFrameBottom;
1646         }
1647 
1648         if (outParams.y < displayFrameTop) {
1649             // The popup is too far up, move it back in and clip if
1650             // it's still too large.
1651             outParams.y = displayFrameTop;
1652 
1653             final int displayFrameHeight = displayFrameBottom - displayFrameTop;
1654             if (canResize && height > displayFrameHeight) {
1655                 outParams.height = displayFrameHeight;
1656             } else {
1657                 fitsInDisplay = false;
1658             }
1659         }
1660 
1661         outParams.y -= winOffsetY;
1662 
1663         return fitsInDisplay;
1664     }
1665 
tryFitHorizontal(@onNull LayoutParams outParams, int xOffset, int width, int anchorWidth, int drawingLocationX, int screenLocationX, int displayFrameLeft, int displayFrameRight, boolean allowResize)1666     private boolean tryFitHorizontal(@NonNull LayoutParams outParams, int xOffset, int width,
1667             int anchorWidth, int drawingLocationX, int screenLocationX, int displayFrameLeft,
1668             int displayFrameRight, boolean allowResize) {
1669         final int winOffsetX = screenLocationX - drawingLocationX;
1670         final int anchorLeftInScreen = outParams.x + winOffsetX;
1671         final int spaceRight = displayFrameRight - anchorLeftInScreen;
1672         if (anchorLeftInScreen >= 0 && width <= spaceRight) {
1673             return true;
1674         }
1675 
1676         if (positionInDisplayHorizontal(outParams, width, drawingLocationX, screenLocationX,
1677                 displayFrameLeft, displayFrameRight, allowResize)) {
1678             return true;
1679         }
1680 
1681         return false;
1682     }
1683 
positionInDisplayHorizontal(@onNull LayoutParams outParams, int width, int drawingLocationX, int screenLocationX, int displayFrameLeft, int displayFrameRight, boolean canResize)1684     private boolean positionInDisplayHorizontal(@NonNull LayoutParams outParams, int width,
1685             int drawingLocationX, int screenLocationX, int displayFrameLeft, int displayFrameRight,
1686             boolean canResize) {
1687         boolean fitsInDisplay = true;
1688 
1689         // Use screen coordinates for comparison against display frame.
1690         final int winOffsetX = screenLocationX - drawingLocationX;
1691         outParams.x += winOffsetX;
1692 
1693         final int right = outParams.x + width;
1694         if (right > displayFrameRight) {
1695             // The popup is too far right, move it back in.
1696             outParams.x -= right - displayFrameRight;
1697         }
1698 
1699         if (outParams.x < displayFrameLeft) {
1700             // The popup is too far left, move it back in and clip if it's
1701             // still too large.
1702             outParams.x = displayFrameLeft;
1703 
1704             final int displayFrameWidth = displayFrameRight - displayFrameLeft;
1705             if (canResize && width > displayFrameWidth) {
1706                 outParams.width = displayFrameWidth;
1707             } else {
1708                 fitsInDisplay = false;
1709             }
1710         }
1711 
1712         outParams.x -= winOffsetX;
1713 
1714         return fitsInDisplay;
1715     }
1716 
1717     /**
1718      * Returns the maximum height that is available for the popup to be
1719      * completely shown. It is recommended that this height be the maximum for
1720      * the popup's height, otherwise it is possible that the popup will be
1721      * clipped.
1722      *
1723      * @param anchor The view on which the popup window must be anchored.
1724      * @return The maximum available height for the popup to be completely
1725      *         shown.
1726      */
getMaxAvailableHeight(@onNull View anchor)1727     public int getMaxAvailableHeight(@NonNull View anchor) {
1728         return getMaxAvailableHeight(anchor, 0);
1729     }
1730 
1731     /**
1732      * Returns the maximum height that is available for the popup to be
1733      * completely shown. It is recommended that this height be the maximum for
1734      * the popup's height, otherwise it is possible that the popup will be
1735      * clipped.
1736      *
1737      * @param anchor The view on which the popup window must be anchored.
1738      * @param yOffset y offset from the view's bottom edge
1739      * @return The maximum available height for the popup to be completely
1740      *         shown.
1741      */
getMaxAvailableHeight(@onNull View anchor, int yOffset)1742     public int getMaxAvailableHeight(@NonNull View anchor, int yOffset) {
1743         return getMaxAvailableHeight(anchor, yOffset, false);
1744     }
1745 
1746     /**
1747      * Returns the maximum height that is available for the popup to be
1748      * completely shown, optionally ignoring any bottom decorations such as
1749      * the input method. It is recommended that this height be the maximum for
1750      * the popup's height, otherwise it is possible that the popup will be
1751      * clipped.
1752      *
1753      * @param anchor The view on which the popup window must be anchored.
1754      * @param yOffset y offset from the view's bottom edge
1755      * @param ignoreBottomDecorations if true, the height returned will be
1756      *        all the way to the bottom of the display, ignoring any
1757      *        bottom decorations
1758      * @return The maximum available height for the popup to be completely
1759      *         shown.
1760      */
getMaxAvailableHeight( @onNull View anchor, int yOffset, boolean ignoreBottomDecorations)1761     public int getMaxAvailableHeight(
1762             @NonNull View anchor, int yOffset, boolean ignoreBottomDecorations) {
1763         final Rect displayFrame = new Rect();
1764         if (ignoreBottomDecorations) {
1765             anchor.getWindowDisplayFrame(displayFrame);
1766         } else {
1767             anchor.getWindowVisibleDisplayFrame(displayFrame);
1768         }
1769 
1770         final int[] anchorPos = mTmpDrawingLocation;
1771         anchor.getLocationOnScreen(anchorPos);
1772 
1773         final int bottomEdge = displayFrame.bottom;
1774 
1775         final int distanceToBottom;
1776         if (mOverlapAnchor) {
1777             distanceToBottom = bottomEdge - anchorPos[1] - yOffset;
1778         } else {
1779             distanceToBottom = bottomEdge - (anchorPos[1] + anchor.getHeight()) - yOffset;
1780         }
1781         final int distanceToTop = anchorPos[1] - displayFrame.top + yOffset;
1782 
1783         // anchorPos[1] is distance from anchor to top of screen
1784         int returnedHeight = Math.max(distanceToBottom, distanceToTop);
1785         if (mBackground != null) {
1786             mBackground.getPadding(mTempRect);
1787             returnedHeight -= mTempRect.top + mTempRect.bottom;
1788         }
1789 
1790         return returnedHeight;
1791     }
1792 
1793     /**
1794      * Disposes of the popup window. This method can be invoked only after
1795      * {@link #showAsDropDown(android.view.View)} has been executed. Failing
1796      * that, calling this method will have no effect.
1797      *
1798      * @see #showAsDropDown(android.view.View)
1799      */
dismiss()1800     public void dismiss() {
1801         if (!isShowing() || mIsTransitioningToDismiss) {
1802             return;
1803         }
1804 
1805         final PopupDecorView decorView = mDecorView;
1806         final View contentView = mContentView;
1807 
1808         final ViewGroup contentHolder;
1809         final ViewParent contentParent = contentView.getParent();
1810         if (contentParent instanceof ViewGroup) {
1811             contentHolder = ((ViewGroup) contentParent);
1812         } else {
1813             contentHolder = null;
1814         }
1815 
1816         // Ensure any ongoing or pending transitions are canceled.
1817         decorView.cancelTransitions();
1818 
1819         mIsShowing = false;
1820         mIsTransitioningToDismiss = true;
1821 
1822         // This method may be called as part of window detachment, in which
1823         // case the anchor view (and its root) will still return true from
1824         // isAttachedToWindow() during execution of this method; however, we
1825         // can expect the OnAttachStateChangeListener to have been called prior
1826         // to executing this method, so we can rely on that instead.
1827         final Transition exitTransition = mExitTransition;
1828         if (mIsAnchorRootAttached && exitTransition != null && decorView.isLaidOut()) {
1829             // The decor view is non-interactive and non-IME-focusable during exit transitions.
1830             final LayoutParams p = (LayoutParams) decorView.getLayoutParams();
1831             p.flags |= LayoutParams.FLAG_NOT_TOUCHABLE;
1832             p.flags |= LayoutParams.FLAG_NOT_FOCUSABLE;
1833             p.flags &= ~LayoutParams.FLAG_ALT_FOCUSABLE_IM;
1834             mWindowManager.updateViewLayout(decorView, p);
1835 
1836             // Once we start dismissing the decor view, all state (including
1837             // the anchor root) needs to be moved to the decor view since we
1838             // may open another popup while it's busy exiting.
1839             final View anchorRoot = mAnchorRoot != null ? mAnchorRoot.get() : null;
1840             final Rect epicenter = getTransitionEpicenter();
1841             exitTransition.setEpicenterCallback(new EpicenterCallback() {
1842                 @Override
1843                 public Rect onGetEpicenter(Transition transition) {
1844                     return epicenter;
1845                 }
1846             });
1847             decorView.startExitTransition(exitTransition, anchorRoot,
1848                     new TransitionListenerAdapter() {
1849                         @Override
1850                         public void onTransitionEnd(Transition transition) {
1851                             dismissImmediate(decorView, contentHolder, contentView);
1852                         }
1853                     });
1854         } else {
1855             dismissImmediate(decorView, contentHolder, contentView);
1856         }
1857 
1858         // Clears the anchor view.
1859         detachFromAnchor();
1860 
1861         if (mOnDismissListener != null) {
1862             mOnDismissListener.onDismiss();
1863         }
1864     }
1865 
1866     /**
1867      * Returns the window-relative epicenter bounds to be used by enter and
1868      * exit transitions.
1869      * <p>
1870      * <strong>Note:</strong> This is distinct from the rect passed to
1871      * {@link #setEpicenterBounds(Rect)}, which is anchor-relative.
1872      *
1873      * @return the window-relative epicenter bounds to be used by enter and
1874      *         exit transitions
1875      */
getTransitionEpicenter()1876     private Rect getTransitionEpicenter() {
1877         final View anchor = mAnchor != null ? mAnchor.get() : null;
1878         final View decor = mDecorView;
1879         if (anchor == null || decor == null) {
1880             return null;
1881         }
1882 
1883         final int[] anchorLocation = anchor.getLocationOnScreen();
1884         final int[] popupLocation = mDecorView.getLocationOnScreen();
1885 
1886         // Compute the position of the anchor relative to the popup.
1887         final Rect bounds = new Rect(0, 0, anchor.getWidth(), anchor.getHeight());
1888         bounds.offset(anchorLocation[0] - popupLocation[0], anchorLocation[1] - popupLocation[1]);
1889 
1890         // Use anchor-relative epicenter, if specified.
1891         if (mEpicenterBounds != null) {
1892             final int offsetX = bounds.left;
1893             final int offsetY = bounds.top;
1894             bounds.set(mEpicenterBounds);
1895             bounds.offset(offsetX, offsetY);
1896         }
1897 
1898         return bounds;
1899     }
1900 
1901     /**
1902      * Removes the popup from the window manager and tears down the supporting
1903      * view hierarchy, if necessary.
1904      */
dismissImmediate(View decorView, ViewGroup contentHolder, View contentView)1905     private void dismissImmediate(View decorView, ViewGroup contentHolder, View contentView) {
1906         // If this method gets called and the decor view doesn't have a parent,
1907         // then it was either never added or was already removed. That should
1908         // never happen, but it's worth checking to avoid potential crashes.
1909         if (decorView.getParent() != null) {
1910             mWindowManager.removeViewImmediate(decorView);
1911         }
1912 
1913         if (contentHolder != null) {
1914             contentHolder.removeView(contentView);
1915         }
1916 
1917         // This needs to stay until after all transitions have ended since we
1918         // need the reference to cancel transitions in preparePopup().
1919         mDecorView = null;
1920         mBackgroundView = null;
1921         mIsTransitioningToDismiss = false;
1922     }
1923 
1924     /**
1925      * Sets the listener to be called when the window is dismissed.
1926      *
1927      * @param onDismissListener The listener.
1928      */
setOnDismissListener(OnDismissListener onDismissListener)1929     public void setOnDismissListener(OnDismissListener onDismissListener) {
1930         mOnDismissListener = onDismissListener;
1931     }
1932 
1933     /**
1934      * Updates the state of the popup window, if it is currently being displayed,
1935      * from the currently set state.
1936      * <p>
1937      * This includes:
1938      * <ul>
1939      *     <li>{@link #setClippingEnabled(boolean)}</li>
1940      *     <li>{@link #setFocusable(boolean)}</li>
1941      *     <li>{@link #setIgnoreCheekPress()}</li>
1942      *     <li>{@link #setInputMethodMode(int)}</li>
1943      *     <li>{@link #setTouchable(boolean)}</li>
1944      *     <li>{@link #setAnimationStyle(int)}</li>
1945      * </ul>
1946      */
update()1947     public void update() {
1948         if (!isShowing() || mContentView == null) {
1949             return;
1950         }
1951 
1952         final WindowManager.LayoutParams p =
1953                 (WindowManager.LayoutParams) mDecorView.getLayoutParams();
1954 
1955         boolean update = false;
1956 
1957         final int newAnim = computeAnimationResource();
1958         if (newAnim != p.windowAnimations) {
1959             p.windowAnimations = newAnim;
1960             update = true;
1961         }
1962 
1963         final int newFlags = computeFlags(p.flags);
1964         if (newFlags != p.flags) {
1965             p.flags = newFlags;
1966             update = true;
1967         }
1968 
1969         final int newGravity = computeGravity();
1970         if (newGravity != p.gravity) {
1971             p.gravity = newGravity;
1972             update = true;
1973         }
1974 
1975         if (update) {
1976             setLayoutDirectionFromAnchor();
1977             mWindowManager.updateViewLayout(mDecorView, p);
1978         }
1979     }
1980 
1981     /**
1982      * Updates the dimension of the popup window.
1983      * <p>
1984      * Calling this function also updates the window with the current popup
1985      * state as described for {@link #update()}.
1986      *
1987      * @param width the new width in pixels, must be >= 0 or -1 to ignore
1988      * @param height the new height in pixels, must be >= 0 or -1 to ignore
1989      */
update(int width, int height)1990     public void update(int width, int height) {
1991         final WindowManager.LayoutParams p =
1992                 (WindowManager.LayoutParams) mDecorView.getLayoutParams();
1993         update(p.x, p.y, width, height, false);
1994     }
1995 
1996     /**
1997      * Updates the position and the dimension of the popup window.
1998      * <p>
1999      * Width and height can be set to -1 to update location only. Calling this
2000      * function also updates the window with the current popup state as
2001      * described for {@link #update()}.
2002      *
2003      * @param x the new x location
2004      * @param y the new y location
2005      * @param width the new width in pixels, must be >= 0 or -1 to ignore
2006      * @param height the new height in pixels, must be >= 0 or -1 to ignore
2007      */
update(int x, int y, int width, int height)2008     public void update(int x, int y, int width, int height) {
2009         update(x, y, width, height, false);
2010     }
2011 
2012     /**
2013      * Updates the position and the dimension of the popup window.
2014      * <p>
2015      * Width and height can be set to -1 to update location only. Calling this
2016      * function also updates the window with the current popup state as
2017      * described for {@link #update()}.
2018      *
2019      * @param x the new x location
2020      * @param y the new y location
2021      * @param width the new width in pixels, must be >= 0 or -1 to ignore
2022      * @param height the new height in pixels, must be >= 0 or -1 to ignore
2023      * @param force {@code true} to reposition the window even if the specified
2024      *              position already seems to correspond to the LayoutParams,
2025      *              {@code false} to only reposition if needed
2026      */
update(int x, int y, int width, int height, boolean force)2027     public void update(int x, int y, int width, int height, boolean force) {
2028         if (width >= 0) {
2029             mLastWidth = width;
2030             setWidth(width);
2031         }
2032 
2033         if (height >= 0) {
2034             mLastHeight = height;
2035             setHeight(height);
2036         }
2037 
2038         if (!isShowing() || mContentView == null) {
2039             return;
2040         }
2041 
2042         final WindowManager.LayoutParams p =
2043                 (WindowManager.LayoutParams) mDecorView.getLayoutParams();
2044 
2045         boolean update = force;
2046 
2047         final int finalWidth = mWidthMode < 0 ? mWidthMode : mLastWidth;
2048         if (width != -1 && p.width != finalWidth) {
2049             p.width = mLastWidth = finalWidth;
2050             update = true;
2051         }
2052 
2053         final int finalHeight = mHeightMode < 0 ? mHeightMode : mLastHeight;
2054         if (height != -1 && p.height != finalHeight) {
2055             p.height = mLastHeight = finalHeight;
2056             update = true;
2057         }
2058 
2059         if (p.x != x) {
2060             p.x = x;
2061             update = true;
2062         }
2063 
2064         if (p.y != y) {
2065             p.y = y;
2066             update = true;
2067         }
2068 
2069         final int newAnim = computeAnimationResource();
2070         if (newAnim != p.windowAnimations) {
2071             p.windowAnimations = newAnim;
2072             update = true;
2073         }
2074 
2075         final int newFlags = computeFlags(p.flags);
2076         if (newFlags != p.flags) {
2077             p.flags = newFlags;
2078             update = true;
2079         }
2080 
2081         final int newGravity = computeGravity();
2082         if (newGravity != p.gravity) {
2083             p.gravity = newGravity;
2084             update = true;
2085         }
2086 
2087         int newAccessibilityIdOfAnchor =
2088                 (mAnchor != null) ? mAnchor.get().getAccessibilityViewId() : -1;
2089         if (newAccessibilityIdOfAnchor != p.accessibilityIdOfAnchor) {
2090             p.accessibilityIdOfAnchor = newAccessibilityIdOfAnchor;
2091             update = true;
2092         }
2093 
2094         if (update) {
2095             setLayoutDirectionFromAnchor();
2096             mWindowManager.updateViewLayout(mDecorView, p);
2097         }
2098     }
2099 
2100     /**
2101      * Updates the position and the dimension of the popup window.
2102      * <p>
2103      * Calling this function also updates the window with the current popup
2104      * state as described for {@link #update()}.
2105      *
2106      * @param anchor the popup's anchor view
2107      * @param width the new width in pixels, must be >= 0 or -1 to ignore
2108      * @param height the new height in pixels, must be >= 0 or -1 to ignore
2109      */
2110     public void update(View anchor, int width, int height) {
2111         update(anchor, false, 0, 0, width, height);
2112     }
2113 
2114     /**
2115      * Updates the position and the dimension of the popup window.
2116      * <p>
2117      * Width and height can be set to -1 to update location only. Calling this
2118      * function also updates the window with the current popup state as
2119      * described for {@link #update()}.
2120      * <p>
2121      * If the view later scrolls to move {@code anchor} to a different
2122      * location, the popup will be moved correspondingly.
2123      *
2124      * @param anchor the popup's anchor view
2125      * @param xoff x offset from the view's left edge
2126      * @param yoff y offset from the view's bottom edge
2127      * @param width the new width in pixels, must be >= 0 or -1 to ignore
2128      * @param height the new height in pixels, must be >= 0 or -1 to ignore
2129      */
2130     public void update(View anchor, int xoff, int yoff, int width, int height) {
2131         update(anchor, true, xoff, yoff, width, height);
2132     }
2133 
2134     private void update(View anchor, boolean updateLocation, int xoff, int yoff,
2135             int width, int height) {
2136 
2137         if (!isShowing() || mContentView == null) {
2138             return;
2139         }
2140 
2141         final WeakReference<View> oldAnchor = mAnchor;
2142         final int gravity = mAnchoredGravity;
2143 
2144         final boolean needsUpdate = updateLocation && (mAnchorXoff != xoff || mAnchorYoff != yoff);
2145         if (oldAnchor == null || oldAnchor.get() != anchor || (needsUpdate && !mIsDropdown)) {
2146             attachToAnchor(anchor, xoff, yoff, gravity);
2147         } else if (needsUpdate) {
2148             // No need to register again if this is a DropDown, showAsDropDown already did.
2149             mAnchorXoff = xoff;
2150             mAnchorYoff = yoff;
2151         }
2152 
2153         final LayoutParams p = (LayoutParams) mDecorView.getLayoutParams();
2154         final int oldGravity = p.gravity;
2155         final int oldWidth = p.width;
2156         final int oldHeight = p.height;
2157         final int oldX = p.x;
2158         final int oldY = p.y;
2159 
2160         // If an explicit width/height has not specified, use the most recent
2161         // explicitly specified value (either from setWidth/Height or update).
2162         if (width < 0) {
2163             width = mWidth;
2164         }
2165         if (height < 0) {
2166             height = mHeight;
2167         }
2168 
2169         final boolean aboveAnchor = findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff,
2170                 width, height, gravity);
2171         updateAboveAnchor(aboveAnchor);
2172 
2173         final boolean paramsChanged = oldGravity != p.gravity || oldX != p.x || oldY != p.y
2174                 || oldWidth != p.width || oldHeight != p.height;
2175         // If width and mWidth were both < 0 then we have a MATCH_PARENT/WRAP_CONTENT case.
2176         // findDropDownPosition will have resolved this to absolute values,
2177         // but we don't want to update mWidth/mHeight to these absolute values.
2178         update(p.x, p.y, width < 0 ? width : p.width, height < 0 ? height : p.height, paramsChanged);
2179     }
2180 
2181     /**
2182      * Listener that is called when this popup window is dismissed.
2183      */
2184     public interface OnDismissListener {
2185         /**
2186          * Called when this popup window is dismissed.
2187          */
2188         public void onDismiss();
2189     }
2190 
2191     private void detachFromAnchor() {
2192         final View anchor = mAnchor != null ? mAnchor.get() : null;
2193         if (anchor != null) {
2194             final ViewTreeObserver vto = anchor.getViewTreeObserver();
2195             vto.removeOnScrollChangedListener(mOnScrollChangedListener);
2196         }
2197 
2198         final View anchorRoot = mAnchorRoot != null ? mAnchorRoot.get() : null;
2199         if (anchorRoot != null) {
2200             anchorRoot.removeOnAttachStateChangeListener(mOnAnchorRootDetachedListener);
2201         }
2202 
2203         mAnchor = null;
2204         mAnchorRoot = null;
2205         mIsAnchorRootAttached = false;
2206     }
2207 
2208     private void attachToAnchor(View anchor, int xoff, int yoff, int gravity) {
2209         detachFromAnchor();
2210 
2211         final ViewTreeObserver vto = anchor.getViewTreeObserver();
2212         if (vto != null) {
2213             vto.addOnScrollChangedListener(mOnScrollChangedListener);
2214         }
2215 
2216         final View anchorRoot = anchor.getRootView();
2217         anchorRoot.addOnAttachStateChangeListener(mOnAnchorRootDetachedListener);
2218 
2219         mAnchor = new WeakReference<>(anchor);
2220         mAnchorRoot = new WeakReference<>(anchorRoot);
2221         mIsAnchorRootAttached = anchorRoot.isAttachedToWindow();
2222 
2223         mAnchorXoff = xoff;
2224         mAnchorYoff = yoff;
2225         mAnchoredGravity = gravity;
2226     }
2227 
2228     private class PopupDecorView extends FrameLayout {
2229         private TransitionListenerAdapter mPendingExitListener;
2230 
2231         public PopupDecorView(Context context) {
2232             super(context);
2233         }
2234 
2235         @Override
2236         public boolean dispatchKeyEvent(KeyEvent event) {
2237             if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
2238                 if (getKeyDispatcherState() == null) {
2239                     return super.dispatchKeyEvent(event);
2240                 }
2241 
2242                 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
2243                     final KeyEvent.DispatcherState state = getKeyDispatcherState();
2244                     if (state != null) {
2245                         state.startTracking(event, this);
2246                     }
2247                     return true;
2248                 } else if (event.getAction() == KeyEvent.ACTION_UP) {
2249                     final KeyEvent.DispatcherState state = getKeyDispatcherState();
2250                     if (state != null && state.isTracking(event) && !event.isCanceled()) {
2251                         dismiss();
2252                         return true;
2253                     }
2254                 }
2255                 return super.dispatchKeyEvent(event);
2256             } else {
2257                 return super.dispatchKeyEvent(event);
2258             }
2259         }
2260 
2261         @Override
2262         public boolean dispatchTouchEvent(MotionEvent ev) {
2263             if (mTouchInterceptor != null && mTouchInterceptor.onTouch(this, ev)) {
2264                 return true;
2265             }
2266             return super.dispatchTouchEvent(ev);
2267         }
2268 
2269         @Override
2270         public boolean onTouchEvent(MotionEvent event) {
2271             final int x = (int) event.getX();
2272             final int y = (int) event.getY();
2273 
2274             if ((event.getAction() == MotionEvent.ACTION_DOWN)
2275                     && ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) {
2276                 dismiss();
2277                 return true;
2278             } else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
2279                 dismiss();
2280                 return true;
2281             } else {
2282                 return super.onTouchEvent(event);
2283             }
2284         }
2285 
2286         /**
2287          * Requests that an enter transition run after the next layout pass.
2288          */
2289         public void requestEnterTransition(Transition transition) {
2290             final ViewTreeObserver observer = getViewTreeObserver();
2291             if (observer != null && transition != null) {
2292                 final Transition enterTransition = transition.clone();
2293 
2294                 // Postpone the enter transition after the first layout pass.
2295                 observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
2296                     @Override
2297                     public void onGlobalLayout() {
2298                         final ViewTreeObserver observer = getViewTreeObserver();
2299                         if (observer != null) {
2300                             observer.removeOnGlobalLayoutListener(this);
2301                         }
2302 
2303                         final Rect epicenter = getTransitionEpicenter();
2304                         enterTransition.setEpicenterCallback(new EpicenterCallback() {
2305                             @Override
2306                             public Rect onGetEpicenter(Transition transition) {
2307                                 return epicenter;
2308                             }
2309                         });
2310                         startEnterTransition(enterTransition);
2311                     }
2312                 });
2313             }
2314         }
2315 
2316         /**
2317          * Starts the pending enter transition, if one is set.
2318          */
2319         private void startEnterTransition(Transition enterTransition) {
2320             final int count = getChildCount();
2321             for (int i = 0; i < count; i++) {
2322                 final View child = getChildAt(i);
2323                 enterTransition.addTarget(child);
2324                 child.setVisibility(View.INVISIBLE);
2325             }
2326 
2327             TransitionManager.beginDelayedTransition(this, enterTransition);
2328 
2329             for (int i = 0; i < count; i++) {
2330                 final View child = getChildAt(i);
2331                 child.setVisibility(View.VISIBLE);
2332             }
2333         }
2334 
2335         /**
2336          * Starts an exit transition immediately.
2337          * <p>
2338          * <strong>Note:</strong> The transition listener is guaranteed to have
2339          * its {@code onTransitionEnd} method called even if the transition
2340          * never starts; however, it may be called with a {@code null} argument.
2341          */
2342         public void startExitTransition(Transition transition, final View anchorRoot,
2343                 final TransitionListener listener) {
2344             if (transition == null) {
2345                 return;
2346             }
2347 
2348             // The anchor view's window may go away while we're executing our
2349             // transition, in which case we need to end the transition
2350             // immediately and execute the listener to remove the popup.
2351             anchorRoot.addOnAttachStateChangeListener(mOnAnchorRootDetachedListener);
2352 
2353             // The exit listener MUST be called for cleanup, even if the
2354             // transition never starts or ends. Stash it for later.
2355             mPendingExitListener = new TransitionListenerAdapter() {
2356                 @Override
2357                 public void onTransitionEnd(Transition transition) {
2358                     anchorRoot.removeOnAttachStateChangeListener(mOnAnchorRootDetachedListener);
2359                     listener.onTransitionEnd(transition);
2360 
2361                     // The listener was called. Our job here is done.
2362                     mPendingExitListener = null;
2363                 }
2364             };
2365 
2366             final Transition exitTransition = transition.clone();
2367             exitTransition.addListener(mPendingExitListener);
2368 
2369             final int count = getChildCount();
2370             for (int i = 0; i < count; i++) {
2371                 final View child = getChildAt(i);
2372                 exitTransition.addTarget(child);
2373             }
2374 
2375             TransitionManager.beginDelayedTransition(this, exitTransition);
2376 
2377             for (int i = 0; i < count; i++) {
2378                 final View child = getChildAt(i);
2379                 child.setVisibility(View.INVISIBLE);
2380             }
2381         }
2382 
2383         /**
2384          * Cancels all pending or current transitions.
2385          */
2386         public void cancelTransitions() {
2387             TransitionManager.endTransitions(this);
2388 
2389             if (mPendingExitListener != null) {
2390                 mPendingExitListener.onTransitionEnd(null);
2391             }
2392         }
2393 
2394         private final OnAttachStateChangeListener mOnAnchorRootDetachedListener =
2395                 new OnAttachStateChangeListener() {
2396                     @Override
2397                     public void onViewAttachedToWindow(View v) {}
2398 
2399                     @Override
2400                     public void onViewDetachedFromWindow(View v) {
2401                         v.removeOnAttachStateChangeListener(this);
2402 
2403                         TransitionManager.endTransitions(PopupDecorView.this);
2404                     }
2405                 };
2406     }
2407 
2408     private class PopupBackgroundView extends FrameLayout {
2409         public PopupBackgroundView(Context context) {
2410             super(context);
2411         }
2412 
2413         @Override
2414         protected int[] onCreateDrawableState(int extraSpace) {
2415             if (mAboveAnchor) {
2416                 final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
2417                 View.mergeDrawableStates(drawableState, ABOVE_ANCHOR_STATE_SET);
2418                 return drawableState;
2419             } else {
2420                 return super.onCreateDrawableState(extraSpace);
2421             }
2422         }
2423     }
2424 }
2425