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