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