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