Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2010 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 android.animation.Animator;
     20 import android.animation.AnimatorListenerAdapter;
     21 import android.animation.ObjectAnimator;
     22 import android.content.Context;
     23 import android.database.DataSetObserver;
     24 import android.graphics.Rect;
     25 import android.graphics.drawable.Drawable;
     26 import android.os.Handler;
     27 import android.text.TextUtils;
     28 import android.util.AttributeSet;
     29 import android.util.IntProperty;
     30 import android.util.Log;
     31 import android.view.Gravity;
     32 import android.view.KeyEvent;
     33 import android.view.MotionEvent;
     34 import android.view.View;
     35 import android.view.View.MeasureSpec;
     36 import android.view.View.OnAttachStateChangeListener;
     37 import android.view.View.OnTouchListener;
     38 import android.view.ViewConfiguration;
     39 import android.view.ViewGroup;
     40 import android.view.ViewParent;
     41 import android.view.animation.AccelerateDecelerateInterpolator;
     42 
     43 import com.android.internal.widget.AutoScrollHelper.AbsListViewAutoScroller;
     44 
     45 import java.util.Locale;
     46 
     47 /**
     48  * A ListPopupWindow anchors itself to a host view and displays a
     49  * list of choices.
     50  *
     51  * <p>ListPopupWindow contains a number of tricky behaviors surrounding
     52  * positioning, scrolling parents to fit the dropdown, interacting
     53  * sanely with the IME if present, and others.
     54  *
     55  * @see android.widget.AutoCompleteTextView
     56  * @see android.widget.Spinner
     57  */
     58 public class ListPopupWindow {
     59     private static final String TAG = "ListPopupWindow";
     60     private static final boolean DEBUG = false;
     61 
     62     /**
     63      * This value controls the length of time that the user
     64      * must leave a pointer down without scrolling to expand
     65      * the autocomplete dropdown list to cover the IME.
     66      */
     67     private static final int EXPAND_LIST_TIMEOUT = 250;
     68 
     69     private Context mContext;
     70     private PopupWindow mPopup;
     71     private ListAdapter mAdapter;
     72     private DropDownListView mDropDownList;
     73 
     74     private int mDropDownHeight = ViewGroup.LayoutParams.WRAP_CONTENT;
     75     private int mDropDownWidth = ViewGroup.LayoutParams.WRAP_CONTENT;
     76     private int mDropDownHorizontalOffset;
     77     private int mDropDownVerticalOffset;
     78     private boolean mDropDownVerticalOffsetSet;
     79 
     80     private int mDropDownGravity = Gravity.NO_GRAVITY;
     81 
     82     private boolean mDropDownAlwaysVisible = false;
     83     private boolean mForceIgnoreOutsideTouch = false;
     84     int mListItemExpandMaximum = Integer.MAX_VALUE;
     85 
     86     private View mPromptView;
     87     private int mPromptPosition = POSITION_PROMPT_ABOVE;
     88 
     89     private DataSetObserver mObserver;
     90 
     91     private View mDropDownAnchorView;
     92 
     93     private Drawable mDropDownListHighlight;
     94 
     95     private AdapterView.OnItemClickListener mItemClickListener;
     96     private AdapterView.OnItemSelectedListener mItemSelectedListener;
     97 
     98     private final ResizePopupRunnable mResizePopupRunnable = new ResizePopupRunnable();
     99     private final PopupTouchInterceptor mTouchInterceptor = new PopupTouchInterceptor();
    100     private final PopupScrollListener mScrollListener = new PopupScrollListener();
    101     private final ListSelectorHider mHideSelector = new ListSelectorHider();
    102     private Runnable mShowDropDownRunnable;
    103 
    104     private Handler mHandler = new Handler();
    105 
    106     private Rect mTempRect = new Rect();
    107 
    108     private boolean mModal;
    109 
    110     private int mLayoutDirection;
    111 
    112     /**
    113      * The provided prompt view should appear above list content.
    114      *
    115      * @see #setPromptPosition(int)
    116      * @see #getPromptPosition()
    117      * @see #setPromptView(View)
    118      */
    119     public static final int POSITION_PROMPT_ABOVE = 0;
    120 
    121     /**
    122      * The provided prompt view should appear below list content.
    123      *
    124      * @see #setPromptPosition(int)
    125      * @see #getPromptPosition()
    126      * @see #setPromptView(View)
    127      */
    128     public static final int POSITION_PROMPT_BELOW = 1;
    129 
    130     /**
    131      * Alias for {@link ViewGroup.LayoutParams#MATCH_PARENT}.
    132      * If used to specify a popup width, the popup will match the width of the anchor view.
    133      * If used to specify a popup height, the popup will fill available space.
    134      */
    135     public static final int MATCH_PARENT = ViewGroup.LayoutParams.MATCH_PARENT;
    136 
    137     /**
    138      * Alias for {@link ViewGroup.LayoutParams#WRAP_CONTENT}.
    139      * If used to specify a popup width, the popup will use the width of its content.
    140      */
    141     public static final int WRAP_CONTENT = ViewGroup.LayoutParams.WRAP_CONTENT;
    142 
    143     /**
    144      * Mode for {@link #setInputMethodMode(int)}: the requirements for the
    145      * input method should be based on the focusability of the popup.  That is
    146      * if it is focusable than it needs to work with the input method, else
    147      * it doesn't.
    148      */
    149     public static final int INPUT_METHOD_FROM_FOCUSABLE = PopupWindow.INPUT_METHOD_FROM_FOCUSABLE;
    150 
    151     /**
    152      * Mode for {@link #setInputMethodMode(int)}: this popup always needs to
    153      * work with an input method, regardless of whether it is focusable.  This
    154      * means that it will always be displayed so that the user can also operate
    155      * the input method while it is shown.
    156      */
    157     public static final int INPUT_METHOD_NEEDED = PopupWindow.INPUT_METHOD_NEEDED;
    158 
    159     /**
    160      * Mode for {@link #setInputMethodMode(int)}: this popup never needs to
    161      * work with an input method, regardless of whether it is focusable.  This
    162      * means that it will always be displayed to use as much space on the
    163      * screen as needed, regardless of whether this covers the input method.
    164      */
    165     public static final int INPUT_METHOD_NOT_NEEDED = PopupWindow.INPUT_METHOD_NOT_NEEDED;
    166 
    167     /**
    168      * Create a new, empty popup window capable of displaying items from a ListAdapter.
    169      * Backgrounds should be set using {@link #setBackgroundDrawable(Drawable)}.
    170      *
    171      * @param context Context used for contained views.
    172      */
    173     public ListPopupWindow(Context context) {
    174         this(context, null, com.android.internal.R.attr.listPopupWindowStyle, 0);
    175     }
    176 
    177     /**
    178      * Create a new, empty popup window capable of displaying items from a ListAdapter.
    179      * Backgrounds should be set using {@link #setBackgroundDrawable(Drawable)}.
    180      *
    181      * @param context Context used for contained views.
    182      * @param attrs Attributes from inflating parent views used to style the popup.
    183      */
    184     public ListPopupWindow(Context context, AttributeSet attrs) {
    185         this(context, attrs, com.android.internal.R.attr.listPopupWindowStyle, 0);
    186     }
    187 
    188     /**
    189      * Create a new, empty popup window capable of displaying items from a ListAdapter.
    190      * Backgrounds should be set using {@link #setBackgroundDrawable(Drawable)}.
    191      *
    192      * @param context Context used for contained views.
    193      * @param attrs Attributes from inflating parent views used to style the popup.
    194      * @param defStyleAttr Default style attribute to use for popup content.
    195      */
    196     public ListPopupWindow(Context context, AttributeSet attrs, int defStyleAttr) {
    197         this(context, attrs, defStyleAttr, 0);
    198     }
    199 
    200     /**
    201      * Create a new, empty popup window capable of displaying items from a ListAdapter.
    202      * Backgrounds should be set using {@link #setBackgroundDrawable(Drawable)}.
    203      *
    204      * @param context Context used for contained views.
    205      * @param attrs Attributes from inflating parent views used to style the popup.
    206      * @param defStyleAttr Style attribute to read for default styling of popup content.
    207      * @param defStyleRes Style resource ID to use for default styling of popup content.
    208      */
    209     public ListPopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    210         mContext = context;
    211         mPopup = new PopupWindow(context, attrs, defStyleAttr, defStyleRes);
    212         mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
    213         // Set the default layout direction to match the default locale one
    214         final Locale locale = mContext.getResources().getConfiguration().locale;
    215         mLayoutDirection = TextUtils.getLayoutDirectionFromLocale(locale);
    216     }
    217 
    218     /**
    219      * Sets the adapter that provides the data and the views to represent the data
    220      * in this popup window.
    221      *
    222      * @param adapter The adapter to use to create this window's content.
    223      */
    224     public void setAdapter(ListAdapter adapter) {
    225         if (mObserver == null) {
    226             mObserver = new PopupDataSetObserver();
    227         } else if (mAdapter != null) {
    228             mAdapter.unregisterDataSetObserver(mObserver);
    229         }
    230         mAdapter = adapter;
    231         if (mAdapter != null) {
    232             adapter.registerDataSetObserver(mObserver);
    233         }
    234 
    235         if (mDropDownList != null) {
    236             mDropDownList.setAdapter(mAdapter);
    237         }
    238     }
    239 
    240     /**
    241      * Set where the optional prompt view should appear. The default is
    242      * {@link #POSITION_PROMPT_ABOVE}.
    243      *
    244      * @param position A position constant declaring where the prompt should be displayed.
    245      *
    246      * @see #POSITION_PROMPT_ABOVE
    247      * @see #POSITION_PROMPT_BELOW
    248      */
    249     public void setPromptPosition(int position) {
    250         mPromptPosition = position;
    251     }
    252 
    253     /**
    254      * @return Where the optional prompt view should appear.
    255      *
    256      * @see #POSITION_PROMPT_ABOVE
    257      * @see #POSITION_PROMPT_BELOW
    258      */
    259     public int getPromptPosition() {
    260         return mPromptPosition;
    261     }
    262 
    263     /**
    264      * Set whether this window should be modal when shown.
    265      *
    266      * <p>If a popup window is modal, it will receive all touch and key input.
    267      * If the user touches outside the popup window's content area the popup window
    268      * will be dismissed.
    269      *
    270      * @param modal {@code true} if the popup window should be modal, {@code false} otherwise.
    271      */
    272     public void setModal(boolean modal) {
    273         mModal = true;
    274         mPopup.setFocusable(modal);
    275     }
    276 
    277     /**
    278      * Returns whether the popup window will be modal when shown.
    279      *
    280      * @return {@code true} if the popup window will be modal, {@code false} otherwise.
    281      */
    282     public boolean isModal() {
    283         return mModal;
    284     }
    285 
    286     /**
    287      * Forces outside touches to be ignored. Normally if {@link #isDropDownAlwaysVisible()} is
    288      * false, we allow outside touch to dismiss the dropdown. If this is set to true, then we
    289      * ignore outside touch even when the drop down is not set to always visible.
    290      *
    291      * @hide Used only by AutoCompleteTextView to handle some internal special cases.
    292      */
    293     public void setForceIgnoreOutsideTouch(boolean forceIgnoreOutsideTouch) {
    294         mForceIgnoreOutsideTouch = forceIgnoreOutsideTouch;
    295     }
    296 
    297     /**
    298      * Sets whether the drop-down should remain visible under certain conditions.
    299      *
    300      * The drop-down will occupy the entire screen below {@link #getAnchorView} regardless
    301      * of the size or content of the list.  {@link #getBackground()} will fill any space
    302      * that is not used by the list.
    303      *
    304      * @param dropDownAlwaysVisible Whether to keep the drop-down visible.
    305      *
    306      * @hide Only used by AutoCompleteTextView under special conditions.
    307      */
    308     public void setDropDownAlwaysVisible(boolean dropDownAlwaysVisible) {
    309         mDropDownAlwaysVisible = dropDownAlwaysVisible;
    310     }
    311 
    312     /**
    313      * @return Whether the drop-down is visible under special conditions.
    314      *
    315      * @hide Only used by AutoCompleteTextView under special conditions.
    316      */
    317     public boolean isDropDownAlwaysVisible() {
    318         return mDropDownAlwaysVisible;
    319     }
    320 
    321     /**
    322      * Sets the operating mode for the soft input area.
    323      *
    324      * @param mode The desired mode, see
    325      *        {@link android.view.WindowManager.LayoutParams#softInputMode}
    326      *        for the full list
    327      *
    328      * @see android.view.WindowManager.LayoutParams#softInputMode
    329      * @see #getSoftInputMode()
    330      */
    331     public void setSoftInputMode(int mode) {
    332         mPopup.setSoftInputMode(mode);
    333     }
    334 
    335     /**
    336      * Returns the current value in {@link #setSoftInputMode(int)}.
    337      *
    338      * @see #setSoftInputMode(int)
    339      * @see android.view.WindowManager.LayoutParams#softInputMode
    340      */
    341     public int getSoftInputMode() {
    342         return mPopup.getSoftInputMode();
    343     }
    344 
    345     /**
    346      * Sets a drawable to use as the list item selector.
    347      *
    348      * @param selector List selector drawable to use in the popup.
    349      */
    350     public void setListSelector(Drawable selector) {
    351         mDropDownListHighlight = selector;
    352     }
    353 
    354     /**
    355      * @return The background drawable for the popup window.
    356      */
    357     public Drawable getBackground() {
    358         return mPopup.getBackground();
    359     }
    360 
    361     /**
    362      * Sets a drawable to be the background for the popup window.
    363      *
    364      * @param d A drawable to set as the background.
    365      */
    366     public void setBackgroundDrawable(Drawable d) {
    367         mPopup.setBackgroundDrawable(d);
    368     }
    369 
    370     /**
    371      * Set an animation style to use when the popup window is shown or dismissed.
    372      *
    373      * @param animationStyle Animation style to use.
    374      */
    375     public void setAnimationStyle(int animationStyle) {
    376         mPopup.setAnimationStyle(animationStyle);
    377     }
    378 
    379     /**
    380      * Returns the animation style that will be used when the popup window is
    381      * shown or dismissed.
    382      *
    383      * @return Animation style that will be used.
    384      */
    385     public int getAnimationStyle() {
    386         return mPopup.getAnimationStyle();
    387     }
    388 
    389     /**
    390      * Returns the view that will be used to anchor this popup.
    391      *
    392      * @return The popup's anchor view
    393      */
    394     public View getAnchorView() {
    395         return mDropDownAnchorView;
    396     }
    397 
    398     /**
    399      * Sets the popup's anchor view. This popup will always be positioned relative to
    400      * the anchor view when shown.
    401      *
    402      * @param anchor The view to use as an anchor.
    403      */
    404     public void setAnchorView(View anchor) {
    405         mDropDownAnchorView = anchor;
    406     }
    407 
    408     /**
    409      * @return The horizontal offset of the popup from its anchor in pixels.
    410      */
    411     public int getHorizontalOffset() {
    412         return mDropDownHorizontalOffset;
    413     }
    414 
    415     /**
    416      * Set the horizontal offset of this popup from its anchor view in pixels.
    417      *
    418      * @param offset The horizontal offset of the popup from its anchor.
    419      */
    420     public void setHorizontalOffset(int offset) {
    421         mDropDownHorizontalOffset = offset;
    422     }
    423 
    424     /**
    425      * @return The vertical offset of the popup from its anchor in pixels.
    426      */
    427     public int getVerticalOffset() {
    428         if (!mDropDownVerticalOffsetSet) {
    429             return 0;
    430         }
    431         return mDropDownVerticalOffset;
    432     }
    433 
    434     /**
    435      * Set the vertical offset of this popup from its anchor view in pixels.
    436      *
    437      * @param offset The vertical offset of the popup from its anchor.
    438      */
    439     public void setVerticalOffset(int offset) {
    440         mDropDownVerticalOffset = offset;
    441         mDropDownVerticalOffsetSet = true;
    442     }
    443 
    444     /**
    445      * Set the gravity of the dropdown list. This is commonly used to
    446      * set gravity to START or END for alignment with the anchor.
    447      *
    448      * @param gravity Gravity value to use
    449      */
    450     public void setDropDownGravity(int gravity) {
    451         mDropDownGravity = gravity;
    452     }
    453 
    454     /**
    455      * @return The width of the popup window in pixels.
    456      */
    457     public int getWidth() {
    458         return mDropDownWidth;
    459     }
    460 
    461     /**
    462      * Sets the width of the popup window in pixels. Can also be {@link #MATCH_PARENT}
    463      * or {@link #WRAP_CONTENT}.
    464      *
    465      * @param width Width of the popup window.
    466      */
    467     public void setWidth(int width) {
    468         mDropDownWidth = width;
    469     }
    470 
    471     /**
    472      * Sets the width of the popup window by the size of its content. The final width may be
    473      * larger to accommodate styled window dressing.
    474      *
    475      * @param width Desired width of content in pixels.
    476      */
    477     public void setContentWidth(int width) {
    478         Drawable popupBackground = mPopup.getBackground();
    479         if (popupBackground != null) {
    480             popupBackground.getPadding(mTempRect);
    481             mDropDownWidth = mTempRect.left + mTempRect.right + width;
    482         } else {
    483             setWidth(width);
    484         }
    485     }
    486 
    487     /**
    488      * @return The height of the popup window in pixels.
    489      */
    490     public int getHeight() {
    491         return mDropDownHeight;
    492     }
    493 
    494     /**
    495      * Sets the height of the popup window in pixels. Can also be {@link #MATCH_PARENT}.
    496      *
    497      * @param height Height of the popup window.
    498      */
    499     public void setHeight(int height) {
    500         mDropDownHeight = height;
    501     }
    502 
    503     /**
    504      * Sets a listener to receive events when a list item is clicked.
    505      *
    506      * @param clickListener Listener to register
    507      *
    508      * @see ListView#setOnItemClickListener(android.widget.AdapterView.OnItemClickListener)
    509      */
    510     public void setOnItemClickListener(AdapterView.OnItemClickListener clickListener) {
    511         mItemClickListener = clickListener;
    512     }
    513 
    514     /**
    515      * Sets a listener to receive events when a list item is selected.
    516      *
    517      * @param selectedListener Listener to register.
    518      *
    519      * @see ListView#setOnItemSelectedListener(android.widget.AdapterView.OnItemSelectedListener)
    520      */
    521     public void setOnItemSelectedListener(AdapterView.OnItemSelectedListener selectedListener) {
    522         mItemSelectedListener = selectedListener;
    523     }
    524 
    525     /**
    526      * Set a view to act as a user prompt for this popup window. Where the prompt view will appear
    527      * is controlled by {@link #setPromptPosition(int)}.
    528      *
    529      * @param prompt View to use as an informational prompt.
    530      */
    531     public void setPromptView(View prompt) {
    532         boolean showing = isShowing();
    533         if (showing) {
    534             removePromptView();
    535         }
    536         mPromptView = prompt;
    537         if (showing) {
    538             show();
    539         }
    540     }
    541 
    542     /**
    543      * Post a {@link #show()} call to the UI thread.
    544      */
    545     public void postShow() {
    546         mHandler.post(mShowDropDownRunnable);
    547     }
    548 
    549     /**
    550      * Show the popup list. If the list is already showing, this method
    551      * will recalculate the popup's size and position.
    552      */
    553     public void show() {
    554         int height = buildDropDown();
    555 
    556         int widthSpec = 0;
    557         int heightSpec = 0;
    558 
    559         boolean noInputMethod = isInputMethodNotNeeded();
    560         mPopup.setAllowScrollingAnchorParent(!noInputMethod);
    561 
    562         if (mPopup.isShowing()) {
    563             if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) {
    564                 // The call to PopupWindow's update method below can accept -1 for any
    565                 // value you do not want to update.
    566                 widthSpec = -1;
    567             } else if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
    568                 widthSpec = getAnchorView().getWidth();
    569             } else {
    570                 widthSpec = mDropDownWidth;
    571             }
    572 
    573             if (mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
    574                 // The call to PopupWindow's update method below can accept -1 for any
    575                 // value you do not want to update.
    576                 heightSpec = noInputMethod ? height : ViewGroup.LayoutParams.MATCH_PARENT;
    577                 if (noInputMethod) {
    578                     mPopup.setWindowLayoutMode(
    579                             mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ?
    580                                     ViewGroup.LayoutParams.MATCH_PARENT : 0, 0);
    581                 } else {
    582                     mPopup.setWindowLayoutMode(
    583                             mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ?
    584                                     ViewGroup.LayoutParams.MATCH_PARENT : 0,
    585                             ViewGroup.LayoutParams.MATCH_PARENT);
    586                 }
    587             } else if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
    588                 heightSpec = height;
    589             } else {
    590                 heightSpec = mDropDownHeight;
    591             }
    592 
    593             mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible);
    594 
    595             mPopup.update(getAnchorView(), mDropDownHorizontalOffset,
    596                     mDropDownVerticalOffset, widthSpec, heightSpec);
    597         } else {
    598             if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) {
    599                 widthSpec = ViewGroup.LayoutParams.MATCH_PARENT;
    600             } else {
    601                 if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
    602                     mPopup.setWidth(getAnchorView().getWidth());
    603                 } else {
    604                     mPopup.setWidth(mDropDownWidth);
    605                 }
    606             }
    607 
    608             if (mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
    609                 heightSpec = ViewGroup.LayoutParams.MATCH_PARENT;
    610             } else {
    611                 if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
    612                     mPopup.setHeight(height);
    613                 } else {
    614                     mPopup.setHeight(mDropDownHeight);
    615                 }
    616             }
    617 
    618             mPopup.setWindowLayoutMode(widthSpec, heightSpec);
    619             mPopup.setClipToScreenEnabled(true);
    620 
    621             // use outside touchable to dismiss drop down when touching outside of it, so
    622             // only set this if the dropdown is not always visible
    623             mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible);
    624             mPopup.setTouchInterceptor(mTouchInterceptor);
    625             mPopup.showAsDropDown(getAnchorView(),
    626                     mDropDownHorizontalOffset, mDropDownVerticalOffset, mDropDownGravity);
    627             mDropDownList.setSelection(ListView.INVALID_POSITION);
    628 
    629             if (!mModal || mDropDownList.isInTouchMode()) {
    630                 clearListSelection();
    631             }
    632             if (!mModal) {
    633                 mHandler.post(mHideSelector);
    634             }
    635         }
    636     }
    637 
    638     /**
    639      * Dismiss the popup window.
    640      */
    641     public void dismiss() {
    642         mPopup.dismiss();
    643         removePromptView();
    644         mPopup.setContentView(null);
    645         mDropDownList = null;
    646         mHandler.removeCallbacks(mResizePopupRunnable);
    647     }
    648 
    649     /**
    650      * Set a listener to receive a callback when the popup is dismissed.
    651      *
    652      * @param listener Listener that will be notified when the popup is dismissed.
    653      */
    654     public void setOnDismissListener(PopupWindow.OnDismissListener listener) {
    655         mPopup.setOnDismissListener(listener);
    656     }
    657 
    658     private void removePromptView() {
    659         if (mPromptView != null) {
    660             final ViewParent parent = mPromptView.getParent();
    661             if (parent instanceof ViewGroup) {
    662                 final ViewGroup group = (ViewGroup) parent;
    663                 group.removeView(mPromptView);
    664             }
    665         }
    666     }
    667 
    668     /**
    669      * Control how the popup operates with an input method: one of
    670      * {@link #INPUT_METHOD_FROM_FOCUSABLE}, {@link #INPUT_METHOD_NEEDED},
    671      * or {@link #INPUT_METHOD_NOT_NEEDED}.
    672      *
    673      * <p>If the popup is showing, calling this method will take effect only
    674      * the next time the popup is shown or through a manual call to the {@link #show()}
    675      * method.</p>
    676      *
    677      * @see #getInputMethodMode()
    678      * @see #show()
    679      */
    680     public void setInputMethodMode(int mode) {
    681         mPopup.setInputMethodMode(mode);
    682     }
    683 
    684     /**
    685      * Return the current value in {@link #setInputMethodMode(int)}.
    686      *
    687      * @see #setInputMethodMode(int)
    688      */
    689     public int getInputMethodMode() {
    690         return mPopup.getInputMethodMode();
    691     }
    692 
    693     /**
    694      * Set the selected position of the list.
    695      * Only valid when {@link #isShowing()} == {@code true}.
    696      *
    697      * @param position List position to set as selected.
    698      */
    699     public void setSelection(int position) {
    700         DropDownListView list = mDropDownList;
    701         if (isShowing() && list != null) {
    702             list.mListSelectionHidden = false;
    703             list.setSelection(position);
    704             if (list.getChoiceMode() != ListView.CHOICE_MODE_NONE) {
    705                 list.setItemChecked(position, true);
    706             }
    707         }
    708     }
    709 
    710     /**
    711      * Clear any current list selection.
    712      * Only valid when {@link #isShowing()} == {@code true}.
    713      */
    714     public void clearListSelection() {
    715         final DropDownListView list = mDropDownList;
    716         if (list != null) {
    717             // WARNING: Please read the comment where mListSelectionHidden is declared
    718             list.mListSelectionHidden = true;
    719             list.hideSelector();
    720             list.requestLayout();
    721         }
    722     }
    723 
    724     /**
    725      * @return {@code true} if the popup is currently showing, {@code false} otherwise.
    726      */
    727     public boolean isShowing() {
    728         return mPopup.isShowing();
    729     }
    730 
    731     /**
    732      * @return {@code true} if this popup is configured to assume the user does not need
    733      * to interact with the IME while it is showing, {@code false} otherwise.
    734      */
    735     public boolean isInputMethodNotNeeded() {
    736         return mPopup.getInputMethodMode() == INPUT_METHOD_NOT_NEEDED;
    737     }
    738 
    739     /**
    740      * Perform an item click operation on the specified list adapter position.
    741      *
    742      * @param position Adapter position for performing the click
    743      * @return true if the click action could be performed, false if not.
    744      *         (e.g. if the popup was not showing, this method would return false.)
    745      */
    746     public boolean performItemClick(int position) {
    747         if (isShowing()) {
    748             if (mItemClickListener != null) {
    749                 final DropDownListView list = mDropDownList;
    750                 final View child = list.getChildAt(position - list.getFirstVisiblePosition());
    751                 final ListAdapter adapter = list.getAdapter();
    752                 mItemClickListener.onItemClick(list, child, position, adapter.getItemId(position));
    753             }
    754             return true;
    755         }
    756         return false;
    757     }
    758 
    759     /**
    760      * @return The currently selected item or null if the popup is not showing.
    761      */
    762     public Object getSelectedItem() {
    763         if (!isShowing()) {
    764             return null;
    765         }
    766         return mDropDownList.getSelectedItem();
    767     }
    768 
    769     /**
    770      * @return The position of the currently selected item or {@link ListView#INVALID_POSITION}
    771      * if {@link #isShowing()} == {@code false}.
    772      *
    773      * @see ListView#getSelectedItemPosition()
    774      */
    775     public int getSelectedItemPosition() {
    776         if (!isShowing()) {
    777             return ListView.INVALID_POSITION;
    778         }
    779         return mDropDownList.getSelectedItemPosition();
    780     }
    781 
    782     /**
    783      * @return The ID of the currently selected item or {@link ListView#INVALID_ROW_ID}
    784      * if {@link #isShowing()} == {@code false}.
    785      *
    786      * @see ListView#getSelectedItemId()
    787      */
    788     public long getSelectedItemId() {
    789         if (!isShowing()) {
    790             return ListView.INVALID_ROW_ID;
    791         }
    792         return mDropDownList.getSelectedItemId();
    793     }
    794 
    795     /**
    796      * @return The View for the currently selected item or null if
    797      * {@link #isShowing()} == {@code false}.
    798      *
    799      * @see ListView#getSelectedView()
    800      */
    801     public View getSelectedView() {
    802         if (!isShowing()) {
    803             return null;
    804         }
    805         return mDropDownList.getSelectedView();
    806     }
    807 
    808     /**
    809      * @return The {@link ListView} displayed within the popup window.
    810      * Only valid when {@link #isShowing()} == {@code true}.
    811      */
    812     public ListView getListView() {
    813         return mDropDownList;
    814     }
    815 
    816     /**
    817      * The maximum number of list items that can be visible and still have
    818      * the list expand when touched.
    819      *
    820      * @param max Max number of items that can be visible and still allow the list to expand.
    821      */
    822     void setListItemExpandMax(int max) {
    823         mListItemExpandMaximum = max;
    824     }
    825 
    826     /**
    827      * Filter key down events. By forwarding key down events to this function,
    828      * views using non-modal ListPopupWindow can have it handle key selection of items.
    829      *
    830      * @param keyCode keyCode param passed to the host view's onKeyDown
    831      * @param event event param passed to the host view's onKeyDown
    832      * @return true if the event was handled, false if it was ignored.
    833      *
    834      * @see #setModal(boolean)
    835      */
    836     public boolean onKeyDown(int keyCode, KeyEvent event) {
    837         // when the drop down is shown, we drive it directly
    838         if (isShowing()) {
    839             // the key events are forwarded to the list in the drop down view
    840             // note that ListView handles space but we don't want that to happen
    841             // also if selection is not currently in the drop down, then don't
    842             // let center or enter presses go there since that would cause it
    843             // to select one of its items
    844             if (keyCode != KeyEvent.KEYCODE_SPACE
    845                     && (mDropDownList.getSelectedItemPosition() >= 0
    846                             || !KeyEvent.isConfirmKey(keyCode))) {
    847                 int curIndex = mDropDownList.getSelectedItemPosition();
    848                 boolean consumed;
    849 
    850                 final boolean below = !mPopup.isAboveAnchor();
    851 
    852                 final ListAdapter adapter = mAdapter;
    853 
    854                 boolean allEnabled;
    855                 int firstItem = Integer.MAX_VALUE;
    856                 int lastItem = Integer.MIN_VALUE;
    857 
    858                 if (adapter != null) {
    859                     allEnabled = adapter.areAllItemsEnabled();
    860                     firstItem = allEnabled ? 0 :
    861                             mDropDownList.lookForSelectablePosition(0, true);
    862                     lastItem = allEnabled ? adapter.getCount() - 1 :
    863                             mDropDownList.lookForSelectablePosition(adapter.getCount() - 1, false);
    864                 }
    865 
    866                 if ((below && keyCode == KeyEvent.KEYCODE_DPAD_UP && curIndex <= firstItem) ||
    867                         (!below && keyCode == KeyEvent.KEYCODE_DPAD_DOWN && curIndex >= lastItem)) {
    868                     // When the selection is at the top, we block the key
    869                     // event to prevent focus from moving.
    870                     clearListSelection();
    871                     mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
    872                     show();
    873                     return true;
    874                 } else {
    875                     // WARNING: Please read the comment where mListSelectionHidden
    876                     //          is declared
    877                     mDropDownList.mListSelectionHidden = false;
    878                 }
    879 
    880                 consumed = mDropDownList.onKeyDown(keyCode, event);
    881                 if (DEBUG) Log.v(TAG, "Key down: code=" + keyCode + " list consumed=" + consumed);
    882 
    883                 if (consumed) {
    884                     // If it handled the key event, then the user is
    885                     // navigating in the list, so we should put it in front.
    886                     mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
    887                     // Here's a little trick we need to do to make sure that
    888                     // the list view is actually showing its focus indicator,
    889                     // by ensuring it has focus and getting its window out
    890                     // of touch mode.
    891                     mDropDownList.requestFocusFromTouch();
    892                     show();
    893 
    894                     switch (keyCode) {
    895                         // avoid passing the focus from the text view to the
    896                         // next component
    897                         case KeyEvent.KEYCODE_ENTER:
    898                         case KeyEvent.KEYCODE_DPAD_CENTER:
    899                         case KeyEvent.KEYCODE_DPAD_DOWN:
    900                         case KeyEvent.KEYCODE_DPAD_UP:
    901                             return true;
    902                     }
    903                 } else {
    904                     if (below && keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
    905                         // when the selection is at the bottom, we block the
    906                         // event to avoid going to the next focusable widget
    907                         if (curIndex == lastItem) {
    908                             return true;
    909                         }
    910                     } else if (!below && keyCode == KeyEvent.KEYCODE_DPAD_UP &&
    911                             curIndex == firstItem) {
    912                         return true;
    913                     }
    914                 }
    915             }
    916         }
    917 
    918         return false;
    919     }
    920 
    921     /**
    922      * Filter key down events. By forwarding key up events to this function,
    923      * views using non-modal ListPopupWindow can have it handle key selection of items.
    924      *
    925      * @param keyCode keyCode param passed to the host view's onKeyUp
    926      * @param event event param passed to the host view's onKeyUp
    927      * @return true if the event was handled, false if it was ignored.
    928      *
    929      * @see #setModal(boolean)
    930      */
    931     public boolean onKeyUp(int keyCode, KeyEvent event) {
    932         if (isShowing() && mDropDownList.getSelectedItemPosition() >= 0) {
    933             boolean consumed = mDropDownList.onKeyUp(keyCode, event);
    934             if (consumed && KeyEvent.isConfirmKey(keyCode)) {
    935                 // if the list accepts the key events and the key event was a click, the text view
    936                 // gets the selected item from the drop down as its content
    937                 dismiss();
    938             }
    939             return consumed;
    940         }
    941         return false;
    942     }
    943 
    944     /**
    945      * Filter pre-IME key events. By forwarding {@link View#onKeyPreIme(int, KeyEvent)}
    946      * events to this function, views using ListPopupWindow can have it dismiss the popup
    947      * when the back key is pressed.
    948      *
    949      * @param keyCode keyCode param passed to the host view's onKeyPreIme
    950      * @param event event param passed to the host view's onKeyPreIme
    951      * @return true if the event was handled, false if it was ignored.
    952      *
    953      * @see #setModal(boolean)
    954      */
    955     public boolean onKeyPreIme(int keyCode, KeyEvent event) {
    956         if (keyCode == KeyEvent.KEYCODE_BACK && isShowing()) {
    957             // special case for the back key, we do not even try to send it
    958             // to the drop down list but instead, consume it immediately
    959             final View anchorView = mDropDownAnchorView;
    960             if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
    961                 KeyEvent.DispatcherState state = anchorView.getKeyDispatcherState();
    962                 if (state != null) {
    963                     state.startTracking(event, this);
    964                 }
    965                 return true;
    966             } else if (event.getAction() == KeyEvent.ACTION_UP) {
    967                 KeyEvent.DispatcherState state = anchorView.getKeyDispatcherState();
    968                 if (state != null) {
    969                     state.handleUpEvent(event);
    970                 }
    971                 if (event.isTracking() && !event.isCanceled()) {
    972                     dismiss();
    973                     return true;
    974                 }
    975             }
    976         }
    977         return false;
    978     }
    979 
    980     /**
    981      * Returns an {@link OnTouchListener} that can be added to the source view
    982      * to implement drag-to-open behavior. Generally, the source view should be
    983      * the same view that was passed to {@link #setAnchorView}.
    984      * <p>
    985      * When the listener is set on a view, touching that view and dragging
    986      * outside of its bounds will open the popup window. Lifting will select the
    987      * currently touched list item.
    988      * <p>
    989      * Example usage:
    990      * <pre>
    991      * ListPopupWindow myPopup = new ListPopupWindow(context);
    992      * myPopup.setAnchor(myAnchor);
    993      * OnTouchListener dragListener = myPopup.createDragToOpenListener(myAnchor);
    994      * myAnchor.setOnTouchListener(dragListener);
    995      * </pre>
    996      *
    997      * @param src the view on which the resulting listener will be set
    998      * @return a touch listener that controls drag-to-open behavior
    999      */
   1000     public OnTouchListener createDragToOpenListener(View src) {
   1001         return new ForwardingListener(src) {
   1002             @Override
   1003             public ListPopupWindow getPopup() {
   1004                 return ListPopupWindow.this;
   1005             }
   1006         };
   1007     }
   1008 
   1009     /**
   1010      * <p>Builds the popup window's content and returns the height the popup
   1011      * should have. Returns -1 when the content already exists.</p>
   1012      *
   1013      * @return the content's height or -1 if content already exists
   1014      */
   1015     private int buildDropDown() {
   1016         ViewGroup dropDownView;
   1017         int otherHeights = 0;
   1018 
   1019         if (mDropDownList == null) {
   1020             Context context = mContext;
   1021 
   1022             /**
   1023              * This Runnable exists for the sole purpose of checking if the view layout has got
   1024              * completed and if so call showDropDown to display the drop down. This is used to show
   1025              * the drop down as soon as possible after user opens up the search dialog, without
   1026              * waiting for the normal UI pipeline to do it's job which is slower than this method.
   1027              */
   1028             mShowDropDownRunnable = new Runnable() {
   1029                 public void run() {
   1030                     // View layout should be all done before displaying the drop down.
   1031                     View view = getAnchorView();
   1032                     if (view != null && view.getWindowToken() != null) {
   1033                         show();
   1034                     }
   1035                 }
   1036             };
   1037 
   1038             mDropDownList = new DropDownListView(context, !mModal);
   1039             if (mDropDownListHighlight != null) {
   1040                 mDropDownList.setSelector(mDropDownListHighlight);
   1041             }
   1042             mDropDownList.setAdapter(mAdapter);
   1043             mDropDownList.setOnItemClickListener(mItemClickListener);
   1044             mDropDownList.setFocusable(true);
   1045             mDropDownList.setFocusableInTouchMode(true);
   1046             mDropDownList.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
   1047                 public void onItemSelected(AdapterView<?> parent, View view,
   1048                         int position, long id) {
   1049 
   1050                     if (position != -1) {
   1051                         DropDownListView dropDownList = mDropDownList;
   1052 
   1053                         if (dropDownList != null) {
   1054                             dropDownList.mListSelectionHidden = false;
   1055                         }
   1056                     }
   1057                 }
   1058 
   1059                 public void onNothingSelected(AdapterView<?> parent) {
   1060                 }
   1061             });
   1062             mDropDownList.setOnScrollListener(mScrollListener);
   1063 
   1064             if (mItemSelectedListener != null) {
   1065                 mDropDownList.setOnItemSelectedListener(mItemSelectedListener);
   1066             }
   1067 
   1068             dropDownView = mDropDownList;
   1069 
   1070             View hintView = mPromptView;
   1071             if (hintView != null) {
   1072                 // if a hint has been specified, we accomodate more space for it and
   1073                 // add a text view in the drop down menu, at the bottom of the list
   1074                 LinearLayout hintContainer = new LinearLayout(context);
   1075                 hintContainer.setOrientation(LinearLayout.VERTICAL);
   1076 
   1077                 LinearLayout.LayoutParams hintParams = new LinearLayout.LayoutParams(
   1078                         ViewGroup.LayoutParams.MATCH_PARENT, 0, 1.0f
   1079                 );
   1080 
   1081                 switch (mPromptPosition) {
   1082                 case POSITION_PROMPT_BELOW:
   1083                     hintContainer.addView(dropDownView, hintParams);
   1084                     hintContainer.addView(hintView);
   1085                     break;
   1086 
   1087                 case POSITION_PROMPT_ABOVE:
   1088                     hintContainer.addView(hintView);
   1089                     hintContainer.addView(dropDownView, hintParams);
   1090                     break;
   1091 
   1092                 default:
   1093                     Log.e(TAG, "Invalid hint position " + mPromptPosition);
   1094                     break;
   1095                 }
   1096 
   1097                 // measure the hint's height to find how much more vertical space
   1098                 // we need to add to the drop down's height
   1099                 int widthSpec = MeasureSpec.makeMeasureSpec(mDropDownWidth, MeasureSpec.AT_MOST);
   1100                 int heightSpec = MeasureSpec.UNSPECIFIED;
   1101                 hintView.measure(widthSpec, heightSpec);
   1102 
   1103                 hintParams = (LinearLayout.LayoutParams) hintView.getLayoutParams();
   1104                 otherHeights = hintView.getMeasuredHeight() + hintParams.topMargin
   1105                         + hintParams.bottomMargin;
   1106 
   1107                 dropDownView = hintContainer;
   1108             }
   1109 
   1110             mPopup.setContentView(dropDownView);
   1111         } else {
   1112             dropDownView = (ViewGroup) mPopup.getContentView();
   1113             final View view = mPromptView;
   1114             if (view != null) {
   1115                 LinearLayout.LayoutParams hintParams =
   1116                         (LinearLayout.LayoutParams) view.getLayoutParams();
   1117                 otherHeights = view.getMeasuredHeight() + hintParams.topMargin
   1118                         + hintParams.bottomMargin;
   1119             }
   1120         }
   1121 
   1122         // getMaxAvailableHeight() subtracts the padding, so we put it back
   1123         // to get the available height for the whole window
   1124         int padding = 0;
   1125         Drawable background = mPopup.getBackground();
   1126         if (background != null) {
   1127             background.getPadding(mTempRect);
   1128             padding = mTempRect.top + mTempRect.bottom;
   1129 
   1130             // If we don't have an explicit vertical offset, determine one from the window
   1131             // background so that content will line up.
   1132             if (!mDropDownVerticalOffsetSet) {
   1133                 mDropDownVerticalOffset = -mTempRect.top;
   1134             }
   1135         } else {
   1136             mTempRect.setEmpty();
   1137         }
   1138 
   1139         // Max height available on the screen for a popup.
   1140         boolean ignoreBottomDecorations =
   1141                 mPopup.getInputMethodMode() == PopupWindow.INPUT_METHOD_NOT_NEEDED;
   1142         final int maxHeight = mPopup.getMaxAvailableHeight(
   1143                 getAnchorView(), mDropDownVerticalOffset, ignoreBottomDecorations);
   1144 
   1145         if (mDropDownAlwaysVisible || mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
   1146             return maxHeight + padding;
   1147         }
   1148 
   1149         final int childWidthSpec;
   1150         switch (mDropDownWidth) {
   1151             case ViewGroup.LayoutParams.WRAP_CONTENT:
   1152                 childWidthSpec = MeasureSpec.makeMeasureSpec(
   1153                         mContext.getResources().getDisplayMetrics().widthPixels -
   1154                         (mTempRect.left + mTempRect.right),
   1155                         MeasureSpec.AT_MOST);
   1156                 break;
   1157             case ViewGroup.LayoutParams.MATCH_PARENT:
   1158                 childWidthSpec = MeasureSpec.makeMeasureSpec(
   1159                         mContext.getResources().getDisplayMetrics().widthPixels -
   1160                         (mTempRect.left + mTempRect.right),
   1161                         MeasureSpec.EXACTLY);
   1162                 break;
   1163             default:
   1164                 childWidthSpec = MeasureSpec.makeMeasureSpec(mDropDownWidth, MeasureSpec.EXACTLY);
   1165                 break;
   1166         }
   1167         final int listContent = mDropDownList.measureHeightOfChildren(childWidthSpec,
   1168                 0, ListView.NO_POSITION, maxHeight - otherHeights, -1);
   1169         // add padding only if the list has items in it, that way we don't show
   1170         // the popup if it is not needed
   1171         if (listContent > 0) otherHeights += padding;
   1172 
   1173         return listContent + otherHeights;
   1174     }
   1175 
   1176     /**
   1177      * Abstract class that forwards touch events to a {@link ListPopupWindow}.
   1178      *
   1179      * @hide
   1180      */
   1181     public static abstract class ForwardingListener
   1182             implements View.OnTouchListener, View.OnAttachStateChangeListener {
   1183         /** Scaled touch slop, used for detecting movement outside bounds. */
   1184         private final float mScaledTouchSlop;
   1185 
   1186         /** Timeout before disallowing intercept on the source's parent. */
   1187         private final int mTapTimeout;
   1188 
   1189         /** Source view from which events are forwarded. */
   1190         private final View mSrc;
   1191 
   1192         /** Runnable used to prevent conflicts with scrolling parents. */
   1193         private Runnable mDisallowIntercept;
   1194 
   1195         /** Whether this listener is currently forwarding touch events. */
   1196         private boolean mForwarding;
   1197 
   1198         /** The id of the first pointer down in the current event stream. */
   1199         private int mActivePointerId;
   1200 
   1201         public ForwardingListener(View src) {
   1202             mSrc = src;
   1203             mScaledTouchSlop = ViewConfiguration.get(src.getContext()).getScaledTouchSlop();
   1204             mTapTimeout = ViewConfiguration.getTapTimeout();
   1205 
   1206             src.addOnAttachStateChangeListener(this);
   1207         }
   1208 
   1209         /**
   1210          * Returns the popup to which this listener is forwarding events.
   1211          * <p>
   1212          * Override this to return the correct popup. If the popup is displayed
   1213          * asynchronously, you may also need to override
   1214          * {@link #onForwardingStopped} to prevent premature cancelation of
   1215          * forwarding.
   1216          *
   1217          * @return the popup to which this listener is forwarding events
   1218          */
   1219         public abstract ListPopupWindow getPopup();
   1220 
   1221         @Override
   1222         public boolean onTouch(View v, MotionEvent event) {
   1223             final boolean wasForwarding = mForwarding;
   1224             final boolean forwarding;
   1225             if (wasForwarding) {
   1226                 forwarding = onTouchForwarded(event) || !onForwardingStopped();
   1227             } else {
   1228                 forwarding = onTouchObserved(event) && onForwardingStarted();
   1229             }
   1230 
   1231             mForwarding = forwarding;
   1232             return forwarding || wasForwarding;
   1233         }
   1234 
   1235         @Override
   1236         public void onViewAttachedToWindow(View v) {
   1237         }
   1238 
   1239         @Override
   1240         public void onViewDetachedFromWindow(View v) {
   1241             mForwarding = false;
   1242             mActivePointerId = MotionEvent.INVALID_POINTER_ID;
   1243 
   1244             if (mDisallowIntercept != null) {
   1245                 mSrc.removeCallbacks(mDisallowIntercept);
   1246             }
   1247         }
   1248 
   1249         /**
   1250          * Called when forwarding would like to start.
   1251          * <p>
   1252          * By default, this will show the popup returned by {@link #getPopup()}.
   1253          * It may be overridden to perform another action, like clicking the
   1254          * source view or preparing the popup before showing it.
   1255          *
   1256          * @return true to start forwarding, false otherwise
   1257          */
   1258         protected boolean onForwardingStarted() {
   1259             final ListPopupWindow popup = getPopup();
   1260             if (popup != null && !popup.isShowing()) {
   1261                 popup.show();
   1262             }
   1263             return true;
   1264         }
   1265 
   1266         /**
   1267          * Called when forwarding would like to stop.
   1268          * <p>
   1269          * By default, this will dismiss the popup returned by
   1270          * {@link #getPopup()}. It may be overridden to perform some other
   1271          * action.
   1272          *
   1273          * @return true to stop forwarding, false otherwise
   1274          */
   1275         protected boolean onForwardingStopped() {
   1276             final ListPopupWindow popup = getPopup();
   1277             if (popup != null && popup.isShowing()) {
   1278                 popup.dismiss();
   1279             }
   1280             return true;
   1281         }
   1282 
   1283         /**
   1284          * Observes motion events and determines when to start forwarding.
   1285          *
   1286          * @param srcEvent motion event in source view coordinates
   1287          * @return true to start forwarding motion events, false otherwise
   1288          */
   1289         private boolean onTouchObserved(MotionEvent srcEvent) {
   1290             final View src = mSrc;
   1291             if (!src.isEnabled()) {
   1292                 return false;
   1293             }
   1294 
   1295             final int actionMasked = srcEvent.getActionMasked();
   1296             switch (actionMasked) {
   1297                 case MotionEvent.ACTION_DOWN:
   1298                     mActivePointerId = srcEvent.getPointerId(0);
   1299                     if (mDisallowIntercept == null) {
   1300                         mDisallowIntercept = new DisallowIntercept();
   1301                     }
   1302                     src.postDelayed(mDisallowIntercept, mTapTimeout);
   1303                     break;
   1304                 case MotionEvent.ACTION_MOVE:
   1305                     final int activePointerIndex = srcEvent.findPointerIndex(mActivePointerId);
   1306                     if (activePointerIndex >= 0) {
   1307                         final float x = srcEvent.getX(activePointerIndex);
   1308                         final float y = srcEvent.getY(activePointerIndex);
   1309                         if (!src.pointInView(x, y, mScaledTouchSlop)) {
   1310                             // The pointer has moved outside of the view.
   1311                             if (mDisallowIntercept != null) {
   1312                                 src.removeCallbacks(mDisallowIntercept);
   1313                             }
   1314                             src.getParent().requestDisallowInterceptTouchEvent(true);
   1315                             return true;
   1316                         }
   1317                     }
   1318                     break;
   1319                 case MotionEvent.ACTION_CANCEL:
   1320                 case MotionEvent.ACTION_UP:
   1321                     if (mDisallowIntercept != null) {
   1322                         src.removeCallbacks(mDisallowIntercept);
   1323                     }
   1324                     break;
   1325             }
   1326 
   1327             return false;
   1328         }
   1329 
   1330         /**
   1331          * Handled forwarded motion events and determines when to stop
   1332          * forwarding.
   1333          *
   1334          * @param srcEvent motion event in source view coordinates
   1335          * @return true to continue forwarding motion events, false to cancel
   1336          */
   1337         private boolean onTouchForwarded(MotionEvent srcEvent) {
   1338             final View src = mSrc;
   1339             final ListPopupWindow popup = getPopup();
   1340             if (popup == null || !popup.isShowing()) {
   1341                 return false;
   1342             }
   1343 
   1344             final DropDownListView dst = popup.mDropDownList;
   1345             if (dst == null || !dst.isShown()) {
   1346                 return false;
   1347             }
   1348 
   1349             // Convert event to destination-local coordinates.
   1350             final MotionEvent dstEvent = MotionEvent.obtainNoHistory(srcEvent);
   1351             src.toGlobalMotionEvent(dstEvent);
   1352             dst.toLocalMotionEvent(dstEvent);
   1353 
   1354             // Forward converted event to destination view, then recycle it.
   1355             final boolean handled = dst.onForwardedEvent(dstEvent, mActivePointerId);
   1356             dstEvent.recycle();
   1357             return handled;
   1358         }
   1359 
   1360         private class DisallowIntercept implements Runnable {
   1361             @Override
   1362             public void run() {
   1363                 final ViewParent parent = mSrc.getParent();
   1364                 parent.requestDisallowInterceptTouchEvent(true);
   1365             }
   1366         }
   1367     }
   1368 
   1369     /**
   1370      * <p>Wrapper class for a ListView. This wrapper can hijack the focus to
   1371      * make sure the list uses the appropriate drawables and states when
   1372      * displayed on screen within a drop down. The focus is never actually
   1373      * passed to the drop down in this mode; the list only looks focused.</p>
   1374      */
   1375     private static class DropDownListView extends ListView {
   1376         /** Duration in milliseconds of the drag-to-open click animation. */
   1377         private static final long CLICK_ANIM_DURATION = 150;
   1378 
   1379         /** Target alpha value for drag-to-open click animation. */
   1380         private static final int CLICK_ANIM_ALPHA = 0x80;
   1381 
   1382         /** Wrapper around Drawable's <code>alpha</code> property. */
   1383         private static final IntProperty<Drawable> DRAWABLE_ALPHA =
   1384                 new IntProperty<Drawable>("alpha") {
   1385                     @Override
   1386                     public void setValue(Drawable object, int value) {
   1387                         object.setAlpha(value);
   1388                     }
   1389 
   1390                     @Override
   1391                     public Integer get(Drawable object) {
   1392                         return object.getAlpha();
   1393                     }
   1394                 };
   1395 
   1396         /*
   1397          * WARNING: This is a workaround for a touch mode issue.
   1398          *
   1399          * Touch mode is propagated lazily to windows. This causes problems in
   1400          * the following scenario:
   1401          * - Type something in the AutoCompleteTextView and get some results
   1402          * - Move down with the d-pad to select an item in the list
   1403          * - Move up with the d-pad until the selection disappears
   1404          * - Type more text in the AutoCompleteTextView *using the soft keyboard*
   1405          *   and get new results; you are now in touch mode
   1406          * - The selection comes back on the first item in the list, even though
   1407          *   the list is supposed to be in touch mode
   1408          *
   1409          * Using the soft keyboard triggers the touch mode change but that change
   1410          * is propagated to our window only after the first list layout, therefore
   1411          * after the list attempts to resurrect the selection.
   1412          *
   1413          * The trick to work around this issue is to pretend the list is in touch
   1414          * mode when we know that the selection should not appear, that is when
   1415          * we know the user moved the selection away from the list.
   1416          *
   1417          * This boolean is set to true whenever we explicitly hide the list's
   1418          * selection and reset to false whenever we know the user moved the
   1419          * selection back to the list.
   1420          *
   1421          * When this boolean is true, isInTouchMode() returns true, otherwise it
   1422          * returns super.isInTouchMode().
   1423          */
   1424         private boolean mListSelectionHidden;
   1425 
   1426         /**
   1427          * True if this wrapper should fake focus.
   1428          */
   1429         private boolean mHijackFocus;
   1430 
   1431         /** Whether to force drawing of the pressed state selector. */
   1432         private boolean mDrawsInPressedState;
   1433 
   1434         /** Current drag-to-open click animation, if any. */
   1435         private Animator mClickAnimation;
   1436 
   1437         /** Helper for drag-to-open auto scrolling. */
   1438         private AbsListViewAutoScroller mScrollHelper;
   1439 
   1440         /**
   1441          * <p>Creates a new list view wrapper.</p>
   1442          *
   1443          * @param context this view's context
   1444          */
   1445         public DropDownListView(Context context, boolean hijackFocus) {
   1446             super(context, null, com.android.internal.R.attr.dropDownListViewStyle);
   1447             mHijackFocus = hijackFocus;
   1448             // TODO: Add an API to control this
   1449             setCacheColorHint(0); // Transparent, since the background drawable could be anything.
   1450         }
   1451 
   1452         /**
   1453          * Handles forwarded events.
   1454          *
   1455          * @param activePointerId id of the pointer that activated forwarding
   1456          * @return whether the event was handled
   1457          */
   1458         public boolean onForwardedEvent(MotionEvent event, int activePointerId) {
   1459             boolean handledEvent = true;
   1460             boolean clearPressedItem = false;
   1461 
   1462             final int actionMasked = event.getActionMasked();
   1463             switch (actionMasked) {
   1464                 case MotionEvent.ACTION_CANCEL:
   1465                     handledEvent = false;
   1466                     break;
   1467                 case MotionEvent.ACTION_UP:
   1468                     handledEvent = false;
   1469                     // $FALL-THROUGH$
   1470                 case MotionEvent.ACTION_MOVE:
   1471                     final int activeIndex = event.findPointerIndex(activePointerId);
   1472                     if (activeIndex < 0) {
   1473                         handledEvent = false;
   1474                         break;
   1475                     }
   1476 
   1477                     final int x = (int) event.getX(activeIndex);
   1478                     final int y = (int) event.getY(activeIndex);
   1479                     final int position = pointToPosition(x, y);
   1480                     if (position == INVALID_POSITION) {
   1481                         clearPressedItem = true;
   1482                         break;
   1483                     }
   1484 
   1485                     final View child = getChildAt(position - getFirstVisiblePosition());
   1486                     setPressedItem(child, position);
   1487                     handledEvent = true;
   1488 
   1489                     if (actionMasked == MotionEvent.ACTION_UP) {
   1490                         clickPressedItem(child, position);
   1491                     }
   1492                     break;
   1493             }
   1494 
   1495             // Failure to handle the event cancels forwarding.
   1496             if (!handledEvent || clearPressedItem) {
   1497                 clearPressedItem();
   1498             }
   1499 
   1500             // Manage automatic scrolling.
   1501             if (handledEvent) {
   1502                 if (mScrollHelper == null) {
   1503                     mScrollHelper = new AbsListViewAutoScroller(this);
   1504                 }
   1505                 mScrollHelper.setEnabled(true);
   1506                 mScrollHelper.onTouch(this, event);
   1507             } else if (mScrollHelper != null) {
   1508                 mScrollHelper.setEnabled(false);
   1509             }
   1510 
   1511             return handledEvent;
   1512         }
   1513 
   1514         /**
   1515          * Starts an alpha animation on the selector. When the animation ends,
   1516          * the list performs a click on the item.
   1517          */
   1518         private void clickPressedItem(final View child, final int position) {
   1519             final long id = getItemIdAtPosition(position);
   1520             final Animator anim = ObjectAnimator.ofInt(
   1521                     mSelector, DRAWABLE_ALPHA, 0xFF, CLICK_ANIM_ALPHA, 0xFF);
   1522             anim.setDuration(CLICK_ANIM_DURATION);
   1523             anim.setInterpolator(new AccelerateDecelerateInterpolator());
   1524             anim.addListener(new AnimatorListenerAdapter() {
   1525                     @Override
   1526                 public void onAnimationEnd(Animator animation) {
   1527                     performItemClick(child, position, id);
   1528                 }
   1529             });
   1530             anim.start();
   1531 
   1532             if (mClickAnimation != null) {
   1533                 mClickAnimation.cancel();
   1534             }
   1535             mClickAnimation = anim;
   1536         }
   1537 
   1538         private void clearPressedItem() {
   1539             mDrawsInPressedState = false;
   1540             setPressed(false);
   1541             updateSelectorState();
   1542 
   1543             if (mClickAnimation != null) {
   1544                 mClickAnimation.cancel();
   1545                 mClickAnimation = null;
   1546             }
   1547         }
   1548 
   1549         private void setPressedItem(View child, int position) {
   1550             mDrawsInPressedState = true;
   1551 
   1552             // Ordering is essential. First update the pressed state and layout
   1553             // the children. This will ensure the selector actually gets drawn.
   1554             setPressed(true);
   1555             layoutChildren();
   1556 
   1557             // Ensure that keyboard focus starts from the last touched position.
   1558             setSelectedPositionInt(position);
   1559             positionSelector(position, child);
   1560 
   1561             // Refresh the drawable state to reflect the new pressed state,
   1562             // which will also update the selector state.
   1563             refreshDrawableState();
   1564 
   1565             if (mClickAnimation != null) {
   1566                 mClickAnimation.cancel();
   1567                 mClickAnimation = null;
   1568             }
   1569         }
   1570 
   1571         @Override
   1572         boolean touchModeDrawsInPressedState() {
   1573             return mDrawsInPressedState || super.touchModeDrawsInPressedState();
   1574         }
   1575 
   1576         /**
   1577          * <p>Avoids jarring scrolling effect by ensuring that list elements
   1578          * made of a text view fit on a single line.</p>
   1579          *
   1580          * @param position the item index in the list to get a view for
   1581          * @return the view for the specified item
   1582          */
   1583         @Override
   1584         View obtainView(int position, boolean[] isScrap) {
   1585             View view = super.obtainView(position, isScrap);
   1586 
   1587             if (view instanceof TextView) {
   1588                 ((TextView) view).setHorizontallyScrolling(true);
   1589             }
   1590 
   1591             return view;
   1592         }
   1593 
   1594         @Override
   1595         public boolean isInTouchMode() {
   1596             // WARNING: Please read the comment where mListSelectionHidden is declared
   1597             return (mHijackFocus && mListSelectionHidden) || super.isInTouchMode();
   1598         }
   1599 
   1600         /**
   1601          * <p>Returns the focus state in the drop down.</p>
   1602          *
   1603          * @return true always if hijacking focus
   1604          */
   1605         @Override
   1606         public boolean hasWindowFocus() {
   1607             return mHijackFocus || super.hasWindowFocus();
   1608         }
   1609 
   1610         /**
   1611          * <p>Returns the focus state in the drop down.</p>
   1612          *
   1613          * @return true always if hijacking focus
   1614          */
   1615         @Override
   1616         public boolean isFocused() {
   1617             return mHijackFocus || super.isFocused();
   1618         }
   1619 
   1620         /**
   1621          * <p>Returns the focus state in the drop down.</p>
   1622          *
   1623          * @return true always if hijacking focus
   1624          */
   1625         @Override
   1626         public boolean hasFocus() {
   1627             return mHijackFocus || super.hasFocus();
   1628         }
   1629     }
   1630 
   1631     private class PopupDataSetObserver extends DataSetObserver {
   1632         @Override
   1633         public void onChanged() {
   1634             if (isShowing()) {
   1635                 // Resize the popup to fit new content
   1636                 show();
   1637             }
   1638         }
   1639 
   1640         @Override
   1641         public void onInvalidated() {
   1642             dismiss();
   1643         }
   1644     }
   1645 
   1646     private class ListSelectorHider implements Runnable {
   1647         public void run() {
   1648             clearListSelection();
   1649         }
   1650     }
   1651 
   1652     private class ResizePopupRunnable implements Runnable {
   1653         public void run() {
   1654             if (mDropDownList != null && mDropDownList.getCount() > mDropDownList.getChildCount() &&
   1655                     mDropDownList.getChildCount() <= mListItemExpandMaximum) {
   1656                 mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
   1657                 show();
   1658             }
   1659         }
   1660     }
   1661 
   1662     private class PopupTouchInterceptor implements OnTouchListener {
   1663         public boolean onTouch(View v, MotionEvent event) {
   1664             final int action = event.getAction();
   1665             final int x = (int) event.getX();
   1666             final int y = (int) event.getY();
   1667 
   1668             if (action == MotionEvent.ACTION_DOWN &&
   1669                     mPopup != null && mPopup.isShowing() &&
   1670                     (x >= 0 && x < mPopup.getWidth() && y >= 0 && y < mPopup.getHeight())) {
   1671                 mHandler.postDelayed(mResizePopupRunnable, EXPAND_LIST_TIMEOUT);
   1672             } else if (action == MotionEvent.ACTION_UP) {
   1673                 mHandler.removeCallbacks(mResizePopupRunnable);
   1674             }
   1675             return false;
   1676         }
   1677     }
   1678 
   1679     private class PopupScrollListener implements ListView.OnScrollListener {
   1680         public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
   1681                 int totalItemCount) {
   1682 
   1683         }
   1684 
   1685         public void onScrollStateChanged(AbsListView view, int scrollState) {
   1686             if (scrollState == SCROLL_STATE_TOUCH_SCROLL &&
   1687                     !isInputMethodNotNeeded() && mPopup.getContentView() != null) {
   1688                 mHandler.removeCallbacks(mResizePopupRunnable);
   1689                 mResizePopupRunnable.run();
   1690             }
   1691         }
   1692     }
   1693 }
   1694