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