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
17package android.support.v7.internal.widget;
18
19import android.content.Context;
20import android.content.res.Resources;
21import android.database.DataSetObserver;
22import android.graphics.Rect;
23import android.graphics.drawable.Drawable;
24import android.os.Handler;
25import android.support.v7.appcompat.R;
26import android.text.TextUtils;
27import android.util.AttributeSet;
28import android.util.Log;
29import android.util.SparseArray;
30import android.view.KeyEvent;
31import android.view.MotionEvent;
32import android.view.View;
33import android.view.View.MeasureSpec;
34import android.view.View.OnTouchListener;
35import android.view.ViewGroup;
36import android.view.ViewParent;
37import android.widget.*;
38
39import java.lang.reflect.Field;
40import java.util.ArrayList;
41import java.util.List;
42import java.util.Locale;
43
44/**
45 * A ListPopupWindow anchors itself to a host view and displays a list of choices.
46 *
47 * <p>ListPopupWindow contains a number of tricky behaviors surrounding positioning, scrolling
48 * parents to fit the dropdown, interacting sanely with the IME if present, and others.
49 *
50 * @see android.widget.AutoCompleteTextView
51 * @see android.widget.Spinner
52 *
53 * @hide
54 */
55public class ListPopupWindow {
56
57    private static final String TAG = "ListPopupWindow";
58    private static final boolean DEBUG = false;
59
60    /**
61     * This value controls the length of time that the user must leave a pointer down without
62     * scrolling to expand the autocomplete dropdown list to cover the IME.
63     */
64    private static final int EXPAND_LIST_TIMEOUT = 250;
65
66    private Context mContext;
67    private PopupWindow mPopup;
68    private ListAdapter mAdapter;
69    private DropDownListView mDropDownList;
70
71    private int mDropDownHeight = ViewGroup.LayoutParams.WRAP_CONTENT;
72    private int mDropDownWidth = ViewGroup.LayoutParams.WRAP_CONTENT;
73    private int mDropDownHorizontalOffset;
74    private int mDropDownVerticalOffset;
75    private boolean mDropDownVerticalOffsetSet;
76
77    private boolean mDropDownAlwaysVisible = false;
78    private boolean mForceIgnoreOutsideTouch = false;
79    int mListItemExpandMaximum = Integer.MAX_VALUE;
80
81    private View mPromptView;
82    private int mPromptPosition = POSITION_PROMPT_ABOVE;
83
84    private DataSetObserver mObserver;
85
86    private View mDropDownAnchorView;
87
88    private Drawable mDropDownListHighlight;
89
90    private AdapterView.OnItemClickListener mItemClickListener;
91    private AdapterView.OnItemSelectedListener mItemSelectedListener;
92
93    private final ResizePopupRunnable mResizePopupRunnable = new ResizePopupRunnable();
94    private final PopupTouchInterceptor mTouchInterceptor = new PopupTouchInterceptor();
95    private final PopupScrollListener mScrollListener = new PopupScrollListener();
96    private final ListSelectorHider mHideSelector = new ListSelectorHider();
97    private Runnable mShowDropDownRunnable;
98
99    private Handler mHandler = new Handler();
100
101    private Rect mTempRect = new Rect();
102
103    private boolean mModal;
104
105    private int mLayoutDirection;
106
107    /**
108     * The provided prompt view should appear above list content.
109     *
110     * @see #setPromptPosition(int)
111     * @see #getPromptPosition()
112     * @see #setPromptView(View)
113     */
114    public static final int POSITION_PROMPT_ABOVE = 0;
115
116    /**
117     * The provided prompt view should appear below list content.
118     *
119     * @see #setPromptPosition(int)
120     * @see #getPromptPosition()
121     * @see #setPromptView(View)
122     */
123    public static final int POSITION_PROMPT_BELOW = 1;
124
125    /**
126     * Alias for {@link ViewGroup.LayoutParams#FILL_PARENT}. If used to specify a popup width, the
127     * popup will match the width of the anchor view. If used to specify a popup height, the popup
128     * will fill available space.
129     */
130    public static final int FILL_PARENT = ViewGroup.LayoutParams.FILL_PARENT;
131
132    /**
133     * Alias for {@link ViewGroup.LayoutParams#WRAP_CONTENT}. If used to specify a popup width, the
134     * popup will use the width of its content.
135     */
136    public static final int WRAP_CONTENT = ViewGroup.LayoutParams.WRAP_CONTENT;
137
138    /**
139     * Mode for {@link #setInputMethodMode(int)}: the requirements for the input method should be
140     * based on the focusability of the popup.  That is if it is focusable than it needs to work
141     * with the input method, else it doesn't.
142     */
143    public static final int INPUT_METHOD_FROM_FOCUSABLE = PopupWindow.INPUT_METHOD_FROM_FOCUSABLE;
144
145    /**
146     * Mode for {@link #setInputMethodMode(int)}: this popup always needs to work with an input
147     * method, regardless of whether it is focusable.  This means that it will always be displayed
148     * so that the user can also operate the input method while it is shown.
149     */
150    public static final int INPUT_METHOD_NEEDED = PopupWindow.INPUT_METHOD_NEEDED;
151
152    /**
153     * Mode for {@link #setInputMethodMode(int)}: this popup never needs to work with an input
154     * method, regardless of whether it is focusable.  This means that it will always be displayed
155     * to use as much space on the screen as needed, regardless of whether this covers the input
156     * method.
157     */
158    public static final int INPUT_METHOD_NOT_NEEDED = PopupWindow.INPUT_METHOD_NOT_NEEDED;
159
160    /**
161     * Create a new, empty popup window capable of displaying items from a ListAdapter. Backgrounds
162     * should be set using {@link #setBackgroundDrawable(Drawable)}.
163     *
164     * @param context Context used for contained views.
165     */
166    public ListPopupWindow(Context context) {
167        this(context, null, R.attr.listPopupWindowStyle);
168    }
169
170    /**
171     * Create a new, empty popup window capable of displaying items from a ListAdapter. Backgrounds
172     * should be set using {@link #setBackgroundDrawable(Drawable)}.
173     *
174     * @param context Context used for contained views.
175     * @param attrs   Attributes from inflating parent views used to style the popup.
176     */
177    public ListPopupWindow(Context context, AttributeSet attrs) {
178        this(context, attrs, R.attr.listPopupWindowStyle);
179    }
180
181    /**
182     * Create a new, empty popup window capable of displaying items from a ListAdapter. Backgrounds
183     * should be set using {@link #setBackgroundDrawable(Drawable)}.
184     *
185     * @param context      Context used for contained views.
186     * @param attrs        Attributes from inflating parent views used to style the popup.
187     * @param defStyleAttr Default style attribute to use for popup content.
188     */
189    public ListPopupWindow(Context context, AttributeSet attrs, int defStyleAttr) {
190        mContext = context;
191        mPopup = new PopupWindow(context, attrs, defStyleAttr);
192        mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
193        // Set the default layout direction to match the default locale one
194        final Locale locale = mContext.getResources().getConfiguration().locale;
195
196    }
197
198    /**
199     * Sets the adapter that provides the data and the views to represent the data in this popup
200     * window.
201     *
202     * @param adapter The adapter to use to create this window's content.
203     */
204    public void setAdapter(ListAdapter adapter) {
205        if (mObserver == null) {
206            mObserver = new PopupDataSetObserver();
207        } else if (mAdapter != null) {
208            mAdapter.unregisterDataSetObserver(mObserver);
209        }
210        mAdapter = adapter;
211        if (mAdapter != null) {
212            adapter.registerDataSetObserver(mObserver);
213        }
214
215        if (mDropDownList != null) {
216            mDropDownList.setAdapter(mAdapter);
217        }
218    }
219
220    /**
221     * Set where the optional prompt view should appear. The default is {@link
222     * #POSITION_PROMPT_ABOVE}.
223     *
224     * @param position A position constant declaring where the prompt should be displayed.
225     * @see #POSITION_PROMPT_ABOVE
226     * @see #POSITION_PROMPT_BELOW
227     */
228    public void setPromptPosition(int position) {
229        mPromptPosition = position;
230    }
231
232    /**
233     * @return Where the optional prompt view should appear.
234     * @see #POSITION_PROMPT_ABOVE
235     * @see #POSITION_PROMPT_BELOW
236     */
237    public int getPromptPosition() {
238        return mPromptPosition;
239    }
240
241    /**
242     * Set whether this window should be modal when shown.
243     *
244     * <p>If a popup window is modal, it will receive all touch and key input. If the user touches
245     * outside the popup window's content area the popup window will be dismissed.
246     *
247     * @param modal {@code true} if the popup window should be modal, {@code false} otherwise.
248     */
249    public void setModal(boolean modal) {
250        mModal = true;
251        mPopup.setFocusable(modal);
252    }
253
254    /**
255     * Returns whether the popup window will be modal when shown.
256     *
257     * @return {@code true} if the popup window will be modal, {@code false} otherwise.
258     */
259    public boolean isModal() {
260        return mModal;
261    }
262
263    /**
264     * Forces outside touches to be ignored. Normally if {@link #isDropDownAlwaysVisible()} is
265     * false, we allow outside touch to dismiss the dropdown. If this is set to true, then we ignore
266     * outside touch even when the drop down is not set to always visible.
267     *
268     * @hide Used only by AutoCompleteTextView to handle some internal special cases.
269     */
270    public void setForceIgnoreOutsideTouch(boolean forceIgnoreOutsideTouch) {
271        mForceIgnoreOutsideTouch = forceIgnoreOutsideTouch;
272    }
273
274    /**
275     * Sets whether the drop-down should remain visible under certain conditions.
276     *
277     * The drop-down will occupy the entire screen below {@link #getAnchorView} regardless of the
278     * size or content of the list.  {@link #getBackground()} will fill any space that is not used
279     * by the list.
280     *
281     * @param dropDownAlwaysVisible Whether to keep the drop-down visible.
282     * @hide Only used by AutoCompleteTextView under special conditions.
283     */
284    public void setDropDownAlwaysVisible(boolean dropDownAlwaysVisible) {
285        mDropDownAlwaysVisible = dropDownAlwaysVisible;
286    }
287
288    /**
289     * @return Whether the drop-down is visible under special conditions.
290     * @hide Only used by AutoCompleteTextView under special conditions.
291     */
292    public boolean isDropDownAlwaysVisible() {
293        return mDropDownAlwaysVisible;
294    }
295
296    /**
297     * Sets the operating mode for the soft input area.
298     *
299     * @param mode The desired mode, see {@link android.view.WindowManager.LayoutParams#softInputMode}
300     *             for the full list
301     * @see android.view.WindowManager.LayoutParams#softInputMode
302     * @see #getSoftInputMode()
303     */
304    public void setSoftInputMode(int mode) {
305        mPopup.setSoftInputMode(mode);
306    }
307
308    /**
309     * Returns the current value in {@link #setSoftInputMode(int)}.
310     *
311     * @see #setSoftInputMode(int)
312     * @see android.view.WindowManager.LayoutParams#softInputMode
313     */
314    public int getSoftInputMode() {
315        return mPopup.getSoftInputMode();
316    }
317
318    /**
319     * Sets a drawable to use as the list item selector.
320     *
321     * @param selector List selector drawable to use in the popup.
322     */
323    public void setListSelector(Drawable selector) {
324        mDropDownListHighlight = selector;
325    }
326
327    /**
328     * @return The background drawable for the popup window.
329     */
330    public Drawable getBackground() {
331        return mPopup.getBackground();
332    }
333
334    /**
335     * Sets a drawable to be the background for the popup window.
336     *
337     * @param d A drawable to set as the background.
338     */
339    public void setBackgroundDrawable(Drawable d) {
340        mPopup.setBackgroundDrawable(d);
341    }
342
343    /**
344     * Set an animation style to use when the popup window is shown or dismissed.
345     *
346     * @param animationStyle Animation style to use.
347     */
348    public void setAnimationStyle(int animationStyle) {
349        mPopup.setAnimationStyle(animationStyle);
350    }
351
352    /**
353     * Returns the animation style that will be used when the popup window is shown or dismissed.
354     *
355     * @return Animation style that will be used.
356     */
357    public int getAnimationStyle() {
358        return mPopup.getAnimationStyle();
359    }
360
361    /**
362     * Returns the view that will be used to anchor this popup.
363     *
364     * @return The popup's anchor view
365     */
366    public View getAnchorView() {
367        return mDropDownAnchorView;
368    }
369
370    /**
371     * Sets the popup's anchor view. This popup will always be positioned relative to the anchor
372     * view when shown.
373     *
374     * @param anchor The view to use as an anchor.
375     */
376    public void setAnchorView(View anchor) {
377        mDropDownAnchorView = anchor;
378    }
379
380    /**
381     * @return The horizontal offset of the popup from its anchor in pixels.
382     */
383    public int getHorizontalOffset() {
384        return mDropDownHorizontalOffset;
385    }
386
387    /**
388     * Set the horizontal offset of this popup from its anchor view in pixels.
389     *
390     * @param offset The horizontal offset of the popup from its anchor.
391     */
392    public void setHorizontalOffset(int offset) {
393        mDropDownHorizontalOffset = offset;
394    }
395
396    /**
397     * @return The vertical offset of the popup from its anchor in pixels.
398     */
399    public int getVerticalOffset() {
400        if (!mDropDownVerticalOffsetSet) {
401            return 0;
402        }
403        return mDropDownVerticalOffset;
404    }
405
406    /**
407     * Set the vertical offset of this popup from its anchor view in pixels.
408     *
409     * @param offset The vertical offset of the popup from its anchor.
410     */
411    public void setVerticalOffset(int offset) {
412        mDropDownVerticalOffset = offset;
413        mDropDownVerticalOffsetSet = true;
414    }
415
416    /**
417     * @return The width of the popup window in pixels.
418     */
419    public int getWidth() {
420        return mDropDownWidth;
421    }
422
423    /**
424     * Sets the width of the popup window in pixels. Can also be {@link #FILL_PARENT} or {@link
425     * #WRAP_CONTENT}.
426     *
427     * @param width Width of the popup window.
428     */
429    public void setWidth(int width) {
430        mDropDownWidth = width;
431    }
432
433    /**
434     * Sets the width of the popup window by the size of its content. The final width may be larger
435     * to accommodate styled window dressing.
436     *
437     * @param width Desired width of content in pixels.
438     */
439    public void setContentWidth(int width) {
440        Drawable popupBackground = mPopup.getBackground();
441        if (popupBackground != null) {
442            popupBackground.getPadding(mTempRect);
443            mDropDownWidth = mTempRect.left + mTempRect.right + width;
444        } else {
445            setWidth(width);
446        }
447    }
448
449    /**
450     * @return The height of the popup window in pixels.
451     */
452    public int getHeight() {
453        return mDropDownHeight;
454    }
455
456    /**
457     * Sets the height of the popup window in pixels. Can also be {@link #FILL_PARENT}.
458     *
459     * @param height Height of the popup window.
460     */
461    public void setHeight(int height) {
462        mDropDownHeight = height;
463    }
464
465    /**
466     * Sets a listener to receive events when a list item is clicked.
467     *
468     * @param clickListener Listener to register
469     * @see ListView#setOnItemClickListener(android.widget.AdapterView.OnItemClickListener)
470     */
471    public void setOnItemClickListener(AdapterView.OnItemClickListener clickListener) {
472        mItemClickListener = clickListener;
473    }
474
475    /**
476     * Sets a listener to receive events when a list item is selected.
477     *
478     * @param selectedListener Listener to register.
479     * @see ListView#setOnItemSelectedListener(android.widget.AdapterView.OnItemSelectedListener)
480     */
481    public void setOnItemSelectedListener(AdapterView.OnItemSelectedListener selectedListener) {
482        mItemSelectedListener = selectedListener;
483    }
484
485    /**
486     * Set a view to act as a user prompt for this popup window. Where the prompt view will appear
487     * is controlled by {@link #setPromptPosition(int)}.
488     *
489     * @param prompt View to use as an informational prompt.
490     */
491    public void setPromptView(View prompt) {
492        boolean showing = isShowing();
493        if (showing) {
494            removePromptView();
495        }
496        mPromptView = prompt;
497        if (showing) {
498            show();
499        }
500    }
501
502    /**
503     * Post a {@link #show()} call to the UI thread.
504     */
505    public void postShow() {
506        mHandler.post(mShowDropDownRunnable);
507    }
508
509    /**
510     * Show the popup list. If the list is already showing, this method will recalculate the popup's
511     * size and position.
512     */
513    public void show() {
514        int height = buildDropDown();
515
516        int widthSpec = 0;
517        int heightSpec = 0;
518
519        boolean noInputMethod = isInputMethodNotNeeded();
520
521        if (mPopup.isShowing()) {
522            if (mDropDownWidth == ViewGroup.LayoutParams.FILL_PARENT) {
523                // The call to PopupWindow's update method below can accept -1 for any
524                // value you do not want to update.
525                widthSpec = -1;
526            } else if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
527                widthSpec = getAnchorView().getWidth();
528            } else {
529                widthSpec = mDropDownWidth;
530            }
531
532            if (mDropDownHeight == ViewGroup.LayoutParams.FILL_PARENT) {
533                // The call to PopupWindow's update method below can accept -1 for any
534                // value you do not want to update.
535                heightSpec = noInputMethod ? height : ViewGroup.LayoutParams.FILL_PARENT;
536                if (noInputMethod) {
537                    mPopup.setWindowLayoutMode(
538                            mDropDownWidth == ViewGroup.LayoutParams.FILL_PARENT ?
539                                    ViewGroup.LayoutParams.FILL_PARENT : 0, 0);
540                } else {
541                    mPopup.setWindowLayoutMode(
542                            mDropDownWidth == ViewGroup.LayoutParams.FILL_PARENT ?
543                                    ViewGroup.LayoutParams.FILL_PARENT : 0,
544                            ViewGroup.LayoutParams.FILL_PARENT);
545                }
546            } else if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
547                heightSpec = height;
548            } else {
549                heightSpec = mDropDownHeight;
550            }
551
552            mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible);
553
554            mPopup.update(getAnchorView(), mDropDownHorizontalOffset,
555                    mDropDownVerticalOffset, widthSpec, heightSpec);
556        } else {
557            if (mDropDownWidth == ViewGroup.LayoutParams.FILL_PARENT) {
558                widthSpec = ViewGroup.LayoutParams.FILL_PARENT;
559            } else {
560                if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
561                    mPopup.setWidth(getAnchorView().getWidth());
562                } else {
563                    mPopup.setWidth(mDropDownWidth);
564                }
565            }
566
567            if (mDropDownHeight == ViewGroup.LayoutParams.FILL_PARENT) {
568                heightSpec = ViewGroup.LayoutParams.FILL_PARENT;
569            } else {
570                if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
571                    mPopup.setHeight(height);
572                } else {
573                    mPopup.setHeight(mDropDownHeight);
574                }
575            }
576
577            mPopup.setWindowLayoutMode(widthSpec, heightSpec);
578
579            // use outside touchable to dismiss drop down when touching outside of it, so
580            // only set this if the dropdown is not always visible
581            mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible);
582            mPopup.setTouchInterceptor(mTouchInterceptor);
583            mPopup.showAsDropDown(getAnchorView(),
584                    mDropDownHorizontalOffset, mDropDownVerticalOffset);
585            mDropDownList.setSelection(ListView.INVALID_POSITION);
586
587            if (!mModal || mDropDownList.isInTouchMode()) {
588                clearListSelection();
589            }
590            if (!mModal) {
591                mHandler.post(mHideSelector);
592            }
593        }
594    }
595
596    /**
597     * Dismiss the popup window.
598     */
599    public void dismiss() {
600        mPopup.dismiss();
601        removePromptView();
602        mPopup.setContentView(null);
603        mDropDownList = null;
604        mHandler.removeCallbacks(mResizePopupRunnable);
605    }
606
607    /**
608     * Set a listener to receive a callback when the popup is dismissed.
609     *
610     * @param listener Listener that will be notified when the popup is dismissed.
611     */
612    public void setOnDismissListener(PopupWindow.OnDismissListener listener) {
613        mPopup.setOnDismissListener(listener);
614    }
615
616    private void removePromptView() {
617        if (mPromptView != null) {
618            final ViewParent parent = mPromptView.getParent();
619            if (parent instanceof ViewGroup) {
620                final ViewGroup group = (ViewGroup) parent;
621                group.removeView(mPromptView);
622            }
623        }
624    }
625
626    /**
627     * Control how the popup operates with an input method: one of {@link
628     * #INPUT_METHOD_FROM_FOCUSABLE}, {@link #INPUT_METHOD_NEEDED}, or {@link
629     * #INPUT_METHOD_NOT_NEEDED}.
630     *
631     * <p>If the popup is showing, calling this method will take effect only the next time the popup
632     * is shown or through a manual call to the {@link #show()} method.</p>
633     *
634     * @see #getInputMethodMode()
635     * @see #show()
636     */
637    public void setInputMethodMode(int mode) {
638        mPopup.setInputMethodMode(mode);
639    }
640
641    /**
642     * Return the current value in {@link #setInputMethodMode(int)}.
643     *
644     * @see #setInputMethodMode(int)
645     */
646    public int getInputMethodMode() {
647        return mPopup.getInputMethodMode();
648    }
649
650    /**
651     * Set the selected position of the list. Only valid when {@link #isShowing()} == {@code true}.
652     *
653     * @param position List position to set as selected.
654     */
655    public void setSelection(int position) {
656        DropDownListView list = mDropDownList;
657        if (isShowing() && list != null) {
658            list.mListSelectionHidden = false;
659            list.setSelection(position);
660            if (list.getChoiceMode() != ListView.CHOICE_MODE_NONE) {
661                list.setItemChecked(position, true);
662            }
663        }
664    }
665
666    /**
667     * Clear any current list selection. Only valid when {@link #isShowing()} == {@code true}.
668     */
669    public void clearListSelection() {
670        final DropDownListView list = mDropDownList;
671        if (list != null) {
672            // WARNING: Please read the comment where mListSelectionHidden is declared
673            list.mListSelectionHidden = true;
674            //list.hideSelector();
675            list.requestLayout();
676        }
677    }
678
679    /**
680     * @return {@code true} if the popup is currently showing, {@code false} otherwise.
681     */
682    public boolean isShowing() {
683        return mPopup.isShowing();
684    }
685
686    /**
687     * @return {@code true} if this popup is configured to assume the user does not need to interact
688     *         with the IME while it is showing, {@code false} otherwise.
689     */
690    public boolean isInputMethodNotNeeded() {
691        return mPopup.getInputMethodMode() == INPUT_METHOD_NOT_NEEDED;
692    }
693
694    /**
695     * Perform an item click operation on the specified list adapter position.
696     *
697     * @param position Adapter position for performing the click
698     * @return true if the click action could be performed, false if not. (e.g. if the popup was not
699     *         showing, this method would return false.)
700     */
701    public boolean performItemClick(int position) {
702        if (isShowing()) {
703            if (mItemClickListener != null) {
704                final DropDownListView list = mDropDownList;
705                final View child = list.getChildAt(position - list.getFirstVisiblePosition());
706                final ListAdapter adapter = list.getAdapter();
707                mItemClickListener.onItemClick(list, child, position, adapter.getItemId(position));
708            }
709            return true;
710        }
711        return false;
712    }
713
714    /**
715     * @return The currently selected item or null if the popup is not showing.
716     */
717    public Object getSelectedItem() {
718        if (!isShowing()) {
719            return null;
720        }
721        return mDropDownList.getSelectedItem();
722    }
723
724    /**
725     * @return The position of the currently selected item or {@link ListView#INVALID_POSITION} if
726     *         {@link #isShowing()} == {@code false}.
727     * @see ListView#getSelectedItemPosition()
728     */
729    public int getSelectedItemPosition() {
730        if (!isShowing()) {
731            return ListView.INVALID_POSITION;
732        }
733        return mDropDownList.getSelectedItemPosition();
734    }
735
736    /**
737     * @return The ID of the currently selected item or {@link ListView#INVALID_ROW_ID} if {@link
738     *         #isShowing()} == {@code false}.
739     * @see ListView#getSelectedItemId()
740     */
741    public long getSelectedItemId() {
742        if (!isShowing()) {
743            return ListView.INVALID_ROW_ID;
744        }
745        return mDropDownList.getSelectedItemId();
746    }
747
748    /**
749     * @return The View for the currently selected item or null if {@link #isShowing()} == {@code
750     *         false}.
751     * @see ListView#getSelectedView()
752     */
753    public View getSelectedView() {
754        if (!isShowing()) {
755            return null;
756        }
757        return mDropDownList.getSelectedView();
758    }
759
760    /**
761     * @return The {@link ListView} displayed within the popup window. Only valid when {@link
762     *         #isShowing()} == {@code true}.
763     */
764    public ListView getListView() {
765        return mDropDownList;
766    }
767
768    /**
769     * The maximum number of list items that can be visible and still have the list expand when
770     * touched.
771     *
772     * @param max Max number of items that can be visible and still allow the list to expand.
773     */
774    void setListItemExpandMax(int max) {
775        mListItemExpandMaximum = max;
776    }
777
778    /**
779     * Filter key down events. By forwarding key down events to this function, views using non-modal
780     * ListPopupWindow can have it handle key selection of items.
781     *
782     * @param keyCode keyCode param passed to the host view's onKeyDown
783     * @param event   event param passed to the host view's onKeyDown
784     * @return true if the event was handled, false if it was ignored.
785     * @see #setModal(boolean)
786     */
787    public boolean onKeyDown(int keyCode, KeyEvent event) {
788        // when the drop down is shown, we drive it directly
789        if (isShowing()) {
790            // the key events are forwarded to the list in the drop down view
791            // note that ListView handles space but we don't want that to happen
792            // also if selection is not currently in the drop down, then don't
793            // let center or enter presses go there since that would cause it
794            // to select one of its items
795            if (keyCode != KeyEvent.KEYCODE_SPACE
796                    && (mDropDownList.getSelectedItemPosition() >= 0
797                    || (keyCode != KeyEvent.KEYCODE_ENTER
798                    && keyCode != KeyEvent.KEYCODE_DPAD_CENTER))) {
799                int curIndex = mDropDownList.getSelectedItemPosition();
800                boolean consumed;
801
802                final boolean below = !mPopup.isAboveAnchor();
803
804                final ListAdapter adapter = mAdapter;
805
806                boolean allEnabled;
807                int firstItem = Integer.MAX_VALUE;
808                int lastItem = Integer.MIN_VALUE;
809
810                if (adapter != null) {
811                    allEnabled = adapter.areAllItemsEnabled();
812                    firstItem = allEnabled ? 0 :
813                            mDropDownList.lookForSelectablePosition(0, true);
814                    lastItem = allEnabled ? adapter.getCount() - 1 :
815                            mDropDownList.lookForSelectablePosition(adapter.getCount() - 1, false);
816                }
817
818                if ((below && keyCode == KeyEvent.KEYCODE_DPAD_UP && curIndex <= firstItem) ||
819                        (!below && keyCode == KeyEvent.KEYCODE_DPAD_DOWN && curIndex >= lastItem)) {
820                    // When the selection is at the top, we block the key
821                    // event to prevent focus from moving.
822                    clearListSelection();
823                    mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
824                    show();
825                    return true;
826                } else {
827                    // WARNING: Please read the comment where mListSelectionHidden
828                    //          is declared
829                    mDropDownList.mListSelectionHidden = false;
830                }
831
832                consumed = mDropDownList.onKeyDown(keyCode, event);
833                if (DEBUG) {
834                    Log.v(TAG, "Key down: code=" + keyCode + " list consumed=" + consumed);
835                }
836
837                if (consumed) {
838                    // If it handled the key event, then the user is
839                    // navigating in the list, so we should put it in front.
840                    mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
841                    // Here's a little trick we need to do to make sure that
842                    // the list view is actually showing its focus indicator,
843                    // by ensuring it has focus and getting its window out
844                    // of touch mode.
845                    mDropDownList.requestFocusFromTouch();
846                    show();
847
848                    switch (keyCode) {
849                        // avoid passing the focus from the text view to the
850                        // next component
851                        case KeyEvent.KEYCODE_ENTER:
852                        case KeyEvent.KEYCODE_DPAD_CENTER:
853                        case KeyEvent.KEYCODE_DPAD_DOWN:
854                        case KeyEvent.KEYCODE_DPAD_UP:
855                            return true;
856                    }
857                } else {
858                    if (below && keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
859                        // when the selection is at the bottom, we block the
860                        // event to avoid going to the next focusable widget
861                        if (curIndex == lastItem) {
862                            return true;
863                        }
864                    } else if (!below && keyCode == KeyEvent.KEYCODE_DPAD_UP &&
865                            curIndex == firstItem) {
866                        return true;
867                    }
868                }
869            }
870        }
871
872        return false;
873    }
874
875    /**
876     * Filter key down events. By forwarding key up events to this function, views using non-modal
877     * ListPopupWindow can have it handle key selection of items.
878     *
879     * @param keyCode keyCode param passed to the host view's onKeyUp
880     * @param event   event param passed to the host view's onKeyUp
881     * @return true if the event was handled, false if it was ignored.
882     * @see #setModal(boolean)
883     */
884    public boolean onKeyUp(int keyCode, KeyEvent event) {
885        if (isShowing() && mDropDownList.getSelectedItemPosition() >= 0) {
886            boolean consumed = mDropDownList.onKeyUp(keyCode, event);
887            if (consumed) {
888                switch (keyCode) {
889                    // if the list accepts the key events and the key event
890                    // was a click, the text view gets the selected item
891                    // from the drop down as its content
892                    case KeyEvent.KEYCODE_ENTER:
893                    case KeyEvent.KEYCODE_DPAD_CENTER:
894                        dismiss();
895                        break;
896                }
897            }
898            return consumed;
899        }
900        return false;
901    }
902
903    /**
904     * <p>Builds the popup window's content and returns the height the popup should have. Returns -1
905     * when the content already exists.</p>
906     *
907     * @return the content's height or -1 if content already exists
908     */
909    private int buildDropDown() {
910        ViewGroup dropDownView;
911        int otherHeights = 0;
912
913        if (mDropDownList == null) {
914            Context context = mContext;
915
916            /**
917             * This Runnable exists for the sole purpose of checking if the view layout has got
918             * completed and if so call showDropDown to display the drop down. This is used to show
919             * the drop down as soon as possible after user opens up the search dialog, without
920             * waiting for the normal UI pipeline to do it's job which is slower than this method.
921             */
922            mShowDropDownRunnable = new Runnable() {
923                public void run() {
924                    // View layout should be all done before displaying the drop down.
925                    View view = getAnchorView();
926                    if (view != null && view.getWindowToken() != null) {
927                        show();
928                    }
929                }
930            };
931
932            mDropDownList = new DropDownListView(context, !mModal);
933            if (mDropDownListHighlight != null) {
934                mDropDownList.setSelector(mDropDownListHighlight);
935            }
936            mDropDownList.setAdapter(mAdapter);
937            mDropDownList.setOnItemClickListener(mItemClickListener);
938            mDropDownList.setFocusable(true);
939            mDropDownList.setFocusableInTouchMode(true);
940            mDropDownList.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
941                public void onItemSelected(AdapterView<?> parent, View view,
942                        int position, long id) {
943
944                    if (position != -1) {
945                        DropDownListView dropDownList = mDropDownList;
946
947                        if (dropDownList != null) {
948                            dropDownList.mListSelectionHidden = false;
949                        }
950                    }
951                }
952
953                public void onNothingSelected(AdapterView<?> parent) {
954                }
955            });
956            mDropDownList.setOnScrollListener(mScrollListener);
957
958            if (mItemSelectedListener != null) {
959                mDropDownList.setOnItemSelectedListener(mItemSelectedListener);
960            }
961
962            dropDownView = mDropDownList;
963
964            View hintView = mPromptView;
965            if (hintView != null) {
966                // if a hint has been specified, we accomodate more space for it and
967                // add a text view in the drop down menu, at the bottom of the list
968                LinearLayout hintContainer = new LinearLayout(context);
969                hintContainer.setOrientation(LinearLayout.VERTICAL);
970
971                LinearLayout.LayoutParams hintParams = new LinearLayout.LayoutParams(
972                        ViewGroup.LayoutParams.FILL_PARENT, 0, 1.0f
973                );
974
975                switch (mPromptPosition) {
976                    case POSITION_PROMPT_BELOW:
977                        hintContainer.addView(dropDownView, hintParams);
978                        hintContainer.addView(hintView);
979                        break;
980
981                    case POSITION_PROMPT_ABOVE:
982                        hintContainer.addView(hintView);
983                        hintContainer.addView(dropDownView, hintParams);
984                        break;
985
986                    default:
987                        Log.e(TAG, "Invalid hint position " + mPromptPosition);
988                        break;
989                }
990
991                // measure the hint's height to find how much more vertical space
992                // we need to add to the drop down's height
993                int widthSpec = MeasureSpec.makeMeasureSpec(mDropDownWidth, MeasureSpec.AT_MOST);
994                int heightSpec = MeasureSpec.UNSPECIFIED;
995                hintView.measure(widthSpec, heightSpec);
996
997                hintParams = (LinearLayout.LayoutParams) hintView.getLayoutParams();
998                otherHeights = hintView.getMeasuredHeight() + hintParams.topMargin
999                        + hintParams.bottomMargin;
1000
1001                dropDownView = hintContainer;
1002            }
1003
1004            mPopup.setContentView(dropDownView);
1005        } else {
1006            dropDownView = (ViewGroup) mPopup.getContentView();
1007            final View view = mPromptView;
1008            if (view != null) {
1009                LinearLayout.LayoutParams hintParams =
1010                        (LinearLayout.LayoutParams) view.getLayoutParams();
1011                otherHeights = view.getMeasuredHeight() + hintParams.topMargin
1012                        + hintParams.bottomMargin;
1013            }
1014        }
1015
1016        // getMaxAvailableHeight() subtracts the padding, so we put it back
1017        // to get the available height for the whole window
1018        int padding = 0;
1019        Drawable background = mPopup.getBackground();
1020        if (background != null) {
1021            background.getPadding(mTempRect);
1022            padding = mTempRect.top + mTempRect.bottom;
1023
1024            // If we don't have an explicit vertical offset, determine one from the window
1025            // background so that content will line up.
1026            if (!mDropDownVerticalOffsetSet) {
1027                mDropDownVerticalOffset = -mTempRect.top;
1028            }
1029        } else {
1030            mTempRect.setEmpty();
1031        }
1032
1033        // Max height available on the screen for a popup.
1034        boolean ignoreBottomDecorations =
1035                mPopup.getInputMethodMode() == PopupWindow.INPUT_METHOD_NOT_NEEDED;
1036        final int maxHeight = getMaxAvailableHeight(
1037                getAnchorView(), mDropDownVerticalOffset, ignoreBottomDecorations);
1038
1039        if (mDropDownAlwaysVisible || mDropDownHeight == ViewGroup.LayoutParams.FILL_PARENT) {
1040            return maxHeight + padding;
1041        }
1042
1043        final int childWidthSpec;
1044        switch (mDropDownWidth) {
1045            case ViewGroup.LayoutParams.WRAP_CONTENT:
1046                childWidthSpec = MeasureSpec.makeMeasureSpec(
1047                        mContext.getResources().getDisplayMetrics().widthPixels -
1048                                (mTempRect.left + mTempRect.right),
1049                        MeasureSpec.AT_MOST);
1050                break;
1051            case ViewGroup.LayoutParams.FILL_PARENT:
1052                childWidthSpec = MeasureSpec.makeMeasureSpec(
1053                        mContext.getResources().getDisplayMetrics().widthPixels -
1054                                (mTempRect.left + mTempRect.right),
1055                        MeasureSpec.EXACTLY);
1056                break;
1057            default:
1058                childWidthSpec = MeasureSpec.makeMeasureSpec(mDropDownWidth, MeasureSpec.EXACTLY);
1059                break;
1060        }
1061
1062        final int listContent = mDropDownList.measureHeightOfChildrenCompat(childWidthSpec,
1063                0, DropDownListView.NO_POSITION, maxHeight - otherHeights, -1);
1064        // add padding only if the list has items in it, that way we don't show
1065        // the popup if it is not needed
1066        if (listContent > 0) {
1067            otherHeights += padding;
1068        }
1069
1070        return listContent + otherHeights;
1071    }
1072
1073    /**
1074     * Copied from PopupWindow.java of JB
1075     *
1076     * Returns the maximum height that is available for the popup to be completely shown, optionally
1077     * ignoring any bottom decorations such as the input method. It is recommended that this height
1078     * be the maximum for the popup's height, otherwise it is possible that the popup will be
1079     * clipped.
1080     *
1081     * @param anchor                  The view on which the popup window must be anchored.
1082     * @param yOffset                 y offset from the view's bottom edge
1083     * @param ignoreBottomDecorations if true, the height returned will be all the way to the bottom
1084     *                                of the display, ignoring any bottom decorations
1085     * @return The maximum available height for the popup to be completely shown.
1086     * @hide Pending API council approval.
1087     */
1088    public int getMaxAvailableHeight(View anchor, int yOffset, boolean ignoreBottomDecorations) {
1089        final Rect displayFrame = new Rect();
1090        anchor.getWindowVisibleDisplayFrame(displayFrame);
1091
1092        int[] mDrawingLocation = new int[2];
1093        final int[] anchorPos = mDrawingLocation;
1094        anchor.getLocationOnScreen(anchorPos);
1095
1096        int bottomEdge = displayFrame.bottom;
1097        if (ignoreBottomDecorations) {
1098            Resources res = anchor.getContext().getResources();
1099            bottomEdge = res.getDisplayMetrics().heightPixels;
1100        }
1101        final int distanceToBottom = bottomEdge - (anchorPos[1] + anchor.getHeight()) - yOffset;
1102        final int distanceToTop = anchorPos[1] - displayFrame.top + yOffset;
1103
1104        // anchorPos[1] is distance from anchor to top of screen
1105        int returnedHeight = Math.max(distanceToBottom, distanceToTop);
1106        if (mPopup.getBackground() != null) {
1107            mPopup.getBackground().getPadding(mTempRect);
1108            returnedHeight -= mTempRect.top + mTempRect.bottom;
1109        }
1110
1111        return returnedHeight;
1112    }
1113
1114    /**
1115     * <p>Wrapper class for a ListView. This wrapper can hijack the focus to make sure the list uses
1116     * the appropriate drawables and states when displayed on screen within a drop down. The focus
1117     * is never actually passed to the drop down in this mode; the list only looks focused.</p>
1118     */
1119    private static class DropDownListView extends ListView {
1120
1121        private static final String TAG = ListPopupWindow.TAG + ".DropDownListView";
1122
1123        /*
1124        * WARNING: This is a workaround for a touch mode issue.
1125        *
1126        * Touch mode is propagated lazily to windows. This causes problems in
1127        * the following scenario:
1128        * - Type something in the AutoCompleteTextView and get some results
1129        * - Move down with the d-pad to select an item in the list
1130        * - Move up with the d-pad until the selection disappears
1131        * - Type more text in the AutoCompleteTextView *using the soft keyboard*
1132        *   and get new results; you are now in touch mode
1133        * - The selection comes back on the first item in the list, even though
1134        *   the list is supposed to be in touch mode
1135        *
1136        * Using the soft keyboard triggers the touch mode change but that change
1137        * is propagated to our window only after the first list layout, therefore
1138        * after the list attempts to resurrect the selection.
1139        *
1140        * The trick to work around this issue is to pretend the list is in touch
1141        * mode when we know that the selection should not appear, that is when
1142        * we know the user moved the selection away from the list.
1143        *
1144        * This boolean is set to true whenever we explicitly hide the list's
1145        * selection and reset to false whenever we know the user moved the
1146        * selection back to the list.
1147        *
1148        * When this boolean is true, isInTouchMode() returns true, otherwise it
1149        * returns super.isInTouchMode().
1150        */
1151        private boolean mListSelectionHidden;
1152
1153
1154        public static final int INVALID_POSITION = -1;
1155
1156        static final int NO_POSITION = -1;
1157
1158
1159        /**
1160         * True if this wrapper should fake focus.
1161         */
1162        private boolean mHijackFocus;
1163
1164        /**
1165         * <p>Creates a new list view wrapper.</p>
1166         *
1167         * @param context this view's context
1168         */
1169        public DropDownListView(Context context, boolean hijackFocus) {
1170            super(context, null, R.attr.dropDownListViewStyle);
1171            mHijackFocus = hijackFocus;
1172            setCacheColorHint(0); // Transparent, since the background drawable could be anything.
1173        }
1174
1175        /**
1176         * Find a position that can be selected (i.e., is not a separator).
1177         *
1178         * @param position The starting position to look at.
1179         * @param lookDown Whether to look down for other positions.
1180         * @return The next selectable position starting at position and then searching either up or
1181         *         down. Returns {@link #INVALID_POSITION} if nothing can be found.
1182         */
1183        private int lookForSelectablePosition(int position, boolean lookDown) {
1184            final ListAdapter adapter = getAdapter();
1185            if (adapter == null || isInTouchMode()) {
1186                return INVALID_POSITION;
1187            }
1188
1189            final int count = adapter.getCount();
1190            if (!getAdapter().areAllItemsEnabled()) {
1191                if (lookDown) {
1192                    position = Math.max(0, position);
1193                    while (position < count && !adapter.isEnabled(position)) {
1194                        position++;
1195                    }
1196                } else {
1197                    position = Math.min(position, count - 1);
1198                    while (position >= 0 && !adapter.isEnabled(position)) {
1199                        position--;
1200                    }
1201                }
1202
1203                if (position < 0 || position >= count) {
1204                    return INVALID_POSITION;
1205                }
1206                return position;
1207            } else {
1208                if (position < 0 || position >= count) {
1209                    return INVALID_POSITION;
1210                }
1211                return position;
1212            }
1213        }
1214
1215        @Override
1216        public boolean isInTouchMode() {
1217            // WARNING: Please read the comment where mListSelectionHidden is declared
1218            return (mHijackFocus && mListSelectionHidden) || super.isInTouchMode();
1219        }
1220
1221        /**
1222         * <p>Returns the focus state in the drop down.</p>
1223         *
1224         * @return true always if hijacking focus
1225         */
1226        @Override
1227        public boolean hasWindowFocus() {
1228            return mHijackFocus || super.hasWindowFocus();
1229        }
1230
1231        /**
1232         * <p>Returns the focus state in the drop down.</p>
1233         *
1234         * @return true always if hijacking focus
1235         */
1236        @Override
1237        public boolean isFocused() {
1238            return mHijackFocus || super.isFocused();
1239        }
1240
1241        /**
1242         * <p>Returns the focus state in the drop down.</p>
1243         *
1244         * @return true always if hijacking focus
1245         */
1246        @Override
1247        public boolean hasFocus() {
1248            return mHijackFocus || super.hasFocus();
1249        }
1250
1251        /**
1252         * Measures the height of the given range of children (inclusive) and returns the height
1253         * with this ListView's padding and divider heights included. If maxHeight is provided, the
1254         * measuring will stop when the current height reaches maxHeight.
1255         *
1256         * @param widthMeasureSpec             The width measure spec to be given to a child's
1257         *                                     {@link View#measure(int, int)}.
1258         * @param startPosition                The position of the first child to be shown.
1259         * @param endPosition                  The (inclusive) position of the last child to be
1260         *                                     shown. Specify {@link #NO_POSITION} if the last child
1261         *                                     should be the last available child from the adapter.
1262         * @param maxHeight                    The maximum height that will be returned (if all the
1263         *                                     children don't fit in this value, this value will be
1264         *                                     returned).
1265         * @param disallowPartialChildPosition In general, whether the returned height should only
1266         *                                     contain entire children. This is more powerful--it is
1267         *                                     the first inclusive position at which partial
1268         *                                     children will not be allowed. Example: it looks nice
1269         *                                     to have at least 3 completely visible children, and
1270         *                                     in portrait this will most likely fit; but in
1271         *                                     landscape there could be times when even 2 children
1272         *                                     can not be completely shown, so a value of 2
1273         *                                     (remember, inclusive) would be good (assuming
1274         *                                     startPosition is 0).
1275         * @return The height of this ListView with the given children.
1276         */
1277        final int measureHeightOfChildrenCompat(int widthMeasureSpec, int startPosition,
1278                int endPosition, final int maxHeight,
1279                int disallowPartialChildPosition) {
1280
1281            final int paddingTop = getListPaddingTop();
1282            final int paddingBottom = getListPaddingBottom();
1283            final int paddingLeft = getListPaddingLeft();
1284            final int paddingRight = getListPaddingRight();
1285            final int reportedDividerHeight = getDividerHeight();
1286            final Drawable divider = getDivider();
1287
1288            final ListAdapter adapter = getAdapter();
1289
1290            if (adapter == null) {
1291                return paddingTop + paddingBottom;
1292            }
1293
1294            // Include the padding of the list
1295            int returnedHeight = paddingTop + paddingBottom;
1296            final int dividerHeight = ((reportedDividerHeight > 0) && divider != null)
1297                    ? reportedDividerHeight : 0;
1298
1299            // The previous height value that was less than maxHeight and contained
1300            // no partial children
1301            int prevHeightWithoutPartialChild = 0;
1302
1303            View child = null;
1304            int viewType = 0;
1305            int count = adapter.getCount();
1306            for (int i = 0; i < count; i++) {
1307                int newType = adapter.getItemViewType(i);
1308                if (newType != viewType) {
1309                    child = null;
1310                    viewType = newType;
1311                }
1312                child = adapter.getView(i, child, this);
1313                ;
1314
1315                // Compute child height spec
1316                int heightMeasureSpec;
1317                int childHeight = child.getLayoutParams().height;
1318                if (childHeight > 0) {
1319                    heightMeasureSpec = MeasureSpec
1320                            .makeMeasureSpec(childHeight, MeasureSpec.EXACTLY);
1321                } else {
1322                    heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
1323                }
1324                child.measure(widthMeasureSpec, heightMeasureSpec);
1325
1326                if (i > 0) {
1327                    // Count the divider for all but one child
1328                    returnedHeight += dividerHeight;
1329                }
1330
1331                returnedHeight += child.getMeasuredHeight();
1332
1333                if (returnedHeight >= maxHeight) {
1334                    // We went over, figure out which height to return.  If returnedHeight >
1335                    // maxHeight, then the i'th position did not fit completely.
1336                    return (disallowPartialChildPosition >= 0) // Disallowing is enabled (> -1)
1337                            && (i > disallowPartialChildPosition) // We've past the min pos
1338                            && (prevHeightWithoutPartialChild > 0) // We have a prev height
1339                            && (returnedHeight != maxHeight) // i'th child did not fit completely
1340                            ? prevHeightWithoutPartialChild
1341                            : maxHeight;
1342                }
1343
1344                if ((disallowPartialChildPosition >= 0) && (i >= disallowPartialChildPosition)) {
1345                    prevHeightWithoutPartialChild = returnedHeight;
1346                }
1347            }
1348
1349            // At this point, we went through the range of children, and they each
1350            // completely fit, so return the returnedHeight
1351            return returnedHeight;
1352        }
1353
1354    }
1355
1356    private class PopupDataSetObserver extends DataSetObserver {
1357
1358        @Override
1359        public void onChanged() {
1360            if (isShowing()) {
1361                // Resize the popup to fit new content
1362                show();
1363            }
1364        }
1365
1366        @Override
1367        public void onInvalidated() {
1368            dismiss();
1369        }
1370    }
1371
1372    private class ListSelectorHider implements Runnable {
1373
1374        public void run() {
1375            clearListSelection();
1376        }
1377    }
1378
1379    private class ResizePopupRunnable implements Runnable {
1380
1381        public void run() {
1382            if (mDropDownList != null && mDropDownList.getCount() > mDropDownList.getChildCount() &&
1383                    mDropDownList.getChildCount() <= mListItemExpandMaximum) {
1384                mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
1385                show();
1386            }
1387        }
1388    }
1389
1390    private class PopupTouchInterceptor implements OnTouchListener {
1391
1392        public boolean onTouch(View v, MotionEvent event) {
1393            final int action = event.getAction();
1394            final int x = (int) event.getX();
1395            final int y = (int) event.getY();
1396
1397            if (action == MotionEvent.ACTION_DOWN &&
1398                    mPopup != null && mPopup.isShowing() &&
1399                    (x >= 0 && x < mPopup.getWidth() && y >= 0 && y < mPopup.getHeight())) {
1400                mHandler.postDelayed(mResizePopupRunnable, EXPAND_LIST_TIMEOUT);
1401            } else if (action == MotionEvent.ACTION_UP) {
1402                mHandler.removeCallbacks(mResizePopupRunnable);
1403            }
1404            return false;
1405        }
1406    }
1407
1408    private class PopupScrollListener implements ListView.OnScrollListener {
1409
1410        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
1411                int totalItemCount) {
1412
1413        }
1414
1415        public void onScrollStateChanged(AbsListView view, int scrollState) {
1416            if (scrollState == SCROLL_STATE_TOUCH_SCROLL &&
1417                    !isInputMethodNotNeeded() && mPopup.getContentView() != null) {
1418                mHandler.removeCallbacks(mResizePopupRunnable);
1419                mResizePopupRunnable.run();
1420            }
1421        }
1422    }
1423}