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