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