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