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         if (p.accessibilityTitle == null) {
   1338             p.accessibilityTitle = mContext.getString(R.string.popup_window_default_title);
   1339         }
   1340 
   1341         // The old decor view may be transitioning out. Make sure it finishes
   1342         // and cleans up before we try to create another one.
   1343         if (mDecorView != null) {
   1344             mDecorView.cancelTransitions();
   1345         }
   1346 
   1347         // When a background is available, we embed the content view within
   1348         // another view that owns the background drawable.
   1349         if (mBackground != null) {
   1350             mBackgroundView = createBackgroundView(mContentView);
   1351             mBackgroundView.setBackground(mBackground);
   1352         } else {
   1353             mBackgroundView = mContentView;
   1354         }
   1355 
   1356         mDecorView = createDecorView(mBackgroundView);
   1357         mDecorView.setIsRootNamespace(true);
   1358 
   1359         // The background owner should be elevated so that it casts a shadow.
   1360         mBackgroundView.setElevation(mElevation);
   1361 
   1362         // We may wrap that in another view, so we'll need to manually specify
   1363         // the surface insets.
   1364         p.setSurfaceInsets(mBackgroundView, true /*manual*/, true /*preservePrevious*/);
   1365 
   1366         mPopupViewInitialLayoutDirectionInherited =
   1367                 (mContentView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT);
   1368     }
   1369 
   1370     /**
   1371      * Wraps a content view in a PopupViewContainer.
   1372      *
   1373      * @param contentView the content view to wrap
   1374      * @return a PopupViewContainer that wraps the content view
   1375      */
   1376     private PopupBackgroundView createBackgroundView(View contentView) {
   1377         final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();
   1378         final int height;
   1379         if (layoutParams != null && layoutParams.height == WRAP_CONTENT) {
   1380             height = WRAP_CONTENT;
   1381         } else {
   1382             height = MATCH_PARENT;
   1383         }
   1384 
   1385         final PopupBackgroundView backgroundView = new PopupBackgroundView(mContext);
   1386         final PopupBackgroundView.LayoutParams listParams = new PopupBackgroundView.LayoutParams(
   1387                 MATCH_PARENT, height);
   1388         backgroundView.addView(contentView, listParams);
   1389 
   1390         return backgroundView;
   1391     }
   1392 
   1393     /**
   1394      * Wraps a content view in a FrameLayout.
   1395      *
   1396      * @param contentView the content view to wrap
   1397      * @return a FrameLayout that wraps the content view
   1398      */
   1399     private PopupDecorView createDecorView(View contentView) {
   1400         final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();
   1401         final int height;
   1402         if (layoutParams != null && layoutParams.height == WRAP_CONTENT) {
   1403             height = WRAP_CONTENT;
   1404         } else {
   1405             height = MATCH_PARENT;
   1406         }
   1407 
   1408         final PopupDecorView decorView = new PopupDecorView(mContext);
   1409         decorView.addView(contentView, MATCH_PARENT, height);
   1410         decorView.setClipChildren(false);
   1411         decorView.setClipToPadding(false);
   1412 
   1413         return decorView;
   1414     }
   1415 
   1416     /**
   1417      * <p>Invoke the popup window by adding the content view to the window
   1418      * manager.</p>
   1419      *
   1420      * <p>The content view must be non-null when this method is invoked.</p>
   1421      *
   1422      * @param p the layout parameters of the popup's content view
   1423      */
   1424     private void invokePopup(WindowManager.LayoutParams p) {
   1425         if (mContext != null) {
   1426             p.packageName = mContext.getPackageName();
   1427         }
   1428 
   1429         final PopupDecorView decorView = mDecorView;
   1430         decorView.setFitsSystemWindows(mLayoutInsetDecor);
   1431 
   1432         setLayoutDirectionFromAnchor();
   1433 
   1434         mWindowManager.addView(decorView, p);
   1435 
   1436         if (mEnterTransition != null) {
   1437             decorView.requestEnterTransition(mEnterTransition);
   1438         }
   1439     }
   1440 
   1441     private void setLayoutDirectionFromAnchor() {
   1442         if (mAnchor != null) {
   1443             View anchor = mAnchor.get();
   1444             if (anchor != null && mPopupViewInitialLayoutDirectionInherited) {
   1445                 mDecorView.setLayoutDirection(anchor.getLayoutDirection());
   1446             }
   1447         }
   1448     }
   1449 
   1450     private int computeGravity() {
   1451         int gravity = mGravity == Gravity.NO_GRAVITY ?  Gravity.START | Gravity.TOP : mGravity;
   1452         if (mIsDropdown && (mClipToScreen || mClippingEnabled)) {
   1453             gravity |= Gravity.DISPLAY_CLIP_VERTICAL;
   1454         }
   1455         return gravity;
   1456     }
   1457 
   1458     /**
   1459      * <p>Generate the layout parameters for the popup window.</p>
   1460      *
   1461      * @param token the window token used to bind the popup's window
   1462      *
   1463      * @return the layout parameters to pass to the window manager
   1464      *
   1465      * @hide
   1466      */
   1467     protected final WindowManager.LayoutParams createPopupLayoutParams(IBinder token) {
   1468         final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
   1469 
   1470         // These gravity settings put the view at the top left corner of the
   1471         // screen. The view is then positioned to the appropriate location by
   1472         // setting the x and y offsets to match the anchor's bottom-left
   1473         // corner.
   1474         p.gravity = computeGravity();
   1475         p.flags = computeFlags(p.flags);
   1476         p.type = mWindowLayoutType;
   1477         p.token = token;
   1478         p.softInputMode = mSoftInputMode;
   1479         p.windowAnimations = computeAnimationResource();
   1480 
   1481         if (mBackground != null) {
   1482             p.format = mBackground.getOpacity();
   1483         } else {
   1484             p.format = PixelFormat.TRANSLUCENT;
   1485         }
   1486 
   1487         if (mHeightMode < 0) {
   1488             p.height = mLastHeight = mHeightMode;
   1489         } else {
   1490             p.height = mLastHeight = mHeight;
   1491         }
   1492 
   1493         if (mWidthMode < 0) {
   1494             p.width = mLastWidth = mWidthMode;
   1495         } else {
   1496             p.width = mLastWidth = mWidth;
   1497         }
   1498 
   1499         p.privateFlags = PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH
   1500                 | PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME;
   1501 
   1502         // Used for debugging.
   1503         p.setTitle("PopupWindow:" + Integer.toHexString(hashCode()));
   1504 
   1505         return p;
   1506     }
   1507 
   1508     private int computeFlags(int curFlags) {
   1509         curFlags &= ~(
   1510                 WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES |
   1511                 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
   1512                 WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE |
   1513                 WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH |
   1514                 WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS |
   1515                 WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM |
   1516                 WindowManager.LayoutParams.FLAG_SPLIT_TOUCH);
   1517         if(mIgnoreCheekPress) {
   1518             curFlags |= WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
   1519         }
   1520         if (!mFocusable) {
   1521             curFlags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
   1522             if (mInputMethodMode == INPUT_METHOD_NEEDED) {
   1523                 curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
   1524             }
   1525         } else if (mInputMethodMode == INPUT_METHOD_NOT_NEEDED) {
   1526             curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
   1527         }
   1528         if (!mTouchable) {
   1529             curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
   1530         }
   1531         if (mOutsideTouchable) {
   1532             curFlags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
   1533         }
   1534         if (!mClippingEnabled || mClipToScreen) {
   1535             curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
   1536         }
   1537         if (isSplitTouchEnabled()) {
   1538             curFlags |= WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
   1539         }
   1540         if (mLayoutInScreen) {
   1541             curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
   1542         }
   1543         if (mLayoutInsetDecor) {
   1544             curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
   1545         }
   1546         if (mNotTouchModal) {
   1547             curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
   1548         }
   1549         if (mAttachedInDecor) {
   1550             curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_ATTACHED_IN_DECOR;
   1551         }
   1552         return curFlags;
   1553     }
   1554 
   1555     private int computeAnimationResource() {
   1556         if (mAnimationStyle == ANIMATION_STYLE_DEFAULT) {
   1557             if (mIsDropdown) {
   1558                 return mAboveAnchor
   1559                         ? com.android.internal.R.style.Animation_DropDownUp
   1560                         : com.android.internal.R.style.Animation_DropDownDown;
   1561             }
   1562             return 0;
   1563         }
   1564         return mAnimationStyle;
   1565     }
   1566 
   1567     /**
   1568      * Positions the popup window on screen. When the popup window is too tall
   1569      * to fit under the anchor, a parent scroll view is seeked and scrolled up
   1570      * to reclaim space. If scrolling is not possible or not enough, the popup
   1571      * window gets moved on top of the anchor.
   1572      * <p>
   1573      * The results of positioning are placed in {@code outParams}.
   1574      *
   1575      * @param anchor the view on which the popup window must be anchored
   1576      * @param outParams the layout parameters used to display the drop down
   1577      * @param xOffset absolute horizontal offset from the left of the anchor
   1578      * @param yOffset absolute vertical offset from the top of the anchor
   1579      * @param gravity horizontal gravity specifying popup alignment
   1580      * @param allowScroll whether the anchor view's parent may be scrolled
   1581      *                    when the popup window doesn't fit on screen
   1582      * @return true if the popup is translated upwards to fit on screen
   1583      *
   1584      * @hide
   1585      */
   1586     protected boolean findDropDownPosition(View anchor, WindowManager.LayoutParams outParams,
   1587             int xOffset, int yOffset, int width, int height, int gravity, boolean allowScroll) {
   1588         final int anchorHeight = anchor.getHeight();
   1589         final int anchorWidth = anchor.getWidth();
   1590         if (mOverlapAnchor) {
   1591             yOffset -= anchorHeight;
   1592         }
   1593 
   1594         // Initially, align to the bottom-left corner of the anchor plus offsets.
   1595         final int[] appScreenLocation = mTmpAppLocation;
   1596         final View appRootView = getAppRootView(anchor);
   1597         appRootView.getLocationOnScreen(appScreenLocation);
   1598 
   1599         final int[] screenLocation = mTmpScreenLocation;
   1600         anchor.getLocationOnScreen(screenLocation);
   1601 
   1602         final int[] drawingLocation = mTmpDrawingLocation;
   1603         drawingLocation[0] = screenLocation[0] - appScreenLocation[0];
   1604         drawingLocation[1] = screenLocation[1] - appScreenLocation[1];
   1605         outParams.x = drawingLocation[0] + xOffset;
   1606         outParams.y = drawingLocation[1] + anchorHeight + yOffset;
   1607 
   1608         final Rect displayFrame = new Rect();
   1609         appRootView.getWindowVisibleDisplayFrame(displayFrame);
   1610         if (width == MATCH_PARENT) {
   1611             width = displayFrame.right - displayFrame.left;
   1612         }
   1613         if (height == MATCH_PARENT) {
   1614             height = displayFrame.bottom - displayFrame.top;
   1615         }
   1616 
   1617         // Let the window manager know to align the top to y.
   1618         outParams.gravity = computeGravity();
   1619         outParams.width = width;
   1620         outParams.height = height;
   1621 
   1622         // If we need to adjust for gravity RIGHT, align to the bottom-right
   1623         // corner of the anchor (still accounting for offsets).
   1624         final int hgrav = Gravity.getAbsoluteGravity(gravity, anchor.getLayoutDirection())
   1625                 & Gravity.HORIZONTAL_GRAVITY_MASK;
   1626         if (hgrav == Gravity.RIGHT) {
   1627             outParams.x -= width - anchorWidth;
   1628         }
   1629 
   1630         // First, attempt to fit the popup vertically without resizing.
   1631         final boolean fitsVertical = tryFitVertical(outParams, yOffset, height,
   1632                 anchorHeight, drawingLocation[1], screenLocation[1], displayFrame.top,
   1633                 displayFrame.bottom, false);
   1634 
   1635         // Next, attempt to fit the popup horizontally without resizing.
   1636         final boolean fitsHorizontal = tryFitHorizontal(outParams, xOffset, width,
   1637                 anchorWidth, drawingLocation[0], screenLocation[0], displayFrame.left,
   1638                 displayFrame.right, false);
   1639 
   1640         // If the popup still doesn't fit, attempt to scroll the parent.
   1641         if (!fitsVertical || !fitsHorizontal) {
   1642             final int scrollX = anchor.getScrollX();
   1643             final int scrollY = anchor.getScrollY();
   1644             final Rect r = new Rect(scrollX, scrollY, scrollX + width + xOffset,
   1645                     scrollY + height + anchorHeight + yOffset);
   1646             if (allowScroll && anchor.requestRectangleOnScreen(r, true)) {
   1647                 // Reset for the new anchor position.
   1648                 anchor.getLocationOnScreen(screenLocation);
   1649                 drawingLocation[0] = screenLocation[0] - appScreenLocation[0];
   1650                 drawingLocation[1] = screenLocation[1] - appScreenLocation[1];
   1651                 outParams.x = drawingLocation[0] + xOffset;
   1652                 outParams.y = drawingLocation[1] + anchorHeight + yOffset;
   1653 
   1654                 // Preserve the gravity adjustment.
   1655                 if (hgrav == Gravity.RIGHT) {
   1656                     outParams.x -= width - anchorWidth;
   1657                 }
   1658             }
   1659 
   1660             // Try to fit the popup again and allowing resizing.
   1661             tryFitVertical(outParams, yOffset, height, anchorHeight, drawingLocation[1],
   1662                     screenLocation[1], displayFrame.top, displayFrame.bottom, mClipToScreen);
   1663             tryFitHorizontal(outParams, xOffset, width, anchorWidth, drawingLocation[0],
   1664                     screenLocation[0], displayFrame.left, displayFrame.right, mClipToScreen);
   1665         }
   1666 
   1667         // Return whether the popup's top edge is above the anchor's top edge.
   1668         return outParams.y < drawingLocation[1];
   1669     }
   1670 
   1671     private boolean tryFitVertical(@NonNull LayoutParams outParams, int yOffset, int height,
   1672             int anchorHeight, int drawingLocationY, int screenLocationY, int displayFrameTop,
   1673             int displayFrameBottom, boolean allowResize) {
   1674         final int winOffsetY = screenLocationY - drawingLocationY;
   1675         final int anchorTopInScreen = outParams.y + winOffsetY;
   1676         final int spaceBelow = displayFrameBottom - anchorTopInScreen;
   1677         if (anchorTopInScreen >= 0 && height <= spaceBelow) {
   1678             return true;
   1679         }
   1680 
   1681         final int spaceAbove = anchorTopInScreen - anchorHeight - displayFrameTop;
   1682         if (height <= spaceAbove) {
   1683             // Move everything up.
   1684             if (mOverlapAnchor) {
   1685                 yOffset += anchorHeight;
   1686             }
   1687             outParams.y = drawingLocationY - height + yOffset;
   1688 
   1689             return true;
   1690         }
   1691 
   1692         if (positionInDisplayVertical(outParams, height, drawingLocationY, screenLocationY,
   1693                 displayFrameTop, displayFrameBottom, allowResize)) {
   1694             return true;
   1695         }
   1696 
   1697         return false;
   1698     }
   1699 
   1700     private boolean positionInDisplayVertical(@NonNull LayoutParams outParams, int height,
   1701             int drawingLocationY, int screenLocationY, int displayFrameTop, int displayFrameBottom,
   1702             boolean canResize) {
   1703         boolean fitsInDisplay = true;
   1704 
   1705         final int winOffsetY = screenLocationY - drawingLocationY;
   1706         outParams.y += winOffsetY;
   1707         outParams.height = height;
   1708 
   1709         final int bottom = outParams.y + height;
   1710         if (bottom > displayFrameBottom) {
   1711             // The popup is too far down, move it back in.
   1712             outParams.y -= bottom - displayFrameBottom;
   1713         }
   1714 
   1715         if (outParams.y < displayFrameTop) {
   1716             // The popup is too far up, move it back in and clip if
   1717             // it's still too large.
   1718             outParams.y = displayFrameTop;
   1719 
   1720             final int displayFrameHeight = displayFrameBottom - displayFrameTop;
   1721             if (canResize && height > displayFrameHeight) {
   1722                 outParams.height = displayFrameHeight;
   1723             } else {
   1724                 fitsInDisplay = false;
   1725             }
   1726         }
   1727 
   1728         outParams.y -= winOffsetY;
   1729 
   1730         return fitsInDisplay;
   1731     }
   1732 
   1733     private boolean tryFitHorizontal(@NonNull LayoutParams outParams, int xOffset, int width,
   1734             int anchorWidth, int drawingLocationX, int screenLocationX, int displayFrameLeft,
   1735             int displayFrameRight, boolean allowResize) {
   1736         final int winOffsetX = screenLocationX - drawingLocationX;
   1737         final int anchorLeftInScreen = outParams.x + winOffsetX;
   1738         final int spaceRight = displayFrameRight - anchorLeftInScreen;
   1739         if (anchorLeftInScreen >= 0 && width <= spaceRight) {
   1740             return true;
   1741         }
   1742 
   1743         if (positionInDisplayHorizontal(outParams, width, drawingLocationX, screenLocationX,
   1744                 displayFrameLeft, displayFrameRight, allowResize)) {
   1745             return true;
   1746         }
   1747 
   1748         return false;
   1749     }
   1750 
   1751     private boolean positionInDisplayHorizontal(@NonNull LayoutParams outParams, int width,
   1752             int drawingLocationX, int screenLocationX, int displayFrameLeft, int displayFrameRight,
   1753             boolean canResize) {
   1754         boolean fitsInDisplay = true;
   1755 
   1756         // Use screen coordinates for comparison against display frame.
   1757         final int winOffsetX = screenLocationX - drawingLocationX;
   1758         outParams.x += winOffsetX;
   1759 
   1760         final int right = outParams.x + width;
   1761         if (right > displayFrameRight) {
   1762             // The popup is too far right, move it back in.
   1763             outParams.x -= right - displayFrameRight;
   1764         }
   1765 
   1766         if (outParams.x < displayFrameLeft) {
   1767             // The popup is too far left, move it back in and clip if it's
   1768             // still too large.
   1769             outParams.x = displayFrameLeft;
   1770 
   1771             final int displayFrameWidth = displayFrameRight - displayFrameLeft;
   1772             if (canResize && width > displayFrameWidth) {
   1773                 outParams.width = displayFrameWidth;
   1774             } else {
   1775                 fitsInDisplay = false;
   1776             }
   1777         }
   1778 
   1779         outParams.x -= winOffsetX;
   1780 
   1781         return fitsInDisplay;
   1782     }
   1783 
   1784     /**
   1785      * Returns the maximum height that is available for the popup to be
   1786      * completely shown. It is recommended that this height be the maximum for
   1787      * the popup's height, otherwise it is possible that the popup will be
   1788      * clipped.
   1789      *
   1790      * @param anchor The view on which the popup window must be anchored.
   1791      * @return The maximum available height for the popup to be completely
   1792      *         shown.
   1793      */
   1794     public int getMaxAvailableHeight(@NonNull View anchor) {
   1795         return getMaxAvailableHeight(anchor, 0);
   1796     }
   1797 
   1798     /**
   1799      * Returns the maximum height that is available for the popup to be
   1800      * completely shown. It is recommended that this height be the maximum for
   1801      * the popup's height, otherwise it is possible that the popup will be
   1802      * clipped.
   1803      *
   1804      * @param anchor The view on which the popup window must be anchored.
   1805      * @param yOffset y offset from the view's bottom edge
   1806      * @return The maximum available height for the popup to be completely
   1807      *         shown.
   1808      */
   1809     public int getMaxAvailableHeight(@NonNull View anchor, int yOffset) {
   1810         return getMaxAvailableHeight(anchor, yOffset, false);
   1811     }
   1812 
   1813     /**
   1814      * Returns the maximum height that is available for the popup to be
   1815      * completely shown, optionally ignoring any bottom decorations such as
   1816      * the input method. It is recommended that this height be the maximum for
   1817      * the popup's height, otherwise it is possible that the popup will be
   1818      * clipped.
   1819      *
   1820      * @param anchor The view on which the popup window must be anchored.
   1821      * @param yOffset y offset from the view's bottom edge
   1822      * @param ignoreBottomDecorations if true, the height returned will be
   1823      *        all the way to the bottom of the display, ignoring any
   1824      *        bottom decorations
   1825      * @return The maximum available height for the popup to be completely
   1826      *         shown.
   1827      */
   1828     public int getMaxAvailableHeight(
   1829             @NonNull View anchor, int yOffset, boolean ignoreBottomDecorations) {
   1830         Rect displayFrame = null;
   1831         final Rect visibleDisplayFrame = new Rect();
   1832 
   1833         final View appView = getAppRootView(anchor);
   1834         appView.getWindowVisibleDisplayFrame(visibleDisplayFrame);
   1835         if (ignoreBottomDecorations) {
   1836             // In the ignore bottom decorations case we want to
   1837             // still respect all other decorations so we use the inset visible
   1838             // frame on the top right and left and take the bottom
   1839             // value from the full frame.
   1840             displayFrame = new Rect();
   1841             anchor.getWindowDisplayFrame(displayFrame);
   1842             displayFrame.top = visibleDisplayFrame.top;
   1843             displayFrame.right = visibleDisplayFrame.right;
   1844             displayFrame.left = visibleDisplayFrame.left;
   1845         } else {
   1846             displayFrame = visibleDisplayFrame;
   1847         }
   1848 
   1849         final int[] anchorPos = mTmpDrawingLocation;
   1850         anchor.getLocationOnScreen(anchorPos);
   1851 
   1852         final int bottomEdge = displayFrame.bottom;
   1853 
   1854         final int distanceToBottom;
   1855         if (mOverlapAnchor) {
   1856             distanceToBottom = bottomEdge - anchorPos[1] - yOffset;
   1857         } else {
   1858             distanceToBottom = bottomEdge - (anchorPos[1] + anchor.getHeight()) - yOffset;
   1859         }
   1860         final int distanceToTop = anchorPos[1] - displayFrame.top + yOffset;
   1861 
   1862         // anchorPos[1] is distance from anchor to top of screen
   1863         int returnedHeight = Math.max(distanceToBottom, distanceToTop);
   1864         if (mBackground != null) {
   1865             mBackground.getPadding(mTempRect);
   1866             returnedHeight -= mTempRect.top + mTempRect.bottom;
   1867         }
   1868 
   1869         return returnedHeight;
   1870     }
   1871 
   1872     /**
   1873      * Disposes of the popup window. This method can be invoked only after
   1874      * {@link #showAsDropDown(android.view.View)} has been executed. Failing
   1875      * that, calling this method will have no effect.
   1876      *
   1877      * @see #showAsDropDown(android.view.View)
   1878      */
   1879     public void dismiss() {
   1880         if (!isShowing() || isTransitioningToDismiss()) {
   1881             return;
   1882         }
   1883 
   1884         final PopupDecorView decorView = mDecorView;
   1885         final View contentView = mContentView;
   1886 
   1887         final ViewGroup contentHolder;
   1888         final ViewParent contentParent = contentView.getParent();
   1889         if (contentParent instanceof ViewGroup) {
   1890             contentHolder = ((ViewGroup) contentParent);
   1891         } else {
   1892             contentHolder = null;
   1893         }
   1894 
   1895         // Ensure any ongoing or pending transitions are canceled.
   1896         decorView.cancelTransitions();
   1897 
   1898         mIsShowing = false;
   1899         mIsTransitioningToDismiss = true;
   1900 
   1901         // This method may be called as part of window detachment, in which
   1902         // case the anchor view (and its root) will still return true from
   1903         // isAttachedToWindow() during execution of this method; however, we
   1904         // can expect the OnAttachStateChangeListener to have been called prior
   1905         // to executing this method, so we can rely on that instead.
   1906         final Transition exitTransition = mExitTransition;
   1907         if (exitTransition != null && decorView.isLaidOut()
   1908                 && (mIsAnchorRootAttached || mAnchorRoot == null)) {
   1909             // The decor view is non-interactive and non-IME-focusable during exit transitions.
   1910             final LayoutParams p = (LayoutParams) decorView.getLayoutParams();
   1911             p.flags |= LayoutParams.FLAG_NOT_TOUCHABLE;
   1912             p.flags |= LayoutParams.FLAG_NOT_FOCUSABLE;
   1913             p.flags &= ~LayoutParams.FLAG_ALT_FOCUSABLE_IM;
   1914             mWindowManager.updateViewLayout(decorView, p);
   1915 
   1916             final View anchorRoot = mAnchorRoot != null ? mAnchorRoot.get() : null;
   1917             final Rect epicenter = getTransitionEpicenter();
   1918 
   1919             // Once we start dismissing the decor view, all state (including
   1920             // the anchor root) needs to be moved to the decor view since we
   1921             // may open another popup while it's busy exiting.
   1922             decorView.startExitTransition(exitTransition, anchorRoot, epicenter,
   1923                     new TransitionListenerAdapter() {
   1924                         @Override
   1925                         public void onTransitionEnd(Transition transition) {
   1926                             dismissImmediate(decorView, contentHolder, contentView);
   1927                         }
   1928                     });
   1929         } else {
   1930             dismissImmediate(decorView, contentHolder, contentView);
   1931         }
   1932 
   1933         // Clears the anchor view.
   1934         detachFromAnchor();
   1935 
   1936         if (mOnDismissListener != null) {
   1937             mOnDismissListener.onDismiss();
   1938         }
   1939     }
   1940 
   1941     /**
   1942      * Returns the window-relative epicenter bounds to be used by enter and
   1943      * exit transitions.
   1944      * <p>
   1945      * <strong>Note:</strong> This is distinct from the rect passed to
   1946      * {@link #setEpicenterBounds(Rect)}, which is anchor-relative.
   1947      *
   1948      * @return the window-relative epicenter bounds to be used by enter and
   1949      *         exit transitions
   1950      *
   1951      * @hide
   1952      */
   1953     protected final Rect getTransitionEpicenter() {
   1954         final View anchor = mAnchor != null ? mAnchor.get() : null;
   1955         final View decor = mDecorView;
   1956         if (anchor == null || decor == null) {
   1957             return null;
   1958         }
   1959 
   1960         final int[] anchorLocation = anchor.getLocationOnScreen();
   1961         final int[] popupLocation = mDecorView.getLocationOnScreen();
   1962 
   1963         // Compute the position of the anchor relative to the popup.
   1964         final Rect bounds = new Rect(0, 0, anchor.getWidth(), anchor.getHeight());
   1965         bounds.offset(anchorLocation[0] - popupLocation[0], anchorLocation[1] - popupLocation[1]);
   1966 
   1967         // Use anchor-relative epicenter, if specified.
   1968         if (mEpicenterBounds != null) {
   1969             final int offsetX = bounds.left;
   1970             final int offsetY = bounds.top;
   1971             bounds.set(mEpicenterBounds);
   1972             bounds.offset(offsetX, offsetY);
   1973         }
   1974 
   1975         return bounds;
   1976     }
   1977 
   1978     /**
   1979      * Removes the popup from the window manager and tears down the supporting
   1980      * view hierarchy, if necessary.
   1981      */
   1982     private void dismissImmediate(View decorView, ViewGroup contentHolder, View contentView) {
   1983         // If this method gets called and the decor view doesn't have a parent,
   1984         // then it was either never added or was already removed. That should
   1985         // never happen, but it's worth checking to avoid potential crashes.
   1986         if (decorView.getParent() != null) {
   1987             mWindowManager.removeViewImmediate(decorView);
   1988         }
   1989 
   1990         if (contentHolder != null) {
   1991             contentHolder.removeView(contentView);
   1992         }
   1993 
   1994         // This needs to stay until after all transitions have ended since we
   1995         // need the reference to cancel transitions in preparePopup().
   1996         mDecorView = null;
   1997         mBackgroundView = null;
   1998         mIsTransitioningToDismiss = false;
   1999     }
   2000 
   2001     /**
   2002      * Sets the listener to be called when the window is dismissed.
   2003      *
   2004      * @param onDismissListener The listener.
   2005      */
   2006     public void setOnDismissListener(OnDismissListener onDismissListener) {
   2007         mOnDismissListener = onDismissListener;
   2008     }
   2009 
   2010     /** @hide */
   2011     protected final OnDismissListener getOnDismissListener() {
   2012         return mOnDismissListener;
   2013     }
   2014 
   2015     /**
   2016      * Updates the state of the popup window, if it is currently being displayed,
   2017      * from the currently set state.
   2018      * <p>
   2019      * This includes:
   2020      * <ul>
   2021      *     <li>{@link #setClippingEnabled(boolean)}</li>
   2022      *     <li>{@link #setFocusable(boolean)}</li>
   2023      *     <li>{@link #setIgnoreCheekPress()}</li>
   2024      *     <li>{@link #setInputMethodMode(int)}</li>
   2025      *     <li>{@link #setTouchable(boolean)}</li>
   2026      *     <li>{@link #setAnimationStyle(int)}</li>
   2027      * </ul>
   2028      */
   2029     public void update() {
   2030         if (!isShowing() || !hasContentView()) {
   2031             return;
   2032         }
   2033 
   2034         final WindowManager.LayoutParams p = getDecorViewLayoutParams();
   2035 
   2036         boolean update = false;
   2037 
   2038         final int newAnim = computeAnimationResource();
   2039         if (newAnim != p.windowAnimations) {
   2040             p.windowAnimations = newAnim;
   2041             update = true;
   2042         }
   2043 
   2044         final int newFlags = computeFlags(p.flags);
   2045         if (newFlags != p.flags) {
   2046             p.flags = newFlags;
   2047             update = true;
   2048         }
   2049 
   2050         final int newGravity = computeGravity();
   2051         if (newGravity != p.gravity) {
   2052             p.gravity = newGravity;
   2053             update = true;
   2054         }
   2055 
   2056         if (update) {
   2057             update(mAnchor != null ? mAnchor.get() : null, p);
   2058         }
   2059     }
   2060 
   2061     /** @hide */
   2062     protected void update(View anchor, WindowManager.LayoutParams params) {
   2063         setLayoutDirectionFromAnchor();
   2064         mWindowManager.updateViewLayout(mDecorView, params);
   2065     }
   2066 
   2067     /**
   2068      * Updates the dimension of the popup window.
   2069      * <p>
   2070      * Calling this function also updates the window with the current popup
   2071      * state as described for {@link #update()}.
   2072      *
   2073      * @param width the new width in pixels, must be >= 0 or -1 to ignore
   2074      * @param height the new height in pixels, must be >= 0 or -1 to ignore
   2075      */
   2076     public void update(int width, int height) {
   2077         final WindowManager.LayoutParams p = getDecorViewLayoutParams();
   2078         update(p.x, p.y, width, height, false);
   2079     }
   2080 
   2081     /**
   2082      * Updates the position and the dimension of the popup window.
   2083      * <p>
   2084      * Width and height can be set to -1 to update location only. Calling this
   2085      * function also updates the window with the current popup state as
   2086      * described for {@link #update()}.
   2087      *
   2088      * @param x the new x location
   2089      * @param y the new y location
   2090      * @param width the new width in pixels, must be >= 0 or -1 to ignore
   2091      * @param height the new height in pixels, must be >= 0 or -1 to ignore
   2092      */
   2093     public void update(int x, int y, int width, int height) {
   2094         update(x, y, width, height, false);
   2095     }
   2096 
   2097     /**
   2098      * Updates the position and the dimension of the popup window.
   2099      * <p>
   2100      * Width and height can be set to -1 to update location only. Calling this
   2101      * function also updates the window with the current popup state as
   2102      * described for {@link #update()}.
   2103      *
   2104      * @param x the new x location
   2105      * @param y the new y location
   2106      * @param width the new width in pixels, must be >= 0 or -1 to ignore
   2107      * @param height the new height in pixels, must be >= 0 or -1 to ignore
   2108      * @param force {@code true} to reposition the window even if the specified
   2109      *              position already seems to correspond to the LayoutParams,
   2110      *              {@code false} to only reposition if needed
   2111      */
   2112     public void update(int x, int y, int width, int height, boolean force) {
   2113         if (width >= 0) {
   2114             mLastWidth = width;
   2115             setWidth(width);
   2116         }
   2117 
   2118         if (height >= 0) {
   2119             mLastHeight = height;
   2120             setHeight(height);
   2121         }
   2122 
   2123         if (!isShowing() || !hasContentView()) {
   2124             return;
   2125         }
   2126 
   2127         final WindowManager.LayoutParams p = getDecorViewLayoutParams();
   2128 
   2129         boolean update = force;
   2130 
   2131         final int finalWidth = mWidthMode < 0 ? mWidthMode : mLastWidth;
   2132         if (width != -1 && p.width != finalWidth) {
   2133             p.width = mLastWidth = finalWidth;
   2134             update = true;
   2135         }
   2136 
   2137         final int finalHeight = mHeightMode < 0 ? mHeightMode : mLastHeight;
   2138         if (height != -1 && p.height != finalHeight) {
   2139             p.height = mLastHeight = finalHeight;
   2140             update = true;
   2141         }
   2142 
   2143         if (p.x != x) {
   2144             p.x = x;
   2145             update = true;
   2146         }
   2147 
   2148         if (p.y != y) {
   2149             p.y = y;
   2150             update = true;
   2151         }
   2152 
   2153         final int newAnim = computeAnimationResource();
   2154         if (newAnim != p.windowAnimations) {
   2155             p.windowAnimations = newAnim;
   2156             update = true;
   2157         }
   2158 
   2159         final int newFlags = computeFlags(p.flags);
   2160         if (newFlags != p.flags) {
   2161             p.flags = newFlags;
   2162             update = true;
   2163         }
   2164 
   2165         final int newGravity = computeGravity();
   2166         if (newGravity != p.gravity) {
   2167             p.gravity = newGravity;
   2168             update = true;
   2169         }
   2170 
   2171         View anchor = null;
   2172         int newAccessibilityIdOfAnchor = -1;
   2173 
   2174         if (mAnchor != null && mAnchor.get() != null) {
   2175             anchor = mAnchor.get();
   2176             newAccessibilityIdOfAnchor = anchor.getAccessibilityViewId();
   2177         }
   2178 
   2179         if (newAccessibilityIdOfAnchor != p.accessibilityIdOfAnchor) {
   2180             p.accessibilityIdOfAnchor = newAccessibilityIdOfAnchor;
   2181             update = true;
   2182         }
   2183 
   2184         if (update) {
   2185             update(anchor, p);
   2186         }
   2187     }
   2188 
   2189     /** @hide */
   2190     protected boolean hasContentView() {
   2191         return mContentView != null;
   2192     }
   2193 
   2194     /** @hide */
   2195     protected boolean hasDecorView() {
   2196         return mDecorView != null;
   2197     }
   2198 
   2199     /** @hide */
   2200     protected WindowManager.LayoutParams getDecorViewLayoutParams() {
   2201         return (WindowManager.LayoutParams) mDecorView.getLayoutParams();
   2202     }
   2203 
   2204     /**
   2205      * Updates the position and the dimension of the popup window.
   2206      * <p>
   2207      * Calling this function also updates the window with the current popup
   2208      * state as described for {@link #update()}.
   2209      *
   2210      * @param anchor the popup's anchor view
   2211      * @param width the new width in pixels, must be >= 0 or -1 to ignore
   2212      * @param height the new height in pixels, must be >= 0 or -1 to ignore
   2213      */
   2214     public void update(View anchor, int width, int height) {
   2215         update(anchor, false, 0, 0, width, height);
   2216     }
   2217 
   2218     /**
   2219      * Updates the position and the dimension of the popup window.
   2220      * <p>
   2221      * Width and height can be set to -1 to update location only. Calling this
   2222      * function also updates the window with the current popup state as
   2223      * described for {@link #update()}.
   2224      * <p>
   2225      * If the view later scrolls to move {@code anchor} to a different
   2226      * location, the popup will be moved correspondingly.
   2227      *
   2228      * @param anchor the popup's anchor view
   2229      * @param xoff x offset from the view's left edge
   2230      * @param yoff y offset from the view's bottom edge
   2231      * @param width the new width in pixels, must be >= 0 or -1 to ignore
   2232      * @param height the new height in pixels, must be >= 0 or -1 to ignore
   2233      */
   2234     public void update(View anchor, int xoff, int yoff, int width, int height) {
   2235         update(anchor, true, xoff, yoff, width, height);
   2236     }
   2237 
   2238     private void update(View anchor, boolean updateLocation, int xoff, int yoff,
   2239             int width, int height) {
   2240 
   2241         if (!isShowing() || !hasContentView()) {
   2242             return;
   2243         }
   2244 
   2245         final WeakReference<View> oldAnchor = mAnchor;
   2246         final int gravity = mAnchoredGravity;
   2247 
   2248         final boolean needsUpdate = updateLocation && (mAnchorXoff != xoff || mAnchorYoff != yoff);
   2249         if (oldAnchor == null || oldAnchor.get() != anchor || (needsUpdate && !mIsDropdown)) {
   2250             attachToAnchor(anchor, xoff, yoff, gravity);
   2251         } else if (needsUpdate) {
   2252             // No need to register again if this is a DropDown, showAsDropDown already did.
   2253             mAnchorXoff = xoff;
   2254             mAnchorYoff = yoff;
   2255         }
   2256 
   2257         final WindowManager.LayoutParams p = getDecorViewLayoutParams();
   2258         final int oldGravity = p.gravity;
   2259         final int oldWidth = p.width;
   2260         final int oldHeight = p.height;
   2261         final int oldX = p.x;
   2262         final int oldY = p.y;
   2263 
   2264         // If an explicit width/height has not specified, use the most recent
   2265         // explicitly specified value (either from setWidth/Height or update).
   2266         if (width < 0) {
   2267             width = mWidth;
   2268         }
   2269         if (height < 0) {
   2270             height = mHeight;
   2271         }
   2272 
   2273         final boolean aboveAnchor = findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff,
   2274                 width, height, gravity, mAllowScrollingAnchorParent);
   2275         updateAboveAnchor(aboveAnchor);
   2276 
   2277         final boolean paramsChanged = oldGravity != p.gravity || oldX != p.x || oldY != p.y
   2278                 || oldWidth != p.width || oldHeight != p.height;
   2279 
   2280         // If width and mWidth were both < 0 then we have a MATCH_PARENT or
   2281         // WRAP_CONTENT case. findDropDownPosition will have resolved this to
   2282         // absolute values, but we don't want to update mWidth/mHeight to these
   2283         // absolute values.
   2284         final int newWidth = width < 0 ? width : p.width;
   2285         final int newHeight = height < 0 ? height : p.height;
   2286         update(p.x, p.y, newWidth, newHeight, paramsChanged);
   2287     }
   2288 
   2289     /**
   2290      * Listener that is called when this popup window is dismissed.
   2291      */
   2292     public interface OnDismissListener {
   2293         /**
   2294          * Called when this popup window is dismissed.
   2295          */
   2296         public void onDismiss();
   2297     }
   2298 
   2299     /** @hide */
   2300     protected void detachFromAnchor() {
   2301         final View anchor = getAnchor();
   2302         if (anchor != null) {
   2303             final ViewTreeObserver vto = anchor.getViewTreeObserver();
   2304             vto.removeOnScrollChangedListener(mOnScrollChangedListener);
   2305             anchor.removeOnAttachStateChangeListener(mOnAnchorDetachedListener);
   2306         }
   2307 
   2308         final View anchorRoot = mAnchorRoot != null ? mAnchorRoot.get() : null;
   2309         if (anchorRoot != null) {
   2310             anchorRoot.removeOnAttachStateChangeListener(mOnAnchorRootDetachedListener);
   2311             anchorRoot.removeOnLayoutChangeListener(mOnLayoutChangeListener);
   2312         }
   2313 
   2314         mAnchor = null;
   2315         mAnchorRoot = null;
   2316         mIsAnchorRootAttached = false;
   2317     }
   2318 
   2319     /** @hide */
   2320     protected void attachToAnchor(View anchor, int xoff, int yoff, int gravity) {
   2321         detachFromAnchor();
   2322 
   2323         final ViewTreeObserver vto = anchor.getViewTreeObserver();
   2324         if (vto != null) {
   2325             vto.addOnScrollChangedListener(mOnScrollChangedListener);
   2326         }
   2327         anchor.addOnAttachStateChangeListener(mOnAnchorDetachedListener);
   2328 
   2329         final View anchorRoot = anchor.getRootView();
   2330         anchorRoot.addOnAttachStateChangeListener(mOnAnchorRootDetachedListener);
   2331         anchorRoot.addOnLayoutChangeListener(mOnLayoutChangeListener);
   2332 
   2333         mAnchor = new WeakReference<>(anchor);
   2334         mAnchorRoot = new WeakReference<>(anchorRoot);
   2335         mIsAnchorRootAttached = anchorRoot.isAttachedToWindow();
   2336         mParentRootView = mAnchorRoot;
   2337 
   2338         mAnchorXoff = xoff;
   2339         mAnchorYoff = yoff;
   2340         mAnchoredGravity = gravity;
   2341     }
   2342 
   2343     /** @hide */
   2344     protected @Nullable View getAnchor() {
   2345         return mAnchor != null ? mAnchor.get() : null;
   2346     }
   2347 
   2348     private void alignToAnchor() {
   2349         final View anchor = mAnchor != null ? mAnchor.get() : null;
   2350         if (anchor != null && anchor.isAttachedToWindow() && hasDecorView()) {
   2351             final WindowManager.LayoutParams p = getDecorViewLayoutParams();
   2352 
   2353             updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff,
   2354                     p.width, p.height, mAnchoredGravity, false));
   2355             update(p.x, p.y, -1, -1, true);
   2356         }
   2357     }
   2358 
   2359     private View getAppRootView(View anchor) {
   2360         final View appWindowView = WindowManagerGlobal.getInstance().getWindowView(
   2361                 anchor.getApplicationWindowToken());
   2362         if (appWindowView != null) {
   2363             return appWindowView;
   2364         }
   2365         return anchor.getRootView();
   2366     }
   2367 
   2368     private class PopupDecorView extends FrameLayout {
   2369         /** Runnable used to clean up listeners after exit transition. */
   2370         private Runnable mCleanupAfterExit;
   2371 
   2372         public PopupDecorView(Context context) {
   2373             super(context);
   2374         }
   2375 
   2376         @Override
   2377         public boolean dispatchKeyEvent(KeyEvent event) {
   2378             if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
   2379                 if (getKeyDispatcherState() == null) {
   2380                     return super.dispatchKeyEvent(event);
   2381                 }
   2382 
   2383                 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
   2384                     final KeyEvent.DispatcherState state = getKeyDispatcherState();
   2385                     if (state != null) {
   2386                         state.startTracking(event, this);
   2387                     }
   2388                     return true;
   2389                 } else if (event.getAction() == KeyEvent.ACTION_UP) {
   2390                     final KeyEvent.DispatcherState state = getKeyDispatcherState();
   2391                     if (state != null && state.isTracking(event) && !event.isCanceled()) {
   2392                         dismiss();
   2393                         return true;
   2394                     }
   2395                 }
   2396                 return super.dispatchKeyEvent(event);
   2397             } else {
   2398                 return super.dispatchKeyEvent(event);
   2399             }
   2400         }
   2401 
   2402         @Override
   2403         public boolean dispatchTouchEvent(MotionEvent ev) {
   2404             if (mTouchInterceptor != null && mTouchInterceptor.onTouch(this, ev)) {
   2405                 return true;
   2406             }
   2407             return super.dispatchTouchEvent(ev);
   2408         }
   2409 
   2410         @Override
   2411         public boolean onTouchEvent(MotionEvent event) {
   2412             final int x = (int) event.getX();
   2413             final int y = (int) event.getY();
   2414 
   2415             if ((event.getAction() == MotionEvent.ACTION_DOWN)
   2416                     && ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) {
   2417                 dismiss();
   2418                 return true;
   2419             } else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
   2420                 dismiss();
   2421                 return true;
   2422             } else {
   2423                 return super.onTouchEvent(event);
   2424             }
   2425         }
   2426 
   2427         /**
   2428          * Requests that an enter transition run after the next layout pass.
   2429          */
   2430         public void requestEnterTransition(Transition transition) {
   2431             final ViewTreeObserver observer = getViewTreeObserver();
   2432             if (observer != null && transition != null) {
   2433                 final Transition enterTransition = transition.clone();
   2434 
   2435                 // Postpone the enter transition after the first layout pass.
   2436                 observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
   2437                     @Override
   2438                     public void onGlobalLayout() {
   2439                         final ViewTreeObserver observer = getViewTreeObserver();
   2440                         if (observer != null) {
   2441                             observer.removeOnGlobalLayoutListener(this);
   2442                         }
   2443 
   2444                         final Rect epicenter = getTransitionEpicenter();
   2445                         enterTransition.setEpicenterCallback(new EpicenterCallback() {
   2446                             @Override
   2447                             public Rect onGetEpicenter(Transition transition) {
   2448                                 return epicenter;
   2449                             }
   2450                         });
   2451                         startEnterTransition(enterTransition);
   2452                     }
   2453                 });
   2454             }
   2455         }
   2456 
   2457         /**
   2458          * Starts the pending enter transition, if one is set.
   2459          */
   2460         private void startEnterTransition(Transition enterTransition) {
   2461             final int count = getChildCount();
   2462             for (int i = 0; i < count; i++) {
   2463                 final View child = getChildAt(i);
   2464                 enterTransition.addTarget(child);
   2465                 child.setTransitionVisibility(View.INVISIBLE);
   2466             }
   2467 
   2468             TransitionManager.beginDelayedTransition(this, enterTransition);
   2469 
   2470             for (int i = 0; i < count; i++) {
   2471                 final View child = getChildAt(i);
   2472                 child.setTransitionVisibility(View.VISIBLE);
   2473             }
   2474         }
   2475 
   2476         /**
   2477          * Starts an exit transition immediately.
   2478          * <p>
   2479          * <strong>Note:</strong> The transition listener is guaranteed to have
   2480          * its {@code onTransitionEnd} method called even if the transition
   2481          * never starts.
   2482          */
   2483         public void startExitTransition(@NonNull Transition transition,
   2484                 @Nullable final View anchorRoot, @Nullable final Rect epicenter,
   2485                 @NonNull final TransitionListener listener) {
   2486             if (transition == null) {
   2487                 return;
   2488             }
   2489 
   2490             // The anchor view's window may go away while we're executing our
   2491             // transition, in which case we need to end the transition
   2492             // immediately and execute the listener to remove the popup.
   2493             if (anchorRoot != null) {
   2494                 anchorRoot.addOnAttachStateChangeListener(mOnAnchorRootDetachedListener);
   2495             }
   2496 
   2497             // The cleanup runnable MUST be called even if the transition is
   2498             // canceled before it starts (and thus can't call onTransitionEnd).
   2499             mCleanupAfterExit = () -> {
   2500                 listener.onTransitionEnd(transition);
   2501 
   2502                 if (anchorRoot != null) {
   2503                     anchorRoot.removeOnAttachStateChangeListener(mOnAnchorRootDetachedListener);
   2504                 }
   2505 
   2506                 // The listener was called. Our job here is done.
   2507                 mCleanupAfterExit = null;
   2508             };
   2509 
   2510             final Transition exitTransition = transition.clone();
   2511             exitTransition.addListener(new TransitionListenerAdapter() {
   2512                 @Override
   2513                 public void onTransitionEnd(Transition t) {
   2514                     t.removeListener(this);
   2515 
   2516                     // This null check shouldn't be necessary, but it's easier
   2517                     // to check here than it is to test every possible case.
   2518                     if (mCleanupAfterExit != null) {
   2519                         mCleanupAfterExit.run();
   2520                     }
   2521                 }
   2522             });
   2523             exitTransition.setEpicenterCallback(new EpicenterCallback() {
   2524                 @Override
   2525                 public Rect onGetEpicenter(Transition transition) {
   2526                     return epicenter;
   2527                 }
   2528             });
   2529 
   2530             final int count = getChildCount();
   2531             for (int i = 0; i < count; i++) {
   2532                 final View child = getChildAt(i);
   2533                 exitTransition.addTarget(child);
   2534             }
   2535 
   2536             TransitionManager.beginDelayedTransition(this, exitTransition);
   2537 
   2538             for (int i = 0; i < count; i++) {
   2539                 final View child = getChildAt(i);
   2540                 child.setVisibility(View.INVISIBLE);
   2541             }
   2542         }
   2543 
   2544         /**
   2545          * Cancels all pending or current transitions.
   2546          */
   2547         public void cancelTransitions() {
   2548             TransitionManager.endTransitions(this);
   2549 
   2550             // If the cleanup runnable is still around, that means the
   2551             // transition never started. We should run it now to clean up.
   2552             if (mCleanupAfterExit != null) {
   2553                 mCleanupAfterExit.run();
   2554             }
   2555         }
   2556 
   2557         private final OnAttachStateChangeListener mOnAnchorRootDetachedListener =
   2558                 new OnAttachStateChangeListener() {
   2559                     @Override
   2560                     public void onViewAttachedToWindow(View v) {}
   2561 
   2562                     @Override
   2563                     public void onViewDetachedFromWindow(View v) {
   2564                         v.removeOnAttachStateChangeListener(this);
   2565 
   2566                         if (isAttachedToWindow()) {
   2567                             TransitionManager.endTransitions(PopupDecorView.this);
   2568                         }
   2569                     }
   2570                 };
   2571 
   2572         @Override
   2573         public void requestKeyboardShortcuts(List<KeyboardShortcutGroup> list, int deviceId) {
   2574             if (mParentRootView != null) {
   2575                 View parentRoot = mParentRootView.get();
   2576                 if (parentRoot != null) {
   2577                     parentRoot.requestKeyboardShortcuts(list, deviceId);
   2578                 }
   2579             }
   2580         }
   2581     }
   2582 
   2583     private class PopupBackgroundView extends FrameLayout {
   2584         public PopupBackgroundView(Context context) {
   2585             super(context);
   2586         }
   2587 
   2588         @Override
   2589         protected int[] onCreateDrawableState(int extraSpace) {
   2590             if (mAboveAnchor) {
   2591                 final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
   2592                 View.mergeDrawableStates(drawableState, ABOVE_ANCHOR_STATE_SET);
   2593                 return drawableState;
   2594             } else {
   2595                 return super.onCreateDrawableState(extraSpace);
   2596             }
   2597         }
   2598     }
   2599 }
   2600