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