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