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