1 /*
2  * Copyright (C) 2007 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.DrawableRes;
20 import android.content.Context;
21 import android.content.res.Resources.Theme;
22 import android.content.res.TypedArray;
23 import android.database.DataSetObserver;
24 import android.graphics.Rect;
25 import android.graphics.drawable.Drawable;
26 import android.text.Editable;
27 import android.text.Selection;
28 import android.text.TextUtils;
29 import android.text.TextWatcher;
30 import android.util.AttributeSet;
31 import android.util.Log;
32 import android.view.ContextThemeWrapper;
33 import android.view.KeyEvent;
34 import android.view.LayoutInflater;
35 import android.view.View;
36 import android.view.ViewGroup;
37 import android.view.ViewGroup.LayoutParams;
38 import android.view.WindowManager;
39 import android.view.inputmethod.CompletionInfo;
40 import android.view.inputmethod.EditorInfo;
41 import android.view.inputmethod.InputMethodManager;
42 import com.android.internal.R;
43 import java.lang.ref.WeakReference;
44 
45 /**
46  * <p>An editable text view that shows completion suggestions automatically
47  * while the user is typing. The list of suggestions is displayed in a drop
48  * down menu from which the user can choose an item to replace the content
49  * of the edit box with.</p>
50  *
51  * <p>The drop down can be dismissed at any time by pressing the back key or,
52  * if no item is selected in the drop down, by pressing the enter/dpad center
53  * key.</p>
54  *
55  * <p>The list of suggestions is obtained from a data adapter and appears
56  * only after a given number of characters defined by
57  * {@link #getThreshold() the threshold}.</p>
58  *
59  * <p>The following code snippet shows how to create a text view which suggests
60  * various countries names while the user is typing:</p>
61  *
62  * <pre class="prettyprint">
63  * public class CountriesActivity extends Activity {
64  *     protected void onCreate(Bundle icicle) {
65  *         super.onCreate(icicle);
66  *         setContentView(R.layout.countries);
67  *
68  *         ArrayAdapter&lt;String&gt; adapter = new ArrayAdapter&lt;String&gt;(this,
69  *                 android.R.layout.simple_dropdown_item_1line, COUNTRIES);
70  *         AutoCompleteTextView textView = (AutoCompleteTextView)
71  *                 findViewById(R.id.countries_list);
72  *         textView.setAdapter(adapter);
73  *     }
74  *
75  *     private static final String[] COUNTRIES = new String[] {
76  *         "Belgium", "France", "Italy", "Germany", "Spain"
77  *     };
78  * }
79  * </pre>
80  *
81  * <p>See the <a href="{@docRoot}guide/topics/ui/controls/text.html">Text Fields</a>
82  * guide.</p>
83  *
84  * @attr ref android.R.styleable#AutoCompleteTextView_completionHint
85  * @attr ref android.R.styleable#AutoCompleteTextView_completionThreshold
86  * @attr ref android.R.styleable#AutoCompleteTextView_completionHintView
87  * @attr ref android.R.styleable#AutoCompleteTextView_dropDownSelector
88  * @attr ref android.R.styleable#AutoCompleteTextView_dropDownAnchor
89  * @attr ref android.R.styleable#AutoCompleteTextView_dropDownWidth
90  * @attr ref android.R.styleable#AutoCompleteTextView_dropDownHeight
91  * @attr ref android.R.styleable#ListPopupWindow_dropDownVerticalOffset
92  * @attr ref android.R.styleable#ListPopupWindow_dropDownHorizontalOffset
93  */
94 public class AutoCompleteTextView extends EditText implements Filter.FilterListener {
95     static final boolean DEBUG = false;
96     static final String TAG = "AutoCompleteTextView";
97 
98     static final int EXPAND_MAX = 3;
99 
100     /** Context used to inflate the popup window or dialog. */
101     private final Context mPopupContext;
102 
103     private final ListPopupWindow mPopup;
104     private final PassThroughClickListener mPassThroughClickListener;
105 
106     private CharSequence mHintText;
107     private TextView mHintView;
108     private int mHintResource;
109 
110     private ListAdapter mAdapter;
111     private Filter mFilter;
112     private int mThreshold;
113 
114     private int mDropDownAnchorId;
115 
116     private AdapterView.OnItemClickListener mItemClickListener;
117     private AdapterView.OnItemSelectedListener mItemSelectedListener;
118 
119     private boolean mDropDownDismissedOnCompletion = true;
120 
121     private int mLastKeyCode = KeyEvent.KEYCODE_UNKNOWN;
122     private boolean mOpenBefore;
123 
124     private Validator mValidator = null;
125 
126     // Set to true when text is set directly and no filtering shall be performed
127     private boolean mBlockCompletion;
128 
129     // When set, an update in the underlying adapter will update the result list popup.
130     // Set to false when the list is hidden to prevent asynchronous updates to popup the list again.
131     private boolean mPopupCanBeUpdated = true;
132 
133     private PopupDataSetObserver mObserver;
134 
135     /**
136      * Constructs a new auto-complete text view with the given context's theme.
137      *
138      * @param context The Context the view is running in, through which it can
139      *                access the current theme, resources, etc.
140      */
AutoCompleteTextView(Context context)141     public AutoCompleteTextView(Context context) {
142         this(context, null);
143     }
144 
145     /**
146      * Constructs a new auto-complete text view with the given context's theme
147      * and the supplied attribute set.
148      *
149      * @param context The Context the view is running in, through which it can
150      *                access the current theme, resources, etc.
151      * @param attrs The attributes of the XML tag that is inflating the view.
152      */
AutoCompleteTextView(Context context, AttributeSet attrs)153     public AutoCompleteTextView(Context context, AttributeSet attrs) {
154         this(context, attrs, R.attr.autoCompleteTextViewStyle);
155     }
156 
157     /**
158      * Constructs a new auto-complete text view with the given context's theme,
159      * the supplied attribute set, and default style attribute.
160      *
161      * @param context The Context the view is running in, through which it can
162      *                access the current theme, resources, etc.
163      * @param attrs The attributes of the XML tag that is inflating the view.
164      * @param defStyleAttr An attribute in the current theme that contains a
165      *                     reference to a style resource that supplies default
166      *                     values for the view. Can be 0 to not look for
167      *                     defaults.
168      */
AutoCompleteTextView(Context context, AttributeSet attrs, int defStyleAttr)169     public AutoCompleteTextView(Context context, AttributeSet attrs, int defStyleAttr) {
170         this(context, attrs, defStyleAttr, 0);
171     }
172 
173     /**
174      * Constructs a new auto-complete text view with the given context's theme,
175      * the supplied attribute set, and default styles.
176      *
177      * @param context The Context the view is running in, through which it can
178      *                access the current theme, resources, etc.
179      * @param attrs The attributes of the XML tag that is inflating the view.
180      * @param defStyleAttr An attribute in the current theme that contains a
181      *                     reference to a style resource that supplies default
182      *                     values for the view. Can be 0 to not look for
183      *                     defaults.
184      * @param defStyleRes A resource identifier of a style resource that
185      *                    supplies default values for the view, used only if
186      *                    defStyleAttr is 0 or can not be found in the theme.
187      *                    Can be 0 to not look for defaults.
188      */
AutoCompleteTextView( Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)189     public AutoCompleteTextView(
190             Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
191         this(context, attrs, defStyleAttr, defStyleRes, null);
192     }
193 
194     /**
195      * Constructs a new auto-complete text view with the given context, the
196      * supplied attribute set, default styles, and the theme against which the
197      * completion popup should be inflated.
198      *
199      * @param context The context against which the view is inflated, which
200      *                provides access to the current theme, resources, etc.
201      * @param attrs The attributes of the XML tag that is inflating the view.
202      * @param defStyleAttr An attribute in the current theme that contains a
203      *                     reference to a style resource that supplies default
204      *                     values for the view. Can be 0 to not look for
205      *                     defaults.
206      * @param defStyleRes A resource identifier of a style resource that
207      *                    supplies default values for the view, used only if
208      *                    defStyleAttr is 0 or can not be found in the theme.
209      *                    Can be 0 to not look for defaults.
210      * @param popupTheme The theme against which the completion popup window
211      *                   should be inflated. May be {@code null} to use the
212      *                   view theme. If set, this will override any value
213      *                   specified by
214      *                   {@link android.R.styleable#AutoCompleteTextView_popupTheme}.
215      */
AutoCompleteTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes, Theme popupTheme)216     public AutoCompleteTextView(Context context, AttributeSet attrs, int defStyleAttr,
217             int defStyleRes, Theme popupTheme) {
218         super(context, attrs, defStyleAttr, defStyleRes);
219 
220         final TypedArray a = context.obtainStyledAttributes(
221                 attrs, R.styleable.AutoCompleteTextView, defStyleAttr, defStyleRes);
222 
223         if (popupTheme != null) {
224             mPopupContext = new ContextThemeWrapper(context, popupTheme);
225         } else {
226             final int popupThemeResId = a.getResourceId(
227                     R.styleable.AutoCompleteTextView_popupTheme, 0);
228             if (popupThemeResId != 0) {
229                 mPopupContext = new ContextThemeWrapper(context, popupThemeResId);
230             } else {
231                 mPopupContext = context;
232             }
233         }
234 
235         // Load attributes used within the popup against the popup context.
236         final TypedArray pa;
237         if (mPopupContext != context) {
238             pa = mPopupContext.obtainStyledAttributes(
239                     attrs, R.styleable.AutoCompleteTextView, defStyleAttr, defStyleRes);
240         } else {
241             pa = a;
242         }
243 
244         final Drawable popupListSelector = pa.getDrawable(
245                 R.styleable.AutoCompleteTextView_dropDownSelector);
246         final int popupWidth = pa.getLayoutDimension(
247                 R.styleable.AutoCompleteTextView_dropDownWidth, LayoutParams.WRAP_CONTENT);
248         final int popupHeight = pa.getLayoutDimension(
249                 R.styleable.AutoCompleteTextView_dropDownHeight, LayoutParams.WRAP_CONTENT);
250         final int popupHintLayoutResId = pa.getResourceId(
251                 R.styleable.AutoCompleteTextView_completionHintView, R.layout.simple_dropdown_hint);
252         final CharSequence popupHintText = pa.getText(
253                 R.styleable.AutoCompleteTextView_completionHint);
254 
255         if (pa != a) {
256             pa.recycle();
257         }
258 
259         mPopup = new ListPopupWindow(mPopupContext, attrs, defStyleAttr, defStyleRes);
260         mPopup.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
261         mPopup.setPromptPosition(ListPopupWindow.POSITION_PROMPT_BELOW);
262         mPopup.setListSelector(popupListSelector);
263         mPopup.setOnItemClickListener(new DropDownItemClickListener());
264 
265         // For dropdown width, the developer can specify a specific width, or
266         // MATCH_PARENT (for full screen width), or WRAP_CONTENT (to match the
267         // width of the anchored view).
268         mPopup.setWidth(popupWidth);
269         mPopup.setHeight(popupHeight);
270 
271         // Completion hint must be set after specifying hint layout.
272         mHintResource = popupHintLayoutResId;
273         setCompletionHint(popupHintText);
274 
275         // Get the anchor's id now, but the view won't be ready, so wait to
276         // actually get the view and store it in mDropDownAnchorView lazily in
277         // getDropDownAnchorView later. Defaults to NO_ID, in which case the
278         // getDropDownAnchorView method will simply return this TextView, as a
279         // default anchoring point.
280         mDropDownAnchorId = a.getResourceId(
281                 R.styleable.AutoCompleteTextView_dropDownAnchor, View.NO_ID);
282 
283         mThreshold = a.getInt(R.styleable.AutoCompleteTextView_completionThreshold, 2);
284 
285         a.recycle();
286 
287         // Always turn on the auto complete input type flag, since it
288         // makes no sense to use this widget without it.
289         int inputType = getInputType();
290         if ((inputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
291             inputType |= EditorInfo.TYPE_TEXT_FLAG_AUTO_COMPLETE;
292             setRawInputType(inputType);
293         }
294 
295         setFocusable(true);
296 
297         addTextChangedListener(new MyWatcher());
298 
299         mPassThroughClickListener = new PassThroughClickListener();
300         super.setOnClickListener(mPassThroughClickListener);
301     }
302 
303     @Override
setOnClickListener(OnClickListener listener)304     public void setOnClickListener(OnClickListener listener) {
305         mPassThroughClickListener.mWrapped = listener;
306     }
307 
308     /**
309      * Private hook into the on click event, dispatched from {@link PassThroughClickListener}
310      */
onClickImpl()311     private void onClickImpl() {
312         // If the dropdown is showing, bring the keyboard to the front
313         // when the user touches the text field.
314         if (isPopupShowing()) {
315             ensureImeVisible(true);
316         }
317     }
318 
319     /**
320      * <p>Sets the optional hint text that is displayed at the bottom of the
321      * the matching list.  This can be used as a cue to the user on how to
322      * best use the list, or to provide extra information.</p>
323      *
324      * @param hint the text to be displayed to the user
325      *
326      * @see #getCompletionHint()
327      *
328      * @attr ref android.R.styleable#AutoCompleteTextView_completionHint
329      */
setCompletionHint(CharSequence hint)330     public void setCompletionHint(CharSequence hint) {
331         mHintText = hint;
332         if (hint != null) {
333             if (mHintView == null) {
334                 final TextView hintView = (TextView) LayoutInflater.from(mPopupContext).inflate(
335                         mHintResource, null).findViewById(R.id.text1);
336                 hintView.setText(mHintText);
337                 mHintView = hintView;
338                 mPopup.setPromptView(hintView);
339             } else {
340                 mHintView.setText(hint);
341             }
342         } else {
343             mPopup.setPromptView(null);
344             mHintView = null;
345         }
346     }
347 
348     /**
349      * Gets the optional hint text displayed at the bottom of the the matching list.
350      *
351      * @return The hint text, if any
352      *
353      * @see #setCompletionHint(CharSequence)
354      *
355      * @attr ref android.R.styleable#AutoCompleteTextView_completionHint
356      */
getCompletionHint()357     public CharSequence getCompletionHint() {
358         return mHintText;
359     }
360 
361     /**
362      * <p>Returns the current width for the auto-complete drop down list. This can
363      * be a fixed width, or {@link ViewGroup.LayoutParams#MATCH_PARENT} to fill the screen, or
364      * {@link ViewGroup.LayoutParams#WRAP_CONTENT} to fit the width of its anchor view.</p>
365      *
366      * @return the width for the drop down list
367      *
368      * @attr ref android.R.styleable#AutoCompleteTextView_dropDownWidth
369      */
getDropDownWidth()370     public int getDropDownWidth() {
371         return mPopup.getWidth();
372     }
373 
374     /**
375      * <p>Sets the current width for the auto-complete drop down list. This can
376      * be a fixed width, or {@link ViewGroup.LayoutParams#MATCH_PARENT} to fill the screen, or
377      * {@link ViewGroup.LayoutParams#WRAP_CONTENT} to fit the width of its anchor view.</p>
378      *
379      * @param width the width to use
380      *
381      * @attr ref android.R.styleable#AutoCompleteTextView_dropDownWidth
382      */
setDropDownWidth(int width)383     public void setDropDownWidth(int width) {
384         mPopup.setWidth(width);
385     }
386 
387     /**
388      * <p>Returns the current height for the auto-complete drop down list. This can
389      * be a fixed height, or {@link ViewGroup.LayoutParams#MATCH_PARENT} to fill
390      * the screen, or {@link ViewGroup.LayoutParams#WRAP_CONTENT} to fit the height
391      * of the drop down's content.</p>
392      *
393      * @return the height for the drop down list
394      *
395      * @attr ref android.R.styleable#AutoCompleteTextView_dropDownHeight
396      */
getDropDownHeight()397     public int getDropDownHeight() {
398         return mPopup.getHeight();
399     }
400 
401     /**
402      * <p>Sets the current height for the auto-complete drop down list. This can
403      * be a fixed height, or {@link ViewGroup.LayoutParams#MATCH_PARENT} to fill
404      * the screen, or {@link ViewGroup.LayoutParams#WRAP_CONTENT} to fit the height
405      * of the drop down's content.</p>
406      *
407      * @param height the height to use
408      *
409      * @attr ref android.R.styleable#AutoCompleteTextView_dropDownHeight
410      */
setDropDownHeight(int height)411     public void setDropDownHeight(int height) {
412         mPopup.setHeight(height);
413     }
414 
415     /**
416      * <p>Returns the id for the view that the auto-complete drop down list is anchored to.</p>
417      *
418      * @return the view's id, or {@link View#NO_ID} if none specified
419      *
420      * @attr ref android.R.styleable#AutoCompleteTextView_dropDownAnchor
421      */
getDropDownAnchor()422     public int getDropDownAnchor() {
423         return mDropDownAnchorId;
424     }
425 
426     /**
427      * <p>Sets the view to which the auto-complete drop down list should anchor. The view
428      * corresponding to this id will not be loaded until the next time it is needed to avoid
429      * loading a view which is not yet instantiated.</p>
430      *
431      * @param id the id to anchor the drop down list view to
432      *
433      * @attr ref android.R.styleable#AutoCompleteTextView_dropDownAnchor
434      */
setDropDownAnchor(int id)435     public void setDropDownAnchor(int id) {
436         mDropDownAnchorId = id;
437         mPopup.setAnchorView(null);
438     }
439 
440     /**
441      * <p>Gets the background of the auto-complete drop-down list.</p>
442      *
443      * @return the background drawable
444      *
445      * @attr ref android.R.styleable#PopupWindow_popupBackground
446      */
getDropDownBackground()447     public Drawable getDropDownBackground() {
448         return mPopup.getBackground();
449     }
450 
451     /**
452      * <p>Sets the background of the auto-complete drop-down list.</p>
453      *
454      * @param d the drawable to set as the background
455      *
456      * @attr ref android.R.styleable#PopupWindow_popupBackground
457      */
setDropDownBackgroundDrawable(Drawable d)458     public void setDropDownBackgroundDrawable(Drawable d) {
459         mPopup.setBackgroundDrawable(d);
460     }
461 
462     /**
463      * <p>Sets the background of the auto-complete drop-down list.</p>
464      *
465      * @param id the id of the drawable to set as the background
466      *
467      * @attr ref android.R.styleable#PopupWindow_popupBackground
468      */
setDropDownBackgroundResource(@rawableRes int id)469     public void setDropDownBackgroundResource(@DrawableRes int id) {
470         mPopup.setBackgroundDrawable(getContext().getDrawable(id));
471     }
472 
473     /**
474      * <p>Sets the vertical offset used for the auto-complete drop-down list.</p>
475      *
476      * @param offset the vertical offset
477      *
478      * @attr ref android.R.styleable#ListPopupWindow_dropDownVerticalOffset
479      */
setDropDownVerticalOffset(int offset)480     public void setDropDownVerticalOffset(int offset) {
481         mPopup.setVerticalOffset(offset);
482     }
483 
484     /**
485      * <p>Gets the vertical offset used for the auto-complete drop-down list.</p>
486      *
487      * @return the vertical offset
488      *
489      * @attr ref android.R.styleable#ListPopupWindow_dropDownVerticalOffset
490      */
getDropDownVerticalOffset()491     public int getDropDownVerticalOffset() {
492         return mPopup.getVerticalOffset();
493     }
494 
495     /**
496      * <p>Sets the horizontal offset used for the auto-complete drop-down list.</p>
497      *
498      * @param offset the horizontal offset
499      *
500      * @attr ref android.R.styleable#ListPopupWindow_dropDownHorizontalOffset
501      */
setDropDownHorizontalOffset(int offset)502     public void setDropDownHorizontalOffset(int offset) {
503         mPopup.setHorizontalOffset(offset);
504     }
505 
506     /**
507      * <p>Gets the horizontal offset used for the auto-complete drop-down list.</p>
508      *
509      * @return the horizontal offset
510      *
511      * @attr ref android.R.styleable#ListPopupWindow_dropDownHorizontalOffset
512      */
getDropDownHorizontalOffset()513     public int getDropDownHorizontalOffset() {
514         return mPopup.getHorizontalOffset();
515     }
516 
517      /**
518      * <p>Sets the animation style of the auto-complete drop-down list.</p>
519      *
520      * <p>If the drop-down is showing, calling this method will take effect only
521      * the next time the drop-down is shown.</p>
522      *
523      * @param animationStyle animation style to use when the drop-down appears
524      *      and disappears.  Set to -1 for the default animation, 0 for no
525      *      animation, or a resource identifier for an explicit animation.
526      *
527      * @hide Pending API council approval
528      */
setDropDownAnimationStyle(int animationStyle)529     public void setDropDownAnimationStyle(int animationStyle) {
530         mPopup.setAnimationStyle(animationStyle);
531     }
532 
533     /**
534      * <p>Returns the animation style that is used when the drop-down list appears and disappears
535      * </p>
536      *
537      * @return the animation style that is used when the drop-down list appears and disappears
538      *
539      * @hide Pending API council approval
540      */
getDropDownAnimationStyle()541     public int getDropDownAnimationStyle() {
542         return mPopup.getAnimationStyle();
543     }
544 
545     /**
546      * @return Whether the drop-down is visible as long as there is {@link #enoughToFilter()}
547      *
548      * @hide Pending API council approval
549      */
isDropDownAlwaysVisible()550     public boolean isDropDownAlwaysVisible() {
551         return mPopup.isDropDownAlwaysVisible();
552     }
553 
554     /**
555      * Sets whether the drop-down should remain visible as long as there is there is
556      * {@link #enoughToFilter()}.  This is useful if an unknown number of results are expected
557      * to show up in the adapter sometime in the future.
558      *
559      * The drop-down will occupy the entire screen below {@link #getDropDownAnchor} regardless
560      * of the size or content of the list.  {@link #getDropDownBackground()} will fill any space
561      * that is not used by the list.
562      *
563      * @param dropDownAlwaysVisible Whether to keep the drop-down visible.
564      *
565      * @hide Pending API council approval
566      */
setDropDownAlwaysVisible(boolean dropDownAlwaysVisible)567     public void setDropDownAlwaysVisible(boolean dropDownAlwaysVisible) {
568         mPopup.setDropDownAlwaysVisible(dropDownAlwaysVisible);
569     }
570 
571     /**
572      * Checks whether the drop-down is dismissed when a suggestion is clicked.
573      *
574      * @hide Pending API council approval
575      */
isDropDownDismissedOnCompletion()576     public boolean isDropDownDismissedOnCompletion() {
577         return mDropDownDismissedOnCompletion;
578     }
579 
580     /**
581      * Sets whether the drop-down is dismissed when a suggestion is clicked. This is
582      * true by default.
583      *
584      * @param dropDownDismissedOnCompletion Whether to dismiss the drop-down.
585      *
586      * @hide Pending API council approval
587      */
setDropDownDismissedOnCompletion(boolean dropDownDismissedOnCompletion)588     public void setDropDownDismissedOnCompletion(boolean dropDownDismissedOnCompletion) {
589         mDropDownDismissedOnCompletion = dropDownDismissedOnCompletion;
590     }
591 
592     /**
593      * <p>Returns the number of characters the user must type before the drop
594      * down list is shown.</p>
595      *
596      * @return the minimum number of characters to type to show the drop down
597      *
598      * @see #setThreshold(int)
599      *
600      * @attr ref android.R.styleable#AutoCompleteTextView_completionThreshold
601      */
getThreshold()602     public int getThreshold() {
603         return mThreshold;
604     }
605 
606     /**
607      * <p>Specifies the minimum number of characters the user has to type in the
608      * edit box before the drop down list is shown.</p>
609      *
610      * <p>When <code>threshold</code> is less than or equals 0, a threshold of
611      * 1 is applied.</p>
612      *
613      * @param threshold the number of characters to type before the drop down
614      *                  is shown
615      *
616      * @see #getThreshold()
617      *
618      * @attr ref android.R.styleable#AutoCompleteTextView_completionThreshold
619      */
setThreshold(int threshold)620     public void setThreshold(int threshold) {
621         if (threshold <= 0) {
622             threshold = 1;
623         }
624 
625         mThreshold = threshold;
626     }
627 
628     /**
629      * <p>Sets the listener that will be notified when the user clicks an item
630      * in the drop down list.</p>
631      *
632      * @param l the item click listener
633      */
setOnItemClickListener(AdapterView.OnItemClickListener l)634     public void setOnItemClickListener(AdapterView.OnItemClickListener l) {
635         mItemClickListener = l;
636     }
637 
638     /**
639      * <p>Sets the listener that will be notified when the user selects an item
640      * in the drop down list.</p>
641      *
642      * @param l the item selected listener
643      */
setOnItemSelectedListener(AdapterView.OnItemSelectedListener l)644     public void setOnItemSelectedListener(AdapterView.OnItemSelectedListener l) {
645         mItemSelectedListener = l;
646     }
647 
648     /**
649      * <p>Returns the listener that is notified whenever the user clicks an item
650      * in the drop down list.</p>
651      *
652      * @return the item click listener
653      *
654      * @deprecated Use {@link #getOnItemClickListener()} intead
655      */
656     @Deprecated
getItemClickListener()657     public AdapterView.OnItemClickListener getItemClickListener() {
658         return mItemClickListener;
659     }
660 
661     /**
662      * <p>Returns the listener that is notified whenever the user selects an
663      * item in the drop down list.</p>
664      *
665      * @return the item selected listener
666      *
667      * @deprecated Use {@link #getOnItemSelectedListener()} intead
668      */
669     @Deprecated
getItemSelectedListener()670     public AdapterView.OnItemSelectedListener getItemSelectedListener() {
671         return mItemSelectedListener;
672     }
673 
674     /**
675      * <p>Returns the listener that is notified whenever the user clicks an item
676      * in the drop down list.</p>
677      *
678      * @return the item click listener
679      */
getOnItemClickListener()680     public AdapterView.OnItemClickListener getOnItemClickListener() {
681         return mItemClickListener;
682     }
683 
684     /**
685      * <p>Returns the listener that is notified whenever the user selects an
686      * item in the drop down list.</p>
687      *
688      * @return the item selected listener
689      */
getOnItemSelectedListener()690     public AdapterView.OnItemSelectedListener getOnItemSelectedListener() {
691         return mItemSelectedListener;
692     }
693 
694     /**
695      * Set a listener that will be invoked whenever the AutoCompleteTextView's
696      * list of completions is dismissed.
697      * @param dismissListener Listener to invoke when completions are dismissed
698      */
setOnDismissListener(final OnDismissListener dismissListener)699     public void setOnDismissListener(final OnDismissListener dismissListener) {
700         PopupWindow.OnDismissListener wrappedListener = null;
701         if (dismissListener != null) {
702             wrappedListener = new PopupWindow.OnDismissListener() {
703                 @Override public void onDismiss() {
704                     dismissListener.onDismiss();
705                 }
706             };
707         }
708         mPopup.setOnDismissListener(wrappedListener);
709     }
710 
711     /**
712      * <p>Returns a filterable list adapter used for auto completion.</p>
713      *
714      * @return a data adapter used for auto completion
715      */
getAdapter()716     public ListAdapter getAdapter() {
717         return mAdapter;
718     }
719 
720     /**
721      * <p>Changes the list of data used for auto completion. The provided list
722      * must be a filterable list adapter.</p>
723      *
724      * <p>The caller is still responsible for managing any resources used by the adapter.
725      * Notably, when the AutoCompleteTextView is closed or released, the adapter is not notified.
726      * A common case is the use of {@link android.widget.CursorAdapter}, which
727      * contains a {@link android.database.Cursor} that must be closed.  This can be done
728      * automatically (see
729      * {@link android.app.Activity#startManagingCursor(android.database.Cursor)
730      * startManagingCursor()}),
731      * or by manually closing the cursor when the AutoCompleteTextView is dismissed.</p>
732      *
733      * @param adapter the adapter holding the auto completion data
734      *
735      * @see #getAdapter()
736      * @see android.widget.Filterable
737      * @see android.widget.ListAdapter
738      */
setAdapter(T adapter)739     public <T extends ListAdapter & Filterable> void setAdapter(T adapter) {
740         if (mObserver == null) {
741             mObserver = new PopupDataSetObserver(this);
742         } else if (mAdapter != null) {
743             mAdapter.unregisterDataSetObserver(mObserver);
744         }
745         mAdapter = adapter;
746         if (mAdapter != null) {
747             //noinspection unchecked
748             mFilter = ((Filterable) mAdapter).getFilter();
749             adapter.registerDataSetObserver(mObserver);
750         } else {
751             mFilter = null;
752         }
753 
754         mPopup.setAdapter(mAdapter);
755     }
756 
757     @Override
onKeyPreIme(int keyCode, KeyEvent event)758     public boolean onKeyPreIme(int keyCode, KeyEvent event) {
759         if (keyCode == KeyEvent.KEYCODE_BACK && isPopupShowing()
760                 && !mPopup.isDropDownAlwaysVisible()) {
761             // special case for the back key, we do not even try to send it
762             // to the drop down list but instead, consume it immediately
763             if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
764                 KeyEvent.DispatcherState state = getKeyDispatcherState();
765                 if (state != null) {
766                     state.startTracking(event, this);
767                 }
768                 return true;
769             } else if (event.getAction() == KeyEvent.ACTION_UP) {
770                 KeyEvent.DispatcherState state = getKeyDispatcherState();
771                 if (state != null) {
772                     state.handleUpEvent(event);
773                 }
774                 if (event.isTracking() && !event.isCanceled()) {
775                     dismissDropDown();
776                     return true;
777                 }
778             }
779         }
780         return super.onKeyPreIme(keyCode, event);
781     }
782 
783     @Override
onKeyUp(int keyCode, KeyEvent event)784     public boolean onKeyUp(int keyCode, KeyEvent event) {
785         boolean consumed = mPopup.onKeyUp(keyCode, event);
786         if (consumed) {
787             switch (keyCode) {
788             // if the list accepts the key events and the key event
789             // was a click, the text view gets the selected item
790             // from the drop down as its content
791             case KeyEvent.KEYCODE_ENTER:
792             case KeyEvent.KEYCODE_DPAD_CENTER:
793             case KeyEvent.KEYCODE_TAB:
794                 if (event.hasNoModifiers()) {
795                     performCompletion();
796                 }
797                 return true;
798             }
799         }
800 
801         if (isPopupShowing() && keyCode == KeyEvent.KEYCODE_TAB && event.hasNoModifiers()) {
802             performCompletion();
803             return true;
804         }
805 
806         return super.onKeyUp(keyCode, event);
807     }
808 
809     @Override
onKeyDown(int keyCode, KeyEvent event)810     public boolean onKeyDown(int keyCode, KeyEvent event) {
811         if (mPopup.onKeyDown(keyCode, event)) {
812             return true;
813         }
814 
815         if (!isPopupShowing()) {
816             switch(keyCode) {
817             case KeyEvent.KEYCODE_DPAD_DOWN:
818                 if (event.hasNoModifiers()) {
819                     performValidation();
820                 }
821             }
822         }
823 
824         if (isPopupShowing() && keyCode == KeyEvent.KEYCODE_TAB && event.hasNoModifiers()) {
825             return true;
826         }
827 
828         mLastKeyCode = keyCode;
829         boolean handled = super.onKeyDown(keyCode, event);
830         mLastKeyCode = KeyEvent.KEYCODE_UNKNOWN;
831 
832         if (handled && isPopupShowing()) {
833             clearListSelection();
834         }
835 
836         return handled;
837     }
838 
839     /**
840      * Returns <code>true</code> if the amount of text in the field meets
841      * or exceeds the {@link #getThreshold} requirement.  You can override
842      * this to impose a different standard for when filtering will be
843      * triggered.
844      */
enoughToFilter()845     public boolean enoughToFilter() {
846         if (DEBUG) Log.v(TAG, "Enough to filter: len=" + getText().length()
847                 + " threshold=" + mThreshold);
848         return getText().length() >= mThreshold;
849     }
850 
851     /**
852      * This is used to watch for edits to the text view.  Note that we call
853      * to methods on the auto complete text view class so that we can access
854      * private vars without going through thunks.
855      */
856     private class MyWatcher implements TextWatcher {
afterTextChanged(Editable s)857         public void afterTextChanged(Editable s) {
858             doAfterTextChanged();
859         }
beforeTextChanged(CharSequence s, int start, int count, int after)860         public void beforeTextChanged(CharSequence s, int start, int count, int after) {
861             doBeforeTextChanged();
862         }
onTextChanged(CharSequence s, int start, int before, int count)863         public void onTextChanged(CharSequence s, int start, int before, int count) {
864         }
865     }
866 
doBeforeTextChanged()867     void doBeforeTextChanged() {
868         if (mBlockCompletion) return;
869 
870         // when text is changed, inserted or deleted, we attempt to show
871         // the drop down
872         mOpenBefore = isPopupShowing();
873         if (DEBUG) Log.v(TAG, "before text changed: open=" + mOpenBefore);
874     }
875 
doAfterTextChanged()876     void doAfterTextChanged() {
877         if (mBlockCompletion) return;
878 
879         // if the list was open before the keystroke, but closed afterwards,
880         // then something in the keystroke processing (an input filter perhaps)
881         // called performCompletion() and we shouldn't do any more processing.
882         if (DEBUG) Log.v(TAG, "after text changed: openBefore=" + mOpenBefore
883                 + " open=" + isPopupShowing());
884         if (mOpenBefore && !isPopupShowing()) {
885             return;
886         }
887 
888         // the drop down is shown only when a minimum number of characters
889         // was typed in the text view
890         if (enoughToFilter()) {
891             if (mFilter != null) {
892                 mPopupCanBeUpdated = true;
893                 performFiltering(getText(), mLastKeyCode);
894             }
895         } else {
896             // drop down is automatically dismissed when enough characters
897             // are deleted from the text view
898             if (!mPopup.isDropDownAlwaysVisible()) {
899                 dismissDropDown();
900             }
901             if (mFilter != null) {
902                 mFilter.filter(null);
903             }
904         }
905     }
906 
907     /**
908      * <p>Indicates whether the popup menu is showing.</p>
909      *
910      * @return true if the popup menu is showing, false otherwise
911      */
isPopupShowing()912     public boolean isPopupShowing() {
913         return mPopup.isShowing();
914     }
915 
916     /**
917      * <p>Converts the selected item from the drop down list into a sequence
918      * of character that can be used in the edit box.</p>
919      *
920      * @param selectedItem the item selected by the user for completion
921      *
922      * @return a sequence of characters representing the selected suggestion
923      */
convertSelectionToString(Object selectedItem)924     protected CharSequence convertSelectionToString(Object selectedItem) {
925         return mFilter.convertResultToString(selectedItem);
926     }
927 
928     /**
929      * <p>Clear the list selection.  This may only be temporary, as user input will often bring
930      * it back.
931      */
clearListSelection()932     public void clearListSelection() {
933         mPopup.clearListSelection();
934     }
935 
936     /**
937      * Set the position of the dropdown view selection.
938      *
939      * @param position The position to move the selector to.
940      */
setListSelection(int position)941     public void setListSelection(int position) {
942         mPopup.setSelection(position);
943     }
944 
945     /**
946      * Get the position of the dropdown view selection, if there is one.  Returns
947      * {@link ListView#INVALID_POSITION ListView.INVALID_POSITION} if there is no dropdown or if
948      * there is no selection.
949      *
950      * @return the position of the current selection, if there is one, or
951      * {@link ListView#INVALID_POSITION ListView.INVALID_POSITION} if not.
952      *
953      * @see ListView#getSelectedItemPosition()
954      */
getListSelection()955     public int getListSelection() {
956         return mPopup.getSelectedItemPosition();
957     }
958 
959     /**
960      * <p>Starts filtering the content of the drop down list. The filtering
961      * pattern is the content of the edit box. Subclasses should override this
962      * method to filter with a different pattern, for instance a substring of
963      * <code>text</code>.</p>
964      *
965      * @param text the filtering pattern
966      * @param keyCode the last character inserted in the edit box; beware that
967      * this will be null when text is being added through a soft input method.
968      */
969     @SuppressWarnings({ "UnusedDeclaration" })
performFiltering(CharSequence text, int keyCode)970     protected void performFiltering(CharSequence text, int keyCode) {
971         mFilter.filter(text, this);
972     }
973 
974     /**
975      * <p>Performs the text completion by converting the selected item from
976      * the drop down list into a string, replacing the text box's content with
977      * this string and finally dismissing the drop down menu.</p>
978      */
performCompletion()979     public void performCompletion() {
980         performCompletion(null, -1, -1);
981     }
982 
983     @Override
onCommitCompletion(CompletionInfo completion)984     public void onCommitCompletion(CompletionInfo completion) {
985         if (isPopupShowing()) {
986             mPopup.performItemClick(completion.getPosition());
987         }
988     }
989 
performCompletion(View selectedView, int position, long id)990     private void performCompletion(View selectedView, int position, long id) {
991         if (isPopupShowing()) {
992             Object selectedItem;
993             if (position < 0) {
994                 selectedItem = mPopup.getSelectedItem();
995             } else {
996                 selectedItem = mAdapter.getItem(position);
997             }
998             if (selectedItem == null) {
999                 Log.w(TAG, "performCompletion: no selected item");
1000                 return;
1001             }
1002 
1003             mBlockCompletion = true;
1004             replaceText(convertSelectionToString(selectedItem));
1005             mBlockCompletion = false;
1006 
1007             if (mItemClickListener != null) {
1008                 final ListPopupWindow list = mPopup;
1009 
1010                 if (selectedView == null || position < 0) {
1011                     selectedView = list.getSelectedView();
1012                     position = list.getSelectedItemPosition();
1013                     id = list.getSelectedItemId();
1014                 }
1015                 mItemClickListener.onItemClick(list.getListView(), selectedView, position, id);
1016             }
1017         }
1018 
1019         if (mDropDownDismissedOnCompletion && !mPopup.isDropDownAlwaysVisible()) {
1020             dismissDropDown();
1021         }
1022     }
1023 
1024     /**
1025      * Identifies whether the view is currently performing a text completion, so subclasses
1026      * can decide whether to respond to text changed events.
1027      */
isPerformingCompletion()1028     public boolean isPerformingCompletion() {
1029         return mBlockCompletion;
1030     }
1031 
1032     /**
1033      * Like {@link #setText(CharSequence)}, except that it can disable filtering.
1034      *
1035      * @param filter If <code>false</code>, no filtering will be performed
1036      *        as a result of this call.
1037      */
setText(CharSequence text, boolean filter)1038     public void setText(CharSequence text, boolean filter) {
1039         if (filter) {
1040             setText(text);
1041         } else {
1042             mBlockCompletion = true;
1043             setText(text);
1044             mBlockCompletion = false;
1045         }
1046     }
1047 
1048     /**
1049      * <p>Performs the text completion by replacing the current text by the
1050      * selected item. Subclasses should override this method to avoid replacing
1051      * the whole content of the edit box.</p>
1052      *
1053      * @param text the selected suggestion in the drop down list
1054      */
replaceText(CharSequence text)1055     protected void replaceText(CharSequence text) {
1056         clearComposingText();
1057 
1058         setText(text);
1059         // make sure we keep the caret at the end of the text view
1060         Editable spannable = getText();
1061         Selection.setSelection(spannable, spannable.length());
1062     }
1063 
1064     /** {@inheritDoc} */
onFilterComplete(int count)1065     public void onFilterComplete(int count) {
1066         updateDropDownForFilter(count);
1067     }
1068 
updateDropDownForFilter(int count)1069     private void updateDropDownForFilter(int count) {
1070         // Not attached to window, don't update drop-down
1071         if (getWindowVisibility() == View.GONE) return;
1072 
1073         /*
1074          * This checks enoughToFilter() again because filtering requests
1075          * are asynchronous, so the result may come back after enough text
1076          * has since been deleted to make it no longer appropriate
1077          * to filter.
1078          */
1079 
1080         final boolean dropDownAlwaysVisible = mPopup.isDropDownAlwaysVisible();
1081         final boolean enoughToFilter = enoughToFilter();
1082         if ((count > 0 || dropDownAlwaysVisible) && enoughToFilter) {
1083             if (hasFocus() && hasWindowFocus() && mPopupCanBeUpdated) {
1084                 showDropDown();
1085             }
1086         } else if (!dropDownAlwaysVisible && isPopupShowing()) {
1087             dismissDropDown();
1088             // When the filter text is changed, the first update from the adapter may show an empty
1089             // count (when the query is being performed on the network). Future updates when some
1090             // content has been retrieved should still be able to update the list.
1091             mPopupCanBeUpdated = true;
1092         }
1093     }
1094 
1095     @Override
onWindowFocusChanged(boolean hasWindowFocus)1096     public void onWindowFocusChanged(boolean hasWindowFocus) {
1097         super.onWindowFocusChanged(hasWindowFocus);
1098         if (!hasWindowFocus && !mPopup.isDropDownAlwaysVisible()) {
1099             dismissDropDown();
1100         }
1101     }
1102 
1103     @Override
onDisplayHint(int hint)1104     protected void onDisplayHint(int hint) {
1105         super.onDisplayHint(hint);
1106         switch (hint) {
1107             case INVISIBLE:
1108                 if (!mPopup.isDropDownAlwaysVisible()) {
1109                     dismissDropDown();
1110                 }
1111                 break;
1112         }
1113     }
1114 
1115     @Override
onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect)1116     protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
1117         super.onFocusChanged(focused, direction, previouslyFocusedRect);
1118 
1119         if (isTemporarilyDetached()) {
1120             // If we are temporarily in the detach state, then do nothing.
1121             return;
1122         }
1123 
1124         // Perform validation if the view is losing focus.
1125         if (!focused) {
1126             performValidation();
1127         }
1128         if (!focused && !mPopup.isDropDownAlwaysVisible()) {
1129             dismissDropDown();
1130         }
1131     }
1132 
1133     @Override
onAttachedToWindow()1134     protected void onAttachedToWindow() {
1135         super.onAttachedToWindow();
1136     }
1137 
1138     @Override
onDetachedFromWindow()1139     protected void onDetachedFromWindow() {
1140         dismissDropDown();
1141         super.onDetachedFromWindow();
1142     }
1143 
1144     /**
1145      * <p>Closes the drop down if present on screen.</p>
1146      */
dismissDropDown()1147     public void dismissDropDown() {
1148         InputMethodManager imm = InputMethodManager.peekInstance();
1149         if (imm != null) {
1150             imm.displayCompletions(this, null);
1151         }
1152         mPopup.dismiss();
1153         mPopupCanBeUpdated = false;
1154     }
1155 
1156     @Override
setFrame(final int l, int t, final int r, int b)1157     protected boolean setFrame(final int l, int t, final int r, int b) {
1158         boolean result = super.setFrame(l, t, r, b);
1159 
1160         if (isPopupShowing()) {
1161             showDropDown();
1162         }
1163 
1164         return result;
1165     }
1166 
1167     /**
1168      * Issues a runnable to show the dropdown as soon as possible.
1169      *
1170      * @hide internal used only by SearchDialog
1171      */
showDropDownAfterLayout()1172     public void showDropDownAfterLayout() {
1173         mPopup.postShow();
1174     }
1175 
1176     /**
1177      * Ensures that the drop down is not obscuring the IME.
1178      * @param visible whether the ime should be in front. If false, the ime is pushed to
1179      * the background.
1180      * @hide internal used only here and SearchDialog
1181      */
ensureImeVisible(boolean visible)1182     public void ensureImeVisible(boolean visible) {
1183         mPopup.setInputMethodMode(visible
1184                 ? ListPopupWindow.INPUT_METHOD_NEEDED : ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
1185         if (mPopup.isDropDownAlwaysVisible() || (mFilter != null && enoughToFilter())) {
1186             showDropDown();
1187         }
1188     }
1189 
1190     /**
1191      * @hide internal used only here and SearchDialog
1192      */
isInputMethodNotNeeded()1193     public boolean isInputMethodNotNeeded() {
1194         return mPopup.getInputMethodMode() == ListPopupWindow.INPUT_METHOD_NOT_NEEDED;
1195     }
1196 
1197     /**
1198      * <p>Displays the drop down on screen.</p>
1199      */
showDropDown()1200     public void showDropDown() {
1201         buildImeCompletions();
1202 
1203         if (mPopup.getAnchorView() == null) {
1204             if (mDropDownAnchorId != View.NO_ID) {
1205                 mPopup.setAnchorView(getRootView().findViewById(mDropDownAnchorId));
1206             } else {
1207                 mPopup.setAnchorView(this);
1208             }
1209         }
1210         if (!isPopupShowing()) {
1211             // Make sure the list does not obscure the IME when shown for the first time.
1212             mPopup.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NEEDED);
1213             mPopup.setListItemExpandMax(EXPAND_MAX);
1214         }
1215         mPopup.show();
1216         mPopup.getListView().setOverScrollMode(View.OVER_SCROLL_ALWAYS);
1217     }
1218 
1219     /**
1220      * Forces outside touches to be ignored. Normally if {@link #isDropDownAlwaysVisible()} is
1221      * false, we allow outside touch to dismiss the dropdown. If this is set to true, then we
1222      * ignore outside touch even when the drop down is not set to always visible.
1223      *
1224      * @hide used only by SearchDialog
1225      */
setForceIgnoreOutsideTouch(boolean forceIgnoreOutsideTouch)1226     public void setForceIgnoreOutsideTouch(boolean forceIgnoreOutsideTouch) {
1227         mPopup.setForceIgnoreOutsideTouch(forceIgnoreOutsideTouch);
1228     }
1229 
buildImeCompletions()1230     private void buildImeCompletions() {
1231         final ListAdapter adapter = mAdapter;
1232         if (adapter != null) {
1233             InputMethodManager imm = InputMethodManager.peekInstance();
1234             if (imm != null) {
1235                 final int count = Math.min(adapter.getCount(), 20);
1236                 CompletionInfo[] completions = new CompletionInfo[count];
1237                 int realCount = 0;
1238 
1239                 for (int i = 0; i < count; i++) {
1240                     if (adapter.isEnabled(i)) {
1241                         Object item = adapter.getItem(i);
1242                         long id = adapter.getItemId(i);
1243                         completions[realCount] = new CompletionInfo(id, realCount,
1244                                 convertSelectionToString(item));
1245                         realCount++;
1246                     }
1247                 }
1248 
1249                 if (realCount != count) {
1250                     CompletionInfo[] tmp = new CompletionInfo[realCount];
1251                     System.arraycopy(completions, 0, tmp, 0, realCount);
1252                     completions = tmp;
1253                 }
1254 
1255                 imm.displayCompletions(this, completions);
1256             }
1257         }
1258     }
1259 
1260     /**
1261      * Sets the validator used to perform text validation.
1262      *
1263      * @param validator The validator used to validate the text entered in this widget.
1264      *
1265      * @see #getValidator()
1266      * @see #performValidation()
1267      */
setValidator(Validator validator)1268     public void setValidator(Validator validator) {
1269         mValidator = validator;
1270     }
1271 
1272     /**
1273      * Returns the Validator set with {@link #setValidator},
1274      * or <code>null</code> if it was not set.
1275      *
1276      * @see #setValidator(android.widget.AutoCompleteTextView.Validator)
1277      * @see #performValidation()
1278      */
getValidator()1279     public Validator getValidator() {
1280         return mValidator;
1281     }
1282 
1283     /**
1284      * If a validator was set on this view and the current string is not valid,
1285      * ask the validator to fix it.
1286      *
1287      * @see #getValidator()
1288      * @see #setValidator(android.widget.AutoCompleteTextView.Validator)
1289      */
performValidation()1290     public void performValidation() {
1291         if (mValidator == null) return;
1292 
1293         CharSequence text = getText();
1294 
1295         if (!TextUtils.isEmpty(text) && !mValidator.isValid(text)) {
1296             setText(mValidator.fixText(text));
1297         }
1298     }
1299 
1300     /**
1301      * Returns the Filter obtained from {@link Filterable#getFilter},
1302      * or <code>null</code> if {@link #setAdapter} was not called with
1303      * a Filterable.
1304      */
getFilter()1305     protected Filter getFilter() {
1306         return mFilter;
1307     }
1308 
1309     private class DropDownItemClickListener implements AdapterView.OnItemClickListener {
onItemClick(AdapterView parent, View v, int position, long id)1310         public void onItemClick(AdapterView parent, View v, int position, long id) {
1311             performCompletion(v, position, id);
1312         }
1313     }
1314 
1315     /**
1316      * This interface is used to make sure that the text entered in this TextView complies to
1317      * a certain format.  Since there is no foolproof way to prevent the user from leaving
1318      * this View with an incorrect value in it, all we can do is try to fix it ourselves
1319      * when this happens.
1320      */
1321     public interface Validator {
1322         /**
1323          * Validates the specified text.
1324          *
1325          * @return true If the text currently in the text editor is valid.
1326          *
1327          * @see #fixText(CharSequence)
1328          */
isValid(CharSequence text)1329         boolean isValid(CharSequence text);
1330 
1331         /**
1332          * Corrects the specified text to make it valid.
1333          *
1334          * @param invalidText A string that doesn't pass validation: isValid(invalidText)
1335          *        returns false
1336          *
1337          * @return A string based on invalidText such as invoking isValid() on it returns true.
1338          *
1339          * @see #isValid(CharSequence)
1340          */
fixText(CharSequence invalidText)1341         CharSequence fixText(CharSequence invalidText);
1342     }
1343 
1344     /**
1345      * Listener to respond to the AutoCompleteTextView's completion list being dismissed.
1346      * @see AutoCompleteTextView#setOnDismissListener(OnDismissListener)
1347      */
1348     public interface OnDismissListener {
1349         /**
1350          * This method will be invoked whenever the AutoCompleteTextView's list
1351          * of completion options has been dismissed and is no longer available
1352          * for user interaction.
1353          */
onDismiss()1354         void onDismiss();
1355     }
1356 
1357     /**
1358      * Allows us a private hook into the on click event without preventing users from setting
1359      * their own click listener.
1360      */
1361     private class PassThroughClickListener implements OnClickListener {
1362 
1363         private View.OnClickListener mWrapped;
1364 
1365         /** {@inheritDoc} */
onClick(View v)1366         public void onClick(View v) {
1367             onClickImpl();
1368 
1369             if (mWrapped != null) mWrapped.onClick(v);
1370         }
1371     }
1372 
1373     /**
1374      * Static inner listener that keeps a WeakReference to the actual AutoCompleteTextView.
1375      * <p>
1376      * This way, if adapter has a longer life span than the View, we won't leak the View, instead
1377      * we will just leak a small Observer with 1 field.
1378      */
1379     private static class PopupDataSetObserver extends DataSetObserver {
1380         private final WeakReference<AutoCompleteTextView> mViewReference;
1381 
PopupDataSetObserver(AutoCompleteTextView view)1382         private PopupDataSetObserver(AutoCompleteTextView view) {
1383             mViewReference = new WeakReference<AutoCompleteTextView>(view);
1384         }
1385 
1386         @Override
onChanged()1387         public void onChanged() {
1388             final AutoCompleteTextView textView = mViewReference.get();
1389             if (textView != null && textView.mAdapter != null) {
1390                 // If the popup is not showing already, showing it will cause
1391                 // the list of data set observers attached to the adapter to
1392                 // change. We can't do it from here, because we are in the middle
1393                 // of iterating through the list of observers.
1394                 textView.post(updateRunnable);
1395             }
1396         }
1397 
1398         private final Runnable updateRunnable = new Runnable() {
1399             @Override
1400             public void run() {
1401                 final AutoCompleteTextView textView = mViewReference.get();
1402                 if (textView == null) {
1403                     return;
1404                 }
1405                 final ListAdapter adapter = textView.mAdapter;
1406                 if (adapter == null) {
1407                     return;
1408                 }
1409                 textView.updateDropDownForFilter(adapter.getCount());
1410             }
1411         };
1412     }
1413 }
1414