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