Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.widget;
     18 
     19 import com.android.internal.R;
     20 
     21 import android.animation.ValueAnimator;
     22 import android.content.Context;
     23 import android.content.res.Resources;
     24 import android.content.res.TypedArray;
     25 import android.graphics.Insets;
     26 import android.graphics.PixelFormat;
     27 import android.graphics.Rect;
     28 import android.graphics.drawable.Drawable;
     29 import android.graphics.drawable.StateListDrawable;
     30 import android.os.Build;
     31 import android.os.IBinder;
     32 import android.util.AttributeSet;
     33 import android.view.Gravity;
     34 import android.view.KeyEvent;
     35 import android.view.MotionEvent;
     36 import android.view.View;
     37 import android.view.View.OnTouchListener;
     38 import android.view.ViewGroup;
     39 import android.view.ViewTreeObserver;
     40 import android.view.ViewTreeObserver.OnScrollChangedListener;
     41 import android.view.WindowManager;
     42 
     43 import java.lang.ref.WeakReference;
     44 
     45 /**
     46  * <p>A popup window that can be used to display an arbitrary view. The popup
     47  * window is a floating container that appears on top of the current
     48  * activity.</p>
     49  *
     50  * @see android.widget.AutoCompleteTextView
     51  * @see android.widget.Spinner
     52  */
     53 public class PopupWindow {
     54     /**
     55      * Mode for {@link #setInputMethodMode(int)}: the requirements for the
     56      * input method should be based on the focusability of the popup.  That is
     57      * if it is focusable than it needs to work with the input method, else
     58      * it doesn't.
     59      */
     60     public static final int INPUT_METHOD_FROM_FOCUSABLE = 0;
     61 
     62     /**
     63      * Mode for {@link #setInputMethodMode(int)}: this popup always needs to
     64      * work with an input method, regardless of whether it is focusable.  This
     65      * means that it will always be displayed so that the user can also operate
     66      * the input method while it is shown.
     67      */
     68     public static final int INPUT_METHOD_NEEDED = 1;
     69 
     70     /**
     71      * Mode for {@link #setInputMethodMode(int)}: this popup never needs to
     72      * work with an input method, regardless of whether it is focusable.  This
     73      * means that it will always be displayed to use as much space on the
     74      * screen as needed, regardless of whether this covers the input method.
     75      */
     76     public static final int INPUT_METHOD_NOT_NEEDED = 2;
     77 
     78     private static final int DEFAULT_ANCHORED_GRAVITY = Gravity.TOP | Gravity.START;
     79 
     80     private Context mContext;
     81     private WindowManager mWindowManager;
     82 
     83     private boolean mIsShowing;
     84     private boolean mIsDropdown;
     85 
     86     private View mContentView;
     87     private View mPopupView;
     88     private boolean mFocusable;
     89     private int mInputMethodMode = INPUT_METHOD_FROM_FOCUSABLE;
     90     private int mSoftInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED;
     91     private boolean mTouchable = true;
     92     private boolean mOutsideTouchable = false;
     93     private boolean mClippingEnabled = true;
     94     private int mSplitTouchEnabled = -1;
     95     private boolean mLayoutInScreen;
     96     private boolean mClipToScreen;
     97     private boolean mAllowScrollingAnchorParent = true;
     98     private boolean mLayoutInsetDecor = false;
     99     private boolean mNotTouchModal;
    100     private boolean mAttachedInDecor = true;
    101     private boolean mAttachedInDecorSet = false;
    102 
    103     private OnTouchListener mTouchInterceptor;
    104 
    105     private int mWidthMode;
    106     private int mWidth;
    107     private int mLastWidth;
    108     private int mHeightMode;
    109     private int mHeight;
    110     private int mLastHeight;
    111 
    112     private int mPopupWidth;
    113     private int mPopupHeight;
    114 
    115     private float mElevation;
    116 
    117     private int[] mDrawingLocation = new int[2];
    118     private int[] mScreenLocation = new int[2];
    119     private Rect mTempRect = new Rect();
    120 
    121     private Drawable mBackground;
    122     private Drawable mAboveAnchorBackgroundDrawable;
    123     private Drawable mBelowAnchorBackgroundDrawable;
    124 
    125     // Temporary animation centers. Should be moved into window params?
    126     private int mAnchorRelativeX;
    127     private int mAnchorRelativeY;
    128 
    129     private boolean mAboveAnchor;
    130     private int mWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
    131 
    132     private OnDismissListener mOnDismissListener;
    133     private boolean mIgnoreCheekPress = false;
    134 
    135     private int mAnimationStyle = -1;
    136 
    137     private static final int[] ABOVE_ANCHOR_STATE_SET = new int[] {
    138         com.android.internal.R.attr.state_above_anchor
    139     };
    140 
    141     private WeakReference<View> mAnchor;
    142 
    143     private final OnScrollChangedListener mOnScrollChangedListener =
    144         new OnScrollChangedListener() {
    145             @Override
    146             public void onScrollChanged() {
    147                 final View anchor = mAnchor != null ? mAnchor.get() : null;
    148                 if (anchor != null && mPopupView != null) {
    149                     final WindowManager.LayoutParams p = (WindowManager.LayoutParams)
    150                             mPopupView.getLayoutParams();
    151 
    152                     updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff,
    153                             mAnchoredGravity));
    154                     update(p.x, p.y, -1, -1, true);
    155                 }
    156             }
    157         };
    158 
    159     private int mAnchorXoff, mAnchorYoff, mAnchoredGravity;
    160     private boolean mOverlapAnchor;
    161 
    162     private boolean mPopupViewInitialLayoutDirectionInherited;
    163 
    164     /**
    165      * <p>Create a new empty, non focusable popup window of dimension (0,0).</p>
    166      *
    167      * <p>The popup does provide a background.</p>
    168      */
    169     public PopupWindow(Context context) {
    170         this(context, null);
    171     }
    172 
    173     /**
    174      * <p>Create a new empty, non focusable popup window of dimension (0,0).</p>
    175      *
    176      * <p>The popup does provide a background.</p>
    177      */
    178     public PopupWindow(Context context, AttributeSet attrs) {
    179         this(context, attrs, com.android.internal.R.attr.popupWindowStyle);
    180     }
    181 
    182     /**
    183      * <p>Create a new empty, non focusable popup window of dimension (0,0).</p>
    184      *
    185      * <p>The popup does provide a background.</p>
    186      */
    187     public PopupWindow(Context context, AttributeSet attrs, int defStyleAttr) {
    188         this(context, attrs, defStyleAttr, 0);
    189     }
    190 
    191     /**
    192      * <p>Create a new, empty, non focusable popup window of dimension (0,0).</p>
    193      *
    194      * <p>The popup does not provide a background.</p>
    195      */
    196     public PopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    197         mContext = context;
    198         mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
    199 
    200         final TypedArray a = context.obtainStyledAttributes(
    201                 attrs, R.styleable.PopupWindow, defStyleAttr, defStyleRes);
    202         final Drawable bg = a.getDrawable(R.styleable.PopupWindow_popupBackground);
    203         mElevation = a.getDimension(R.styleable.PopupWindow_popupElevation, 0);
    204         mOverlapAnchor = a.getBoolean(R.styleable.PopupWindow_overlapAnchor, false);
    205 
    206         final int animStyle = a.getResourceId(R.styleable.PopupWindow_popupAnimationStyle, -1);
    207         mAnimationStyle = animStyle == R.style.Animation_PopupWindow ? -1 : animStyle;
    208 
    209         a.recycle();
    210 
    211         setBackgroundDrawable(bg);
    212     }
    213 
    214     /**
    215      * <p>Create a new empty, non focusable popup window of dimension (0,0).</p>
    216      *
    217      * <p>The popup does not provide any background. This should be handled
    218      * by the content view.</p>
    219      */
    220     public PopupWindow() {
    221         this(null, 0, 0);
    222     }
    223 
    224     /**
    225      * <p>Create a new non focusable popup window which can display the
    226      * <tt>contentView</tt>. The dimension of the window are (0,0).</p>
    227      *
    228      * <p>The popup does not provide any background. This should be handled
    229      * by the content view.</p>
    230      *
    231      * @param contentView the popup's content
    232      */
    233     public PopupWindow(View contentView) {
    234         this(contentView, 0, 0);
    235     }
    236 
    237     /**
    238      * <p>Create a new empty, non focusable popup window. The dimension of the
    239      * window must be passed to this constructor.</p>
    240      *
    241      * <p>The popup does not provide any background. This should be handled
    242      * by the content view.</p>
    243      *
    244      * @param width the popup's width
    245      * @param height the popup's height
    246      */
    247     public PopupWindow(int width, int height) {
    248         this(null, width, height);
    249     }
    250 
    251     /**
    252      * <p>Create a new non focusable popup window which can display the
    253      * <tt>contentView</tt>. The dimension of the window must be passed to
    254      * this constructor.</p>
    255      *
    256      * <p>The popup does not provide any background. This should be handled
    257      * by the content view.</p>
    258      *
    259      * @param contentView the popup's content
    260      * @param width the popup's width
    261      * @param height the popup's height
    262      */
    263     public PopupWindow(View contentView, int width, int height) {
    264         this(contentView, width, height, false);
    265     }
    266 
    267     /**
    268      * <p>Create a new popup window which can display the <tt>contentView</tt>.
    269      * The dimension of the window must be passed to this constructor.</p>
    270      *
    271      * <p>The popup does not provide any background. This should be handled
    272      * by the content view.</p>
    273      *
    274      * @param contentView the popup's content
    275      * @param width the popup's width
    276      * @param height the popup's height
    277      * @param focusable true if the popup can be focused, false otherwise
    278      */
    279     public PopupWindow(View contentView, int width, int height, boolean focusable) {
    280         if (contentView != null) {
    281             mContext = contentView.getContext();
    282             mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
    283         }
    284 
    285         setContentView(contentView);
    286         setWidth(width);
    287         setHeight(height);
    288         setFocusable(focusable);
    289     }
    290 
    291     /**
    292      * Return the drawable used as the popup window's background.
    293      *
    294      * @return the background drawable or {@code null} if not set
    295      * @see #setBackgroundDrawable(Drawable)
    296      * @attr ref android.R.styleable#PopupWindow_popupBackground
    297      */
    298     public Drawable getBackground() {
    299         return mBackground;
    300     }
    301 
    302     /**
    303      * Specifies the background drawable for this popup window. The background
    304      * can be set to {@code null}.
    305      *
    306      * @param background the popup's background
    307      * @see #getBackground()
    308      * @attr ref android.R.styleable#PopupWindow_popupBackground
    309      */
    310     public void setBackgroundDrawable(Drawable background) {
    311         mBackground = background;
    312 
    313         // If this is a StateListDrawable, try to find and store the drawable to be
    314         // used when the drop-down is placed above its anchor view, and the one to be
    315         // used when the drop-down is placed below its anchor view. We extract
    316         // the drawables ourselves to work around a problem with using refreshDrawableState
    317         // that it will take into account the padding of all drawables specified in a
    318         // StateListDrawable, thus adding superfluous padding to drop-down views.
    319         //
    320         // We assume a StateListDrawable will have a drawable for ABOVE_ANCHOR_STATE_SET and
    321         // at least one other drawable, intended for the 'below-anchor state'.
    322         if (mBackground instanceof StateListDrawable) {
    323             StateListDrawable stateList = (StateListDrawable) mBackground;
    324 
    325             // Find the above-anchor view - this one's easy, it should be labeled as such.
    326             int aboveAnchorStateIndex = stateList.getStateDrawableIndex(ABOVE_ANCHOR_STATE_SET);
    327 
    328             // Now, for the below-anchor view, look for any other drawable specified in the
    329             // StateListDrawable which is not for the above-anchor state and use that.
    330             int count = stateList.getStateCount();
    331             int belowAnchorStateIndex = -1;
    332             for (int i = 0; i < count; i++) {
    333                 if (i != aboveAnchorStateIndex) {
    334                     belowAnchorStateIndex = i;
    335                     break;
    336                 }
    337             }
    338 
    339             // Store the drawables we found, if we found them. Otherwise, set them both
    340             // to null so that we'll just use refreshDrawableState.
    341             if (aboveAnchorStateIndex != -1 && belowAnchorStateIndex != -1) {
    342                 mAboveAnchorBackgroundDrawable = stateList.getStateDrawable(aboveAnchorStateIndex);
    343                 mBelowAnchorBackgroundDrawable = stateList.getStateDrawable(belowAnchorStateIndex);
    344             } else {
    345                 mBelowAnchorBackgroundDrawable = null;
    346                 mAboveAnchorBackgroundDrawable = null;
    347             }
    348         }
    349     }
    350 
    351     /**
    352      * @return the elevation for this popup window in pixels
    353      * @see #setElevation(float)
    354      * @attr ref android.R.styleable#PopupWindow_popupElevation
    355      */
    356     public float getElevation() {
    357         return mElevation;
    358     }
    359 
    360     /**
    361      * Specifies the elevation for this popup window.
    362      *
    363      * @param elevation the popup's elevation in pixels
    364      * @see #getElevation()
    365      * @attr ref android.R.styleable#PopupWindow_popupElevation
    366      */
    367     public void setElevation(float elevation) {
    368         mElevation = elevation;
    369     }
    370 
    371     /**
    372      * <p>Return the animation style to use the popup appears and disappears</p>
    373      *
    374      * @return the animation style to use the popup appears and disappears
    375      */
    376     public int getAnimationStyle() {
    377         return mAnimationStyle;
    378     }
    379 
    380     /**
    381      * Set the flag on popup to ignore cheek press event; by default this flag
    382      * is set to false
    383      * which means the pop wont ignore cheek press dispatch events.
    384      *
    385      * <p>If the popup is showing, calling this method will take effect only
    386      * the next time the popup is shown or through a manual call to one of
    387      * the {@link #update()} methods.</p>
    388      *
    389      * @see #update()
    390      */
    391     public void setIgnoreCheekPress() {
    392         mIgnoreCheekPress = true;
    393     }
    394 
    395 
    396     /**
    397      * <p>Change the animation style resource for this popup.</p>
    398      *
    399      * <p>If the popup is showing, calling this method will take effect only
    400      * the next time the popup is shown or through a manual call to one of
    401      * the {@link #update()} methods.</p>
    402      *
    403      * @param animationStyle animation style to use when the popup appears
    404      *      and disappears.  Set to -1 for the default animation, 0 for no
    405      *      animation, or a resource identifier for an explicit animation.
    406      *
    407      * @see #update()
    408      */
    409     public void setAnimationStyle(int animationStyle) {
    410         mAnimationStyle = animationStyle;
    411     }
    412 
    413     /**
    414      * <p>Return the view used as the content of the popup window.</p>
    415      *
    416      * @return a {@link android.view.View} representing the popup's content
    417      *
    418      * @see #setContentView(android.view.View)
    419      */
    420     public View getContentView() {
    421         return mContentView;
    422     }
    423 
    424     /**
    425      * <p>Change the popup's content. The content is represented by an instance
    426      * of {@link android.view.View}.</p>
    427      *
    428      * <p>This method has no effect if called when the popup is showing.</p>
    429      *
    430      * @param contentView the new content for the popup
    431      *
    432      * @see #getContentView()
    433      * @see #isShowing()
    434      */
    435     public void setContentView(View contentView) {
    436         if (isShowing()) {
    437             return;
    438         }
    439 
    440         mContentView = contentView;
    441 
    442         if (mContext == null && mContentView != null) {
    443             mContext = mContentView.getContext();
    444         }
    445 
    446         if (mWindowManager == null && mContentView != null) {
    447             mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
    448         }
    449 
    450         // Setting the default for attachedInDecor based on SDK version here
    451         // instead of in the constructor since we might not have the context
    452         // object in the constructor. We only want to set default here if the
    453         // app hasn't already set the attachedInDecor.
    454         if (mContext != null && !mAttachedInDecorSet) {
    455             // Attach popup window in decor frame of parent window by default for
    456             // {@link Build.VERSION_CODES.LOLLIPOP_MR1} or greater. Keep current
    457             // behavior of not attaching to decor frame for older SDKs.
    458             setAttachedInDecor(mContext.getApplicationInfo().targetSdkVersion
    459                     >= Build.VERSION_CODES.LOLLIPOP_MR1);
    460         }
    461 
    462     }
    463 
    464     /**
    465      * Set a callback for all touch events being dispatched to the popup
    466      * window.
    467      */
    468     public void setTouchInterceptor(OnTouchListener l) {
    469         mTouchInterceptor = l;
    470     }
    471 
    472     /**
    473      * <p>Indicate whether the popup window can grab the focus.</p>
    474      *
    475      * @return true if the popup is focusable, false otherwise
    476      *
    477      * @see #setFocusable(boolean)
    478      */
    479     public boolean isFocusable() {
    480         return mFocusable;
    481     }
    482 
    483     /**
    484      * <p>Changes the focusability of the popup window. When focusable, the
    485      * window will grab the focus from the current focused widget if the popup
    486      * contains a focusable {@link android.view.View}.  By default a popup
    487      * window is not focusable.</p>
    488      *
    489      * <p>If the popup is showing, calling this method will take effect only
    490      * the next time the popup is shown or through a manual call to one of
    491      * the {@link #update()} methods.</p>
    492      *
    493      * @param focusable true if the popup should grab focus, false otherwise.
    494      *
    495      * @see #isFocusable()
    496      * @see #isShowing()
    497      * @see #update()
    498      */
    499     public void setFocusable(boolean focusable) {
    500         mFocusable = focusable;
    501     }
    502 
    503     /**
    504      * Return the current value in {@link #setInputMethodMode(int)}.
    505      *
    506      * @see #setInputMethodMode(int)
    507      */
    508     public int getInputMethodMode() {
    509         return mInputMethodMode;
    510 
    511     }
    512 
    513     /**
    514      * Control how the popup operates with an input method: one of
    515      * {@link #INPUT_METHOD_FROM_FOCUSABLE}, {@link #INPUT_METHOD_NEEDED},
    516      * or {@link #INPUT_METHOD_NOT_NEEDED}.
    517      *
    518      * <p>If the popup is showing, calling this method will take effect only
    519      * the next time the popup is shown or through a manual call to one of
    520      * the {@link #update()} methods.</p>
    521      *
    522      * @see #getInputMethodMode()
    523      * @see #update()
    524      */
    525     public void setInputMethodMode(int mode) {
    526         mInputMethodMode = mode;
    527     }
    528 
    529     /**
    530      * Sets the operating mode for the soft input area.
    531      *
    532      * @param mode The desired mode, see
    533      *        {@link android.view.WindowManager.LayoutParams#softInputMode}
    534      *        for the full list
    535      *
    536      * @see android.view.WindowManager.LayoutParams#softInputMode
    537      * @see #getSoftInputMode()
    538      */
    539     public void setSoftInputMode(int mode) {
    540         mSoftInputMode = mode;
    541     }
    542 
    543     /**
    544      * Returns the current value in {@link #setSoftInputMode(int)}.
    545      *
    546      * @see #setSoftInputMode(int)
    547      * @see android.view.WindowManager.LayoutParams#softInputMode
    548      */
    549     public int getSoftInputMode() {
    550         return mSoftInputMode;
    551     }
    552 
    553     /**
    554      * <p>Indicates whether the popup window receives touch events.</p>
    555      *
    556      * @return true if the popup is touchable, false otherwise
    557      *
    558      * @see #setTouchable(boolean)
    559      */
    560     public boolean isTouchable() {
    561         return mTouchable;
    562     }
    563 
    564     /**
    565      * <p>Changes the touchability of the popup window. When touchable, the
    566      * window will receive touch events, otherwise touch events will go to the
    567      * window below it. By default the window is touchable.</p>
    568      *
    569      * <p>If the popup is showing, calling this method will take effect only
    570      * the next time the popup is shown or through a manual call to one of
    571      * the {@link #update()} methods.</p>
    572      *
    573      * @param touchable true if the popup should receive touch events, false otherwise
    574      *
    575      * @see #isTouchable()
    576      * @see #isShowing()
    577      * @see #update()
    578      */
    579     public void setTouchable(boolean touchable) {
    580         mTouchable = touchable;
    581     }
    582 
    583     /**
    584      * <p>Indicates whether the popup window will be informed of touch events
    585      * outside of its window.</p>
    586      *
    587      * @return true if the popup is outside touchable, false otherwise
    588      *
    589      * @see #setOutsideTouchable(boolean)
    590      */
    591     public boolean isOutsideTouchable() {
    592         return mOutsideTouchable;
    593     }
    594 
    595     /**
    596      * <p>Controls whether the pop-up will be informed of touch events outside
    597      * of its window.  This only makes sense for pop-ups that are touchable
    598      * but not focusable, which means touches outside of the window will
    599      * be delivered to the window behind.  The default is false.</p>
    600      *
    601      * <p>If the popup is showing, calling this method will take effect only
    602      * the next time the popup is shown or through a manual call to one of
    603      * the {@link #update()} methods.</p>
    604      *
    605      * @param touchable true if the popup should receive outside
    606      * touch events, false otherwise
    607      *
    608      * @see #isOutsideTouchable()
    609      * @see #isShowing()
    610      * @see #update()
    611      */
    612     public void setOutsideTouchable(boolean touchable) {
    613         mOutsideTouchable = touchable;
    614     }
    615 
    616     /**
    617      * <p>Indicates whether clipping of the popup window is enabled.</p>
    618      *
    619      * @return true if the clipping is enabled, false otherwise
    620      *
    621      * @see #setClippingEnabled(boolean)
    622      */
    623     public boolean isClippingEnabled() {
    624         return mClippingEnabled;
    625     }
    626 
    627     /**
    628      * <p>Allows the popup window to extend beyond the bounds of the screen. By default the
    629      * window is clipped to the screen boundaries. Setting this to false will allow windows to be
    630      * accurately positioned.</p>
    631      *
    632      * <p>If the popup is showing, calling this method will take effect only
    633      * the next time the popup is shown or through a manual call to one of
    634      * the {@link #update()} methods.</p>
    635      *
    636      * @param enabled false if the window should be allowed to extend outside of the screen
    637      * @see #isShowing()
    638      * @see #isClippingEnabled()
    639      * @see #update()
    640      */
    641     public void setClippingEnabled(boolean enabled) {
    642         mClippingEnabled = enabled;
    643     }
    644 
    645     /**
    646      * Clip this popup window to the screen, but not to the containing window.
    647      *
    648      * @param enabled True to clip to the screen.
    649      * @hide
    650      */
    651     public void setClipToScreenEnabled(boolean enabled) {
    652         mClipToScreen = enabled;
    653         setClippingEnabled(!enabled);
    654     }
    655 
    656     /**
    657      * Allow PopupWindow to scroll the anchor's parent to provide more room
    658      * for the popup. Enabled by default.
    659      *
    660      * @param enabled True to scroll the anchor's parent when more room is desired by the popup.
    661      */
    662     void setAllowScrollingAnchorParent(boolean enabled) {
    663         mAllowScrollingAnchorParent = enabled;
    664     }
    665 
    666     /**
    667      * <p>Indicates whether the popup window supports splitting touches.</p>
    668      *
    669      * @return true if the touch splitting is enabled, false otherwise
    670      *
    671      * @see #setSplitTouchEnabled(boolean)
    672      */
    673     public boolean isSplitTouchEnabled() {
    674         if (mSplitTouchEnabled < 0 && mContext != null) {
    675             return mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB;
    676         }
    677         return mSplitTouchEnabled == 1;
    678     }
    679 
    680     /**
    681      * <p>Allows the popup window to split touches across other windows that also
    682      * support split touch.  When this flag is false, the first pointer
    683      * that goes down determines the window to which all subsequent touches
    684      * go until all pointers go up.  When this flag is true, each pointer
    685      * (not necessarily the first) that goes down determines the window
    686      * to which all subsequent touches of that pointer will go until that
    687      * pointer goes up thereby enabling touches with multiple pointers
    688      * to be split across multiple windows.</p>
    689      *
    690      * @param enabled true if the split touches should be enabled, false otherwise
    691      * @see #isSplitTouchEnabled()
    692      */
    693     public void setSplitTouchEnabled(boolean enabled) {
    694         mSplitTouchEnabled = enabled ? 1 : 0;
    695     }
    696 
    697     /**
    698      * <p>Indicates whether the popup window will be forced into using absolute screen coordinates
    699      * for positioning.</p>
    700      *
    701      * @return true if the window will always be positioned in screen coordinates.
    702      * @hide
    703      */
    704     public boolean isLayoutInScreenEnabled() {
    705         return mLayoutInScreen;
    706     }
    707 
    708     /**
    709      * <p>Allows the popup window to force the flag
    710      * {@link WindowManager.LayoutParams#FLAG_LAYOUT_IN_SCREEN}, overriding default behavior.
    711      * This will cause the popup to be positioned in absolute screen coordinates.</p>
    712      *
    713      * @param enabled true if the popup should always be positioned in screen coordinates
    714      * @hide
    715      */
    716     public void setLayoutInScreenEnabled(boolean enabled) {
    717         mLayoutInScreen = enabled;
    718     }
    719 
    720     /**
    721      * <p>Indicates whether the popup window will be attached in the decor frame of its parent
    722      * window.
    723      *
    724      * @return true if the window will be attached to the decor frame of its parent window.
    725      *
    726      * @see #setAttachedInDecor(boolean)
    727      * @see WindowManager.LayoutParams#FLAG_LAYOUT_ATTACHED_IN_DECOR
    728      */
    729     public boolean isAttachedInDecor() {
    730         return mAttachedInDecor;
    731     }
    732 
    733     /**
    734      * <p>This will attach the popup window to the decor frame of the parent window to avoid
    735      * overlaping with screen decorations like the navigation bar. Overrides the default behavior of
    736      * the flag {@link WindowManager.LayoutParams#FLAG_LAYOUT_ATTACHED_IN_DECOR}.
    737      *
    738      * <p>By default the flag is set on SDK version {@link Build.VERSION_CODES#LOLLIPOP_MR1} or
    739      * greater and cleared on lesser SDK versions.
    740      *
    741      * @param enabled true if the popup should be attached to the decor frame of its parent window.
    742      *
    743      * @see WindowManager.LayoutParams#FLAG_LAYOUT_ATTACHED_IN_DECOR
    744      */
    745     public void setAttachedInDecor(boolean enabled) {
    746         mAttachedInDecor = enabled;
    747         mAttachedInDecorSet = true;
    748     }
    749 
    750     /**
    751      * Allows the popup window to force the flag
    752      * {@link WindowManager.LayoutParams#FLAG_LAYOUT_INSET_DECOR}, overriding default behavior.
    753      * This will cause the popup to inset its content to account for system windows overlaying
    754      * the screen, such as the status bar.
    755      *
    756      * <p>This will often be combined with {@link #setLayoutInScreenEnabled(boolean)}.
    757      *
    758      * @param enabled true if the popup's views should inset content to account for system windows,
    759      *                the way that decor views behave for full-screen windows.
    760      * @hide
    761      */
    762     public void setLayoutInsetDecor(boolean enabled) {
    763         mLayoutInsetDecor = enabled;
    764     }
    765 
    766     /**
    767      * Set the layout type for this window. Should be one of the TYPE constants defined in
    768      * {@link WindowManager.LayoutParams}.
    769      *
    770      * @param layoutType Layout type for this window.
    771      * @hide
    772      */
    773     public void setWindowLayoutType(int layoutType) {
    774         mWindowLayoutType = layoutType;
    775     }
    776 
    777     /**
    778      * @return The layout type for this window.
    779      * @hide
    780      */
    781     public int getWindowLayoutType() {
    782         return mWindowLayoutType;
    783     }
    784 
    785     /**
    786      * Set whether this window is touch modal or if outside touches will be sent to
    787      * other windows behind it.
    788      * @hide
    789      */
    790     public void setTouchModal(boolean touchModal) {
    791         mNotTouchModal = !touchModal;
    792     }
    793 
    794     /**
    795      * <p>Change the width and height measure specs that are given to the
    796      * window manager by the popup.  By default these are 0, meaning that
    797      * the current width or height is requested as an explicit size from
    798      * the window manager.  You can supply
    799      * {@link ViewGroup.LayoutParams#WRAP_CONTENT} or
    800      * {@link ViewGroup.LayoutParams#MATCH_PARENT} to have that measure
    801      * spec supplied instead, replacing the absolute width and height that
    802      * has been set in the popup.</p>
    803      *
    804      * <p>If the popup is showing, calling this method will take effect only
    805      * the next time the popup is shown.</p>
    806      *
    807      * @param widthSpec an explicit width measure spec mode, either
    808      * {@link ViewGroup.LayoutParams#WRAP_CONTENT},
    809      * {@link ViewGroup.LayoutParams#MATCH_PARENT}, or 0 to use the absolute
    810      * width.
    811      * @param heightSpec an explicit height measure spec mode, either
    812      * {@link ViewGroup.LayoutParams#WRAP_CONTENT},
    813      * {@link ViewGroup.LayoutParams#MATCH_PARENT}, or 0 to use the absolute
    814      * height.
    815      */
    816     public void setWindowLayoutMode(int widthSpec, int heightSpec) {
    817         mWidthMode = widthSpec;
    818         mHeightMode = heightSpec;
    819     }
    820 
    821     /**
    822      * <p>Return this popup's height MeasureSpec</p>
    823      *
    824      * @return the height MeasureSpec of the popup
    825      *
    826      * @see #setHeight(int)
    827      */
    828     public int getHeight() {
    829         return mHeight;
    830     }
    831 
    832     /**
    833      * <p>Change the popup's height MeasureSpec</p>
    834      *
    835      * <p>If the popup is showing, calling this method will take effect only
    836      * the next time the popup is shown.</p>
    837      *
    838      * @param height the height MeasureSpec of the popup
    839      *
    840      * @see #getHeight()
    841      * @see #isShowing()
    842      */
    843     public void setHeight(int height) {
    844         mHeight = height;
    845     }
    846 
    847     /**
    848      * <p>Return this popup's width MeasureSpec</p>
    849      *
    850      * @return the width MeasureSpec of the popup
    851      *
    852      * @see #setWidth(int)
    853      */
    854     public int getWidth() {
    855         return mWidth;
    856     }
    857 
    858     /**
    859      * <p>Change the popup's width MeasureSpec</p>
    860      *
    861      * <p>If the popup is showing, calling this method will take effect only
    862      * the next time the popup is shown.</p>
    863      *
    864      * @param width the width MeasureSpec of the popup
    865      *
    866      * @see #getWidth()
    867      * @see #isShowing()
    868      */
    869     public void setWidth(int width) {
    870         mWidth = width;
    871     }
    872 
    873     /**
    874      * <p>Indicate whether this popup window is showing on screen.</p>
    875      *
    876      * @return true if the popup is showing, false otherwise
    877      */
    878     public boolean isShowing() {
    879         return mIsShowing;
    880     }
    881 
    882     /**
    883      * <p>
    884      * Display the content view in a popup window at the specified location. If the popup window
    885      * cannot fit on screen, it will be clipped. See {@link android.view.WindowManager.LayoutParams}
    886      * for more information on how gravity and the x and y parameters are related. Specifying
    887      * a gravity of {@link android.view.Gravity#NO_GRAVITY} is similar to specifying
    888      * <code>Gravity.LEFT | Gravity.TOP</code>.
    889      * </p>
    890      *
    891      * @param parent a parent view to get the {@link android.view.View#getWindowToken()} token from
    892      * @param gravity the gravity which controls the placement of the popup window
    893      * @param x the popup's x location offset
    894      * @param y the popup's y location offset
    895      */
    896     public void showAtLocation(View parent, int gravity, int x, int y) {
    897         showAtLocation(parent.getWindowToken(), gravity, x, y);
    898     }
    899 
    900     /**
    901      * Display the content view in a popup window at the specified location.
    902      *
    903      * @param token Window token to use for creating the new window
    904      * @param gravity the gravity which controls the placement of the popup window
    905      * @param x the popup's x location offset
    906      * @param y the popup's y location offset
    907      *
    908      * @hide Internal use only. Applications should use
    909      *       {@link #showAtLocation(View, int, int, int)} instead.
    910      */
    911     public void showAtLocation(IBinder token, int gravity, int x, int y) {
    912         if (isShowing() || mContentView == null) {
    913             return;
    914         }
    915 
    916         unregisterForScrollChanged();
    917 
    918         mIsShowing = true;
    919         mIsDropdown = false;
    920 
    921         WindowManager.LayoutParams p = createPopupLayout(token);
    922         p.windowAnimations = computeAnimationResource();
    923 
    924         preparePopup(p);
    925         if (gravity == Gravity.NO_GRAVITY) {
    926             gravity = Gravity.TOP | Gravity.START;
    927         }
    928         p.gravity = gravity;
    929         p.x = x;
    930         p.y = y;
    931         if (mHeightMode < 0) p.height = mLastHeight = mHeightMode;
    932         if (mWidthMode < 0) p.width = mLastWidth = mWidthMode;
    933         invokePopup(p);
    934     }
    935 
    936     /**
    937      * <p>Display the content view in a popup window anchored to the bottom-left
    938      * corner of the anchor view. If there is not enough room on screen to show
    939      * the popup in its entirety, this method tries to find a parent scroll
    940      * view to scroll. If no parent scroll view can be scrolled, the bottom-left
    941      * corner of the popup is pinned at the top left corner of the anchor view.</p>
    942      *
    943      * @param anchor the view on which to pin the popup window
    944      *
    945      * @see #dismiss()
    946      */
    947     public void showAsDropDown(View anchor) {
    948         showAsDropDown(anchor, 0, 0);
    949     }
    950 
    951     /**
    952      * <p>Display the content view in a popup window anchored to the bottom-left
    953      * corner of the anchor view offset by the specified x and y coordinates.
    954      * If there is not enough room on screen to show
    955      * the popup in its entirety, this method tries to find a parent scroll
    956      * view to scroll. If no parent scroll view can be scrolled, the bottom-left
    957      * corner of the popup is pinned at the top left corner of the anchor view.</p>
    958      * <p>If the view later scrolls to move <code>anchor</code> to a different
    959      * location, the popup will be moved correspondingly.</p>
    960      *
    961      * @param anchor the view on which to pin the popup window
    962      * @param xoff A horizontal offset from the anchor in pixels
    963      * @param yoff A vertical offset from the anchor in pixels
    964      *
    965      * @see #dismiss()
    966      */
    967     public void showAsDropDown(View anchor, int xoff, int yoff) {
    968         showAsDropDown(anchor, xoff, yoff, DEFAULT_ANCHORED_GRAVITY);
    969     }
    970 
    971     /**
    972      * <p>Display the content view in a popup window anchored to the bottom-left
    973      * corner of the anchor view offset by the specified x and y coordinates.
    974      * If there is not enough room on screen to show
    975      * the popup in its entirety, this method tries to find a parent scroll
    976      * view to scroll. If no parent scroll view can be scrolled, the bottom-left
    977      * corner of the popup is pinned at the top left corner of the anchor view.</p>
    978      * <p>If the view later scrolls to move <code>anchor</code> to a different
    979      * location, the popup will be moved correspondingly.</p>
    980      *
    981      * @param anchor the view on which to pin the popup window
    982      * @param xoff A horizontal offset from the anchor in pixels
    983      * @param yoff A vertical offset from the anchor in pixels
    984      * @param gravity Alignment of the popup relative to the anchor
    985      *
    986      * @see #dismiss()
    987      */
    988     public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
    989         if (isShowing() || mContentView == null) {
    990             return;
    991         }
    992 
    993         registerForScrollChanged(anchor, xoff, yoff, gravity);
    994 
    995         mIsShowing = true;
    996         mIsDropdown = true;
    997 
    998         WindowManager.LayoutParams p = createPopupLayout(anchor.getWindowToken());
    999         preparePopup(p);
   1000 
   1001         updateAboveAnchor(findDropDownPosition(anchor, p, xoff, yoff, gravity));
   1002 
   1003         if (mHeightMode < 0) p.height = mLastHeight = mHeightMode;
   1004         if (mWidthMode < 0) p.width = mLastWidth = mWidthMode;
   1005 
   1006         p.windowAnimations = computeAnimationResource();
   1007 
   1008         invokePopup(p);
   1009     }
   1010 
   1011     private void updateAboveAnchor(boolean aboveAnchor) {
   1012         if (aboveAnchor != mAboveAnchor) {
   1013             mAboveAnchor = aboveAnchor;
   1014 
   1015             if (mBackground != null) {
   1016                 // If the background drawable provided was a StateListDrawable with above-anchor
   1017                 // and below-anchor states, use those. Otherwise rely on refreshDrawableState to
   1018                 // do the job.
   1019                 if (mAboveAnchorBackgroundDrawable != null) {
   1020                     if (mAboveAnchor) {
   1021                         mPopupView.setBackground(mAboveAnchorBackgroundDrawable);
   1022                     } else {
   1023                         mPopupView.setBackground(mBelowAnchorBackgroundDrawable);
   1024                     }
   1025                 } else {
   1026                     mPopupView.refreshDrawableState();
   1027                 }
   1028             }
   1029         }
   1030     }
   1031 
   1032     /**
   1033      * Indicates whether the popup is showing above (the y coordinate of the popup's bottom
   1034      * is less than the y coordinate of the anchor) or below the anchor view (the y coordinate
   1035      * of the popup is greater than y coordinate of the anchor's bottom).
   1036      *
   1037      * The value returned
   1038      * by this method is meaningful only after {@link #showAsDropDown(android.view.View)}
   1039      * or {@link #showAsDropDown(android.view.View, int, int)} was invoked.
   1040      *
   1041      * @return True if this popup is showing above the anchor view, false otherwise.
   1042      */
   1043     public boolean isAboveAnchor() {
   1044         return mAboveAnchor;
   1045     }
   1046 
   1047     /**
   1048      * <p>Prepare the popup by embedding in into a new ViewGroup if the
   1049      * background drawable is not null. If embedding is required, the layout
   1050      * parameters' height is modified to take into account the background's
   1051      * padding.</p>
   1052      *
   1053      * @param p the layout parameters of the popup's content view
   1054      */
   1055     private void preparePopup(WindowManager.LayoutParams p) {
   1056         if (mContentView == null || mContext == null || mWindowManager == null) {
   1057             throw new IllegalStateException("You must specify a valid content view by "
   1058                     + "calling setContentView() before attempting to show the popup.");
   1059         }
   1060 
   1061         if (mBackground != null) {
   1062             final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();
   1063             int height = ViewGroup.LayoutParams.MATCH_PARENT;
   1064             if (layoutParams != null &&
   1065                     layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
   1066                 height = ViewGroup.LayoutParams.WRAP_CONTENT;
   1067             }
   1068 
   1069             // when a background is available, we embed the content view
   1070             // within another view that owns the background drawable
   1071             PopupViewContainer popupViewContainer = new PopupViewContainer(mContext);
   1072             PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams(
   1073                     ViewGroup.LayoutParams.MATCH_PARENT, height
   1074             );
   1075             popupViewContainer.setBackground(mBackground);
   1076             popupViewContainer.addView(mContentView, listParams);
   1077 
   1078             mPopupView = popupViewContainer;
   1079         } else {
   1080             mPopupView = mContentView;
   1081         }
   1082 
   1083         mPopupView.setElevation(mElevation);
   1084         mPopupViewInitialLayoutDirectionInherited =
   1085                 (mPopupView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT);
   1086         mPopupWidth = p.width;
   1087         mPopupHeight = p.height;
   1088     }
   1089 
   1090     /**
   1091      * <p>Invoke the popup window by adding the content view to the window
   1092      * manager.</p>
   1093      *
   1094      * <p>The content view must be non-null when this method is invoked.</p>
   1095      *
   1096      * @param p the layout parameters of the popup's content view
   1097      */
   1098     private void invokePopup(WindowManager.LayoutParams p) {
   1099         if (mContext != null) {
   1100             p.packageName = mContext.getPackageName();
   1101         }
   1102         mPopupView.setFitsSystemWindows(mLayoutInsetDecor);
   1103         setLayoutDirectionFromAnchor();
   1104         mWindowManager.addView(mPopupView, p);
   1105     }
   1106 
   1107     private void setLayoutDirectionFromAnchor() {
   1108         if (mAnchor != null) {
   1109             View anchor = mAnchor.get();
   1110             if (anchor != null && mPopupViewInitialLayoutDirectionInherited) {
   1111                 mPopupView.setLayoutDirection(anchor.getLayoutDirection());
   1112             }
   1113         }
   1114     }
   1115 
   1116     /**
   1117      * <p>Generate the layout parameters for the popup window.</p>
   1118      *
   1119      * @param token the window token used to bind the popup's window
   1120      *
   1121      * @return the layout parameters to pass to the window manager
   1122      */
   1123     private WindowManager.LayoutParams createPopupLayout(IBinder token) {
   1124         // generates the layout parameters for the drop down
   1125         // we want a fixed size view located at the bottom left of the anchor
   1126         WindowManager.LayoutParams p = new WindowManager.LayoutParams();
   1127         // these gravity settings put the view at the top left corner of the
   1128         // screen. The view is then positioned to the appropriate location
   1129         // by setting the x and y offsets to match the anchor's bottom
   1130         // left corner
   1131         p.gravity = Gravity.START | Gravity.TOP;
   1132         p.width = mLastWidth = mWidth;
   1133         p.height = mLastHeight = mHeight;
   1134         if (mBackground != null) {
   1135             p.format = mBackground.getOpacity();
   1136         } else {
   1137             p.format = PixelFormat.TRANSLUCENT;
   1138         }
   1139         p.flags = computeFlags(p.flags);
   1140         p.type = mWindowLayoutType;
   1141         p.token = token;
   1142         p.softInputMode = mSoftInputMode;
   1143         p.setTitle("PopupWindow:" + Integer.toHexString(hashCode()));
   1144 
   1145         return p;
   1146     }
   1147 
   1148     private int computeFlags(int curFlags) {
   1149         curFlags &= ~(
   1150                 WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES |
   1151                 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
   1152                 WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE |
   1153                 WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH |
   1154                 WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS |
   1155                 WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM |
   1156                 WindowManager.LayoutParams.FLAG_SPLIT_TOUCH);
   1157         if(mIgnoreCheekPress) {
   1158             curFlags |= WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
   1159         }
   1160         if (!mFocusable) {
   1161             curFlags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
   1162             if (mInputMethodMode == INPUT_METHOD_NEEDED) {
   1163                 curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
   1164             }
   1165         } else if (mInputMethodMode == INPUT_METHOD_NOT_NEEDED) {
   1166             curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
   1167         }
   1168         if (!mTouchable) {
   1169             curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
   1170         }
   1171         if (mOutsideTouchable) {
   1172             curFlags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
   1173         }
   1174         if (!mClippingEnabled) {
   1175             curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
   1176         }
   1177         if (isSplitTouchEnabled()) {
   1178             curFlags |= WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
   1179         }
   1180         if (mLayoutInScreen) {
   1181             curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
   1182         }
   1183         if (mLayoutInsetDecor) {
   1184             curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
   1185         }
   1186         if (mNotTouchModal) {
   1187             curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
   1188         }
   1189         if (mAttachedInDecor) {
   1190           curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_ATTACHED_IN_DECOR;
   1191         }
   1192         return curFlags;
   1193     }
   1194 
   1195     private int computeAnimationResource() {
   1196         if (mAnimationStyle == -1) {
   1197             if (mIsDropdown) {
   1198                 return mAboveAnchor
   1199                         ? com.android.internal.R.style.Animation_DropDownUp
   1200                         : com.android.internal.R.style.Animation_DropDownDown;
   1201             }
   1202             return 0;
   1203         }
   1204         return mAnimationStyle;
   1205     }
   1206 
   1207     /**
   1208      * Positions the popup window on screen. When the popup window is too tall
   1209      * to fit under the anchor, a parent scroll view is seeked and scrolled up
   1210      * to reclaim space. If scrolling is not possible or not enough, the popup
   1211      * window gets moved on top of the anchor.
   1212      * <p>
   1213      * The height must have been set on the layout parameters prior to calling
   1214      * this method.
   1215      *
   1216      * @param anchor the view on which the popup window must be anchored
   1217      * @param p the layout parameters used to display the drop down
   1218      * @param xoff horizontal offset used to adjust for background padding
   1219      * @param yoff vertical offset used to adjust for background padding
   1220      * @param gravity horizontal gravity specifying popup alignment
   1221      * @return true if the popup is translated upwards to fit on screen
   1222      */
   1223     private boolean findDropDownPosition(View anchor, WindowManager.LayoutParams p, int xoff,
   1224             int yoff, int gravity) {
   1225         final int anchorHeight = anchor.getHeight();
   1226         final int anchorWidth = anchor.getWidth();
   1227         if (mOverlapAnchor) {
   1228             yoff -= anchorHeight;
   1229         }
   1230 
   1231         anchor.getLocationInWindow(mDrawingLocation);
   1232         p.x = mDrawingLocation[0] + xoff;
   1233         p.y = mDrawingLocation[1] + anchorHeight + yoff;
   1234 
   1235         final int hgrav = Gravity.getAbsoluteGravity(gravity, anchor.getLayoutDirection())
   1236                 & Gravity.HORIZONTAL_GRAVITY_MASK;
   1237         if (hgrav == Gravity.RIGHT) {
   1238             // Flip the location to align the right sides of the popup and
   1239             // anchor instead of left.
   1240             p.x -= mPopupWidth - anchorWidth;
   1241         }
   1242 
   1243         boolean onTop = false;
   1244 
   1245         p.gravity = Gravity.LEFT | Gravity.TOP;
   1246 
   1247         anchor.getLocationOnScreen(mScreenLocation);
   1248         final Rect displayFrame = new Rect();
   1249         anchor.getWindowVisibleDisplayFrame(displayFrame);
   1250 
   1251         final int screenY = mScreenLocation[1] + anchorHeight + yoff;
   1252         final View root = anchor.getRootView();
   1253         if (screenY + mPopupHeight > displayFrame.bottom
   1254                 || p.x + mPopupWidth - root.getWidth() > 0) {
   1255             // If the drop down disappears at the bottom of the screen, we try
   1256             // to scroll a parent scrollview or move the drop down back up on
   1257             // top of the edit box.
   1258             if (mAllowScrollingAnchorParent) {
   1259                 final int scrollX = anchor.getScrollX();
   1260                 final int scrollY = anchor.getScrollY();
   1261                 final Rect r = new Rect(scrollX, scrollY, scrollX + mPopupWidth + xoff,
   1262                         scrollY + mPopupHeight + anchorHeight + yoff);
   1263                 anchor.requestRectangleOnScreen(r, true);
   1264             }
   1265 
   1266             // Now we re-evaluate the space available, and decide from that
   1267             // whether the pop-up will go above or below the anchor.
   1268             anchor.getLocationInWindow(mDrawingLocation);
   1269             p.x = mDrawingLocation[0] + xoff;
   1270             p.y = mDrawingLocation[1] + anchorHeight + yoff;
   1271 
   1272             // Preserve the gravity adjustment.
   1273             if (hgrav == Gravity.RIGHT) {
   1274                 p.x -= mPopupWidth - anchorWidth;
   1275             }
   1276 
   1277             // Determine whether there is more space above or below the anchor.
   1278             anchor.getLocationOnScreen(mScreenLocation);
   1279             onTop = (displayFrame.bottom - mScreenLocation[1] - anchorHeight - yoff) <
   1280                     (mScreenLocation[1] - yoff - displayFrame.top);
   1281             if (onTop) {
   1282                 p.gravity = Gravity.LEFT | Gravity.BOTTOM;
   1283                 p.y = root.getHeight() - mDrawingLocation[1] + yoff;
   1284             } else {
   1285                 p.y = mDrawingLocation[1] + anchorHeight + yoff;
   1286             }
   1287         }
   1288 
   1289         if (mClipToScreen) {
   1290             final int displayFrameWidth = displayFrame.right - displayFrame.left;
   1291             final int right = p.x + p.width;
   1292             if (right > displayFrameWidth) {
   1293                 p.x -= right - displayFrameWidth;
   1294             }
   1295 
   1296             if (p.x < displayFrame.left) {
   1297                 p.x = displayFrame.left;
   1298                 p.width = Math.min(p.width, displayFrameWidth);
   1299             }
   1300 
   1301             if (onTop) {
   1302                 final int popupTop = mScreenLocation[1] + yoff - mPopupHeight;
   1303                 if (popupTop < 0) {
   1304                     p.y += popupTop;
   1305                 }
   1306             } else {
   1307                 p.y = Math.max(p.y, displayFrame.top);
   1308             }
   1309         }
   1310 
   1311         p.gravity |= Gravity.DISPLAY_CLIP_VERTICAL;
   1312 
   1313         // Compute the position of the anchor relative to the popup.
   1314         mAnchorRelativeX = mDrawingLocation[0] - p.x + anchorHeight / 2;
   1315         mAnchorRelativeY = mDrawingLocation[1] - p.y + anchorWidth / 2;
   1316 
   1317         return onTop;
   1318     }
   1319 
   1320     /**
   1321      * Returns the maximum height that is available for the popup to be
   1322      * completely shown. It is recommended that this height be the maximum for
   1323      * the popup's height, otherwise it is possible that the popup will be
   1324      * clipped.
   1325      *
   1326      * @param anchor The view on which the popup window must be anchored.
   1327      * @return The maximum available height for the popup to be completely
   1328      *         shown.
   1329      */
   1330     public int getMaxAvailableHeight(View anchor) {
   1331         return getMaxAvailableHeight(anchor, 0);
   1332     }
   1333 
   1334     /**
   1335      * Returns the maximum height that is available for the popup to be
   1336      * completely shown. It is recommended that this height be the maximum for
   1337      * the popup's height, otherwise it is possible that the popup will be
   1338      * clipped.
   1339      *
   1340      * @param anchor The view on which the popup window must be anchored.
   1341      * @param yOffset y offset from the view's bottom edge
   1342      * @return The maximum available height for the popup to be completely
   1343      *         shown.
   1344      */
   1345     public int getMaxAvailableHeight(View anchor, int yOffset) {
   1346         return getMaxAvailableHeight(anchor, yOffset, false);
   1347     }
   1348 
   1349     /**
   1350      * Returns the maximum height that is available for the popup to be
   1351      * completely shown, optionally ignoring any bottom decorations such as
   1352      * the input method. It is recommended that this height be the maximum for
   1353      * the popup's height, otherwise it is possible that the popup will be
   1354      * clipped.
   1355      *
   1356      * @param anchor The view on which the popup window must be anchored.
   1357      * @param yOffset y offset from the view's bottom edge
   1358      * @param ignoreBottomDecorations if true, the height returned will be
   1359      *        all the way to the bottom of the display, ignoring any
   1360      *        bottom decorations
   1361      * @return The maximum available height for the popup to be completely
   1362      *         shown.
   1363      *
   1364      * @hide Pending API council approval.
   1365      */
   1366     public int getMaxAvailableHeight(View anchor, int yOffset, boolean ignoreBottomDecorations) {
   1367         final Rect displayFrame = new Rect();
   1368         anchor.getWindowVisibleDisplayFrame(displayFrame);
   1369 
   1370         final int[] anchorPos = mDrawingLocation;
   1371         anchor.getLocationOnScreen(anchorPos);
   1372 
   1373         int bottomEdge = displayFrame.bottom;
   1374         if (ignoreBottomDecorations) {
   1375             Resources res = anchor.getContext().getResources();
   1376             bottomEdge = res.getDisplayMetrics().heightPixels;
   1377         }
   1378         final int distanceToBottom = bottomEdge - (anchorPos[1] + anchor.getHeight()) - yOffset;
   1379         final int distanceToTop = anchorPos[1] - displayFrame.top + yOffset;
   1380 
   1381         // anchorPos[1] is distance from anchor to top of screen
   1382         int returnedHeight = Math.max(distanceToBottom, distanceToTop);
   1383         if (mBackground != null) {
   1384             mBackground.getPadding(mTempRect);
   1385             returnedHeight -= mTempRect.top + mTempRect.bottom;
   1386         }
   1387 
   1388         return returnedHeight;
   1389     }
   1390 
   1391     /**
   1392      * <p>Dispose of the popup window. This method can be invoked only after
   1393      * {@link #showAsDropDown(android.view.View)} has been executed. Failing that, calling
   1394      * this method will have no effect.</p>
   1395      *
   1396      * @see #showAsDropDown(android.view.View)
   1397      */
   1398     public void dismiss() {
   1399         if (isShowing() && mPopupView != null) {
   1400             mIsShowing = false;
   1401 
   1402             unregisterForScrollChanged();
   1403 
   1404             try {
   1405                 mWindowManager.removeViewImmediate(mPopupView);
   1406             } finally {
   1407                 if (mPopupView != mContentView && mPopupView instanceof ViewGroup) {
   1408                     ((ViewGroup) mPopupView).removeView(mContentView);
   1409                 }
   1410                 mPopupView = null;
   1411 
   1412                 if (mOnDismissListener != null) {
   1413                     mOnDismissListener.onDismiss();
   1414                 }
   1415             }
   1416         }
   1417     }
   1418 
   1419     /**
   1420      * Sets the listener to be called when the window is dismissed.
   1421      *
   1422      * @param onDismissListener The listener.
   1423      */
   1424     public void setOnDismissListener(OnDismissListener onDismissListener) {
   1425         mOnDismissListener = onDismissListener;
   1426     }
   1427 
   1428     /**
   1429      * Updates the state of the popup window, if it is currently being displayed,
   1430      * from the currently set state.  This includes:
   1431      * {@link #setClippingEnabled(boolean)}, {@link #setFocusable(boolean)},
   1432      * {@link #setIgnoreCheekPress()}, {@link #setInputMethodMode(int)},
   1433      * {@link #setTouchable(boolean)}, and {@link #setAnimationStyle(int)}.
   1434      */
   1435     public void update() {
   1436         if (!isShowing() || mContentView == null) {
   1437             return;
   1438         }
   1439 
   1440         WindowManager.LayoutParams p = (WindowManager.LayoutParams)
   1441                 mPopupView.getLayoutParams();
   1442 
   1443         boolean update = false;
   1444 
   1445         final int newAnim = computeAnimationResource();
   1446         if (newAnim != p.windowAnimations) {
   1447             p.windowAnimations = newAnim;
   1448             update = true;
   1449         }
   1450 
   1451         final int newFlags = computeFlags(p.flags);
   1452         if (newFlags != p.flags) {
   1453             p.flags = newFlags;
   1454             update = true;
   1455         }
   1456 
   1457         if (update) {
   1458             setLayoutDirectionFromAnchor();
   1459             mWindowManager.updateViewLayout(mPopupView, p);
   1460         }
   1461     }
   1462 
   1463     /**
   1464      * <p>Updates the dimension of the popup window. Calling this function
   1465      * also updates the window with the current popup state as described
   1466      * for {@link #update()}.</p>
   1467      *
   1468      * @param width the new width
   1469      * @param height the new height
   1470      */
   1471     public void update(int width, int height) {
   1472         WindowManager.LayoutParams p = (WindowManager.LayoutParams)
   1473                 mPopupView.getLayoutParams();
   1474         update(p.x, p.y, width, height, false);
   1475     }
   1476 
   1477     /**
   1478      * <p>Updates the position and the dimension of the popup window. Width and
   1479      * height can be set to -1 to update location only.  Calling this function
   1480      * also updates the window with the current popup state as
   1481      * described for {@link #update()}.</p>
   1482      *
   1483      * @param x the new x location
   1484      * @param y the new y location
   1485      * @param width the new width, can be -1 to ignore
   1486      * @param height the new height, can be -1 to ignore
   1487      */
   1488     public void update(int x, int y, int width, int height) {
   1489         update(x, y, width, height, false);
   1490     }
   1491 
   1492     /**
   1493      * <p>Updates the position and the dimension of the popup window. Width and
   1494      * height can be set to -1 to update location only.  Calling this function
   1495      * also updates the window with the current popup state as
   1496      * described for {@link #update()}.</p>
   1497      *
   1498      * @param x the new x location
   1499      * @param y the new y location
   1500      * @param width the new width, can be -1 to ignore
   1501      * @param height the new height, can be -1 to ignore
   1502      * @param force reposition the window even if the specified position
   1503      *              already seems to correspond to the LayoutParams
   1504      */
   1505     public void update(int x, int y, int width, int height, boolean force) {
   1506         if (width != -1) {
   1507             mLastWidth = width;
   1508             setWidth(width);
   1509         }
   1510 
   1511         if (height != -1) {
   1512             mLastHeight = height;
   1513             setHeight(height);
   1514         }
   1515 
   1516         if (!isShowing() || mContentView == null) {
   1517             return;
   1518         }
   1519 
   1520         WindowManager.LayoutParams p = (WindowManager.LayoutParams) mPopupView.getLayoutParams();
   1521 
   1522         boolean update = force;
   1523 
   1524         final int finalWidth = mWidthMode < 0 ? mWidthMode : mLastWidth;
   1525         if (width != -1 && p.width != finalWidth) {
   1526             p.width = mLastWidth = finalWidth;
   1527             update = true;
   1528         }
   1529 
   1530         final int finalHeight = mHeightMode < 0 ? mHeightMode : mLastHeight;
   1531         if (height != -1 && p.height != finalHeight) {
   1532             p.height = mLastHeight = finalHeight;
   1533             update = true;
   1534         }
   1535 
   1536         if (p.x != x) {
   1537             p.x = x;
   1538             update = true;
   1539         }
   1540 
   1541         if (p.y != y) {
   1542             p.y = y;
   1543             update = true;
   1544         }
   1545 
   1546         final int newAnim = computeAnimationResource();
   1547         if (newAnim != p.windowAnimations) {
   1548             p.windowAnimations = newAnim;
   1549             update = true;
   1550         }
   1551 
   1552         final int newFlags = computeFlags(p.flags);
   1553         if (newFlags != p.flags) {
   1554             p.flags = newFlags;
   1555             update = true;
   1556         }
   1557 
   1558         if (update) {
   1559             setLayoutDirectionFromAnchor();
   1560             mWindowManager.updateViewLayout(mPopupView, p);
   1561         }
   1562     }
   1563 
   1564     /**
   1565      * <p>Updates the position and the dimension of the popup window. Calling this
   1566      * function also updates the window with the current popup state as described
   1567      * for {@link #update()}.</p>
   1568      *
   1569      * @param anchor the popup's anchor view
   1570      * @param width the new width, can be -1 to ignore
   1571      * @param height the new height, can be -1 to ignore
   1572      */
   1573     public void update(View anchor, int width, int height) {
   1574         update(anchor, false, 0, 0, true, width, height, mAnchoredGravity);
   1575     }
   1576 
   1577     /**
   1578      * <p>Updates the position and the dimension of the popup window. Width and
   1579      * height can be set to -1 to update location only.  Calling this function
   1580      * also updates the window with the current popup state as
   1581      * described for {@link #update()}.</p>
   1582      *
   1583      * <p>If the view later scrolls to move <code>anchor</code> to a different
   1584      * location, the popup will be moved correspondingly.</p>
   1585      *
   1586      * @param anchor the popup's anchor view
   1587      * @param xoff x offset from the view's left edge
   1588      * @param yoff y offset from the view's bottom edge
   1589      * @param width the new width, can be -1 to ignore
   1590      * @param height the new height, can be -1 to ignore
   1591      */
   1592     public void update(View anchor, int xoff, int yoff, int width, int height) {
   1593         update(anchor, true, xoff, yoff, true, width, height, mAnchoredGravity);
   1594     }
   1595 
   1596     private void update(View anchor, boolean updateLocation, int xoff, int yoff,
   1597             boolean updateDimension, int width, int height, int gravity) {
   1598 
   1599         if (!isShowing() || mContentView == null) {
   1600             return;
   1601         }
   1602 
   1603         WeakReference<View> oldAnchor = mAnchor;
   1604         final boolean needsUpdate = updateLocation
   1605                 && (mAnchorXoff != xoff || mAnchorYoff != yoff);
   1606         if (oldAnchor == null || oldAnchor.get() != anchor || (needsUpdate && !mIsDropdown)) {
   1607             registerForScrollChanged(anchor, xoff, yoff, gravity);
   1608         } else if (needsUpdate) {
   1609             // No need to register again if this is a DropDown, showAsDropDown already did.
   1610             mAnchorXoff = xoff;
   1611             mAnchorYoff = yoff;
   1612             mAnchoredGravity = gravity;
   1613         }
   1614 
   1615         WindowManager.LayoutParams p = (WindowManager.LayoutParams) mPopupView.getLayoutParams();
   1616 
   1617         if (updateDimension) {
   1618             if (width == -1) {
   1619                 width = mPopupWidth;
   1620             } else {
   1621                 mPopupWidth = width;
   1622             }
   1623             if (height == -1) {
   1624                 height = mPopupHeight;
   1625             } else {
   1626                 mPopupHeight = height;
   1627             }
   1628         }
   1629 
   1630         int x = p.x;
   1631         int y = p.y;
   1632 
   1633         if (updateLocation) {
   1634             updateAboveAnchor(findDropDownPosition(anchor, p, xoff, yoff, gravity));
   1635         } else {
   1636             updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff,
   1637                     mAnchoredGravity));
   1638         }
   1639 
   1640         update(p.x, p.y, width, height, x != p.x || y != p.y);
   1641     }
   1642 
   1643     /**
   1644      * Listener that is called when this popup window is dismissed.
   1645      */
   1646     public interface OnDismissListener {
   1647         /**
   1648          * Called when this popup window is dismissed.
   1649          */
   1650         public void onDismiss();
   1651     }
   1652 
   1653     private void unregisterForScrollChanged() {
   1654         WeakReference<View> anchorRef = mAnchor;
   1655         View anchor = null;
   1656         if (anchorRef != null) {
   1657             anchor = anchorRef.get();
   1658         }
   1659         if (anchor != null) {
   1660             ViewTreeObserver vto = anchor.getViewTreeObserver();
   1661             vto.removeOnScrollChangedListener(mOnScrollChangedListener);
   1662         }
   1663         mAnchor = null;
   1664     }
   1665 
   1666     private void registerForScrollChanged(View anchor, int xoff, int yoff, int gravity) {
   1667         unregisterForScrollChanged();
   1668 
   1669         mAnchor = new WeakReference<View>(anchor);
   1670         ViewTreeObserver vto = anchor.getViewTreeObserver();
   1671         if (vto != null) {
   1672             vto.addOnScrollChangedListener(mOnScrollChangedListener);
   1673         }
   1674 
   1675         mAnchorXoff = xoff;
   1676         mAnchorYoff = yoff;
   1677         mAnchoredGravity = gravity;
   1678     }
   1679 
   1680     private class PopupViewContainer extends FrameLayout {
   1681         private static final String TAG = "PopupWindow.PopupViewContainer";
   1682 
   1683         public PopupViewContainer(Context context) {
   1684             super(context);
   1685         }
   1686 
   1687         @Override
   1688         protected int[] onCreateDrawableState(int extraSpace) {
   1689             if (mAboveAnchor) {
   1690                 // 1 more needed for the above anchor state
   1691                 final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
   1692                 View.mergeDrawableStates(drawableState, ABOVE_ANCHOR_STATE_SET);
   1693                 return drawableState;
   1694             } else {
   1695                 return super.onCreateDrawableState(extraSpace);
   1696             }
   1697         }
   1698 
   1699         @Override
   1700         public boolean dispatchKeyEvent(KeyEvent event) {
   1701             if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
   1702                 if (getKeyDispatcherState() == null) {
   1703                     return super.dispatchKeyEvent(event);
   1704                 }
   1705 
   1706                 if (event.getAction() == KeyEvent.ACTION_DOWN
   1707                         && event.getRepeatCount() == 0) {
   1708                     KeyEvent.DispatcherState state = getKeyDispatcherState();
   1709                     if (state != null) {
   1710                         state.startTracking(event, this);
   1711                     }
   1712                     return true;
   1713                 } else if (event.getAction() == KeyEvent.ACTION_UP) {
   1714                     KeyEvent.DispatcherState state = getKeyDispatcherState();
   1715                     if (state != null && state.isTracking(event) && !event.isCanceled()) {
   1716                         dismiss();
   1717                         return true;
   1718                     }
   1719                 }
   1720                 return super.dispatchKeyEvent(event);
   1721             } else {
   1722                 return super.dispatchKeyEvent(event);
   1723             }
   1724         }
   1725 
   1726         @Override
   1727         public boolean dispatchTouchEvent(MotionEvent ev) {
   1728             if (mTouchInterceptor != null && mTouchInterceptor.onTouch(this, ev)) {
   1729                 return true;
   1730             }
   1731             return super.dispatchTouchEvent(ev);
   1732         }
   1733 
   1734         @Override
   1735         public boolean onTouchEvent(MotionEvent event) {
   1736             final int x = (int) event.getX();
   1737             final int y = (int) event.getY();
   1738 
   1739             if ((event.getAction() == MotionEvent.ACTION_DOWN)
   1740                     && ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) {
   1741                 dismiss();
   1742                 return true;
   1743             } else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
   1744                 dismiss();
   1745                 return true;
   1746             } else {
   1747                 return super.onTouchEvent(event);
   1748             }
   1749         }
   1750 
   1751         @Override
   1752         public void sendAccessibilityEvent(int eventType) {
   1753             // clinets are interested in the content not the container, make it event source
   1754             if (mContentView != null) {
   1755                 mContentView.sendAccessibilityEvent(eventType);
   1756             } else {
   1757                 super.sendAccessibilityEvent(eventType);
   1758             }
   1759         }
   1760     }
   1761 
   1762 }
   1763