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