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