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