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