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