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