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