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