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.MotionEvent;
     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.InputMethodManager;
     38 import android.view.inputmethod.EditorInfo;
     39 
     40 import com.android.internal.R;
     41 
     42 
     43 /**
     44  * <p>An editable text view that shows completion suggestions automatically
     45  * while the user is typing. The list of suggestions is displayed in a drop
     46  * down menu from which the user can choose an item to replace the content
     47  * of the edit box with.</p>
     48  *
     49  * <p>The drop down can be dismissed at any time by pressing the back key or,
     50  * if no item is selected in the drop down, by pressing the enter/dpad center
     51  * key.</p>
     52  *
     53  * <p>The list of suggestions is obtained from a data adapter and appears
     54  * only after a given number of characters defined by
     55  * {@link #getThreshold() the threshold}.</p>
     56  *
     57  * <p>The following code snippet shows how to create a text view which suggests
     58  * various countries names while the user is typing:</p>
     59  *
     60  * <pre class="prettyprint">
     61  * public class CountriesActivity extends Activity {
     62  *     protected void onCreate(Bundle icicle) {
     63  *         super.onCreate(icicle);
     64  *         setContentView(R.layout.countries);
     65  *
     66  *         ArrayAdapter&lt;String&gt; adapter = new ArrayAdapter&lt;String&gt;(this,
     67  *                 android.R.layout.simple_dropdown_item_1line, COUNTRIES);
     68  *         AutoCompleteTextView textView = (AutoCompleteTextView)
     69  *                 findViewById(R.id.countries_list);
     70  *         textView.setAdapter(adapter);
     71  *     }
     72  *
     73  *     private static final String[] COUNTRIES = new String[] {
     74  *         "Belgium", "France", "Italy", "Germany", "Spain"
     75  *     };
     76  * }
     77  * </pre>
     78  *
     79  * @attr ref android.R.styleable#AutoCompleteTextView_completionHint
     80  * @attr ref android.R.styleable#AutoCompleteTextView_completionThreshold
     81  * @attr ref android.R.styleable#AutoCompleteTextView_completionHintView
     82  * @attr ref android.R.styleable#AutoCompleteTextView_dropDownSelector
     83  * @attr ref android.R.styleable#AutoCompleteTextView_dropDownAnchor
     84  * @attr ref android.R.styleable#AutoCompleteTextView_dropDownWidth
     85  * @attr ref android.R.styleable#AutoCompleteTextView_dropDownHeight
     86  * @attr ref android.R.styleable#AutoCompleteTextView_dropDownVerticalOffset
     87  * @attr ref android.R.styleable#AutoCompleteTextView_dropDownHorizontalOffset
     88  */
     89 public class AutoCompleteTextView extends EditText implements Filter.FilterListener {
     90     static final boolean DEBUG = false;
     91     static final String TAG = "AutoCompleteTextView";
     92 
     93     private static final int HINT_VIEW_ID = 0x17;
     94 
     95     /**
     96      * This value controls the length of time that the user
     97      * must leave a pointer down without scrolling to expand
     98      * the autocomplete dropdown list to cover the IME.
     99      */
    100     private static final int EXPAND_LIST_TIMEOUT = 250;
    101 
    102     private CharSequence mHintText;
    103     private int mHintResource;
    104 
    105     private ListAdapter mAdapter;
    106     private Filter mFilter;
    107     private int mThreshold;
    108 
    109     private PopupWindow mPopup;
    110     private DropDownListView mDropDownList;
    111     private int mDropDownVerticalOffset;
    112     private int mDropDownHorizontalOffset;
    113     private int mDropDownAnchorId;
    114     private View mDropDownAnchorView;  // view is retrieved lazily from id once needed
    115     private int mDropDownWidth;
    116     private int mDropDownHeight;
    117     private final Rect mTempRect = new Rect();
    118 
    119     private Drawable mDropDownListHighlight;
    120 
    121     private AdapterView.OnItemClickListener mItemClickListener;
    122     private AdapterView.OnItemSelectedListener mItemSelectedListener;
    123 
    124     private final DropDownItemClickListener mDropDownItemClickListener =
    125             new DropDownItemClickListener();
    126 
    127     private boolean mDropDownAlwaysVisible = false;
    128 
    129     private boolean mDropDownDismissedOnCompletion = true;
    130 
    131     private boolean mForceIgnoreOutsideTouch = false;
    132 
    133     private int mLastKeyCode = KeyEvent.KEYCODE_UNKNOWN;
    134     private boolean mOpenBefore;
    135 
    136     private Validator mValidator = null;
    137 
    138     private boolean mBlockCompletion;
    139 
    140     private ListSelectorHider mHideSelector;
    141     private Runnable mShowDropDownRunnable;
    142     private Runnable mResizePopupRunnable = new ResizePopupRunnable();
    143 
    144     private PassThroughClickListener mPassThroughClickListener;
    145     private PopupDataSetObserver mObserver;
    146 
    147     public AutoCompleteTextView(Context context) {
    148         this(context, null);
    149     }
    150 
    151     public AutoCompleteTextView(Context context, AttributeSet attrs) {
    152         this(context, attrs, com.android.internal.R.attr.autoCompleteTextViewStyle);
    153     }
    154 
    155     public AutoCompleteTextView(Context context, AttributeSet attrs, int defStyle) {
    156         super(context, attrs, defStyle);
    157 
    158         mPopup = new PopupWindow(context, attrs,
    159                 com.android.internal.R.attr.autoCompleteTextViewStyle);
    160         mPopup.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
    161 
    162         TypedArray a =
    163             context.obtainStyledAttributes(
    164                 attrs, com.android.internal.R.styleable.AutoCompleteTextView, defStyle, 0);
    165 
    166         mThreshold = a.getInt(
    167                 R.styleable.AutoCompleteTextView_completionThreshold, 2);
    168 
    169         mHintText = a.getText(R.styleable.AutoCompleteTextView_completionHint);
    170 
    171         mDropDownListHighlight = a.getDrawable(
    172                 R.styleable.AutoCompleteTextView_dropDownSelector);
    173         mDropDownVerticalOffset = (int)
    174                 a.getDimension(R.styleable.AutoCompleteTextView_dropDownVerticalOffset, 0.0f);
    175         mDropDownHorizontalOffset = (int)
    176                 a.getDimension(R.styleable.AutoCompleteTextView_dropDownHorizontalOffset, 0.0f);
    177 
    178         // Get the anchor's id now, but the view won't be ready, so wait to actually get the
    179         // view and store it in mDropDownAnchorView lazily in getDropDownAnchorView later.
    180         // Defaults to NO_ID, in which case the getDropDownAnchorView method will simply return
    181         // this TextView, as a default anchoring point.
    182         mDropDownAnchorId = a.getResourceId(R.styleable.AutoCompleteTextView_dropDownAnchor,
    183                 View.NO_ID);
    184 
    185         // For dropdown width, the developer can specify a specific width, or MATCH_PARENT
    186         // (for full screen width) or WRAP_CONTENT (to match the width of the anchored view).
    187         mDropDownWidth = a.getLayoutDimension(R.styleable.AutoCompleteTextView_dropDownWidth,
    188                 ViewGroup.LayoutParams.WRAP_CONTENT);
    189         mDropDownHeight = a.getLayoutDimension(R.styleable.AutoCompleteTextView_dropDownHeight,
    190                 ViewGroup.LayoutParams.WRAP_CONTENT);
    191 
    192         mHintResource = a.getResourceId(R.styleable.AutoCompleteTextView_completionHintView,
    193                 R.layout.simple_dropdown_hint);
    194 
    195         // Always turn on the auto complete input type flag, since it
    196         // makes no sense to use this widget without it.
    197         int inputType = getInputType();
    198         if ((inputType&EditorInfo.TYPE_MASK_CLASS)
    199                 == EditorInfo.TYPE_CLASS_TEXT) {
    200             inputType |= EditorInfo.TYPE_TEXT_FLAG_AUTO_COMPLETE;
    201             setRawInputType(inputType);
    202         }
    203 
    204         a.recycle();
    205 
    206         setFocusable(true);
    207 
    208         addTextChangedListener(new MyWatcher());
    209 
    210         mPassThroughClickListener = new PassThroughClickListener();
    211         super.setOnClickListener(mPassThroughClickListener);
    212     }
    213 
    214     @Override
    215     public void setOnClickListener(OnClickListener listener) {
    216         mPassThroughClickListener.mWrapped = listener;
    217     }
    218 
    219     /**
    220      * Private hook into the on click event, dispatched from {@link PassThroughClickListener}
    221      */
    222     private void onClickImpl() {
    223         // If the dropdown is showing, bring the keyboard to the front
    224         // when the user touches the text field.
    225         if (mPopup.isShowing()) {
    226             ensureImeVisible(true);
    227         }
    228     }
    229 
    230     /**
    231      * <p>Sets the optional hint text that is displayed at the bottom of the
    232      * the matching list.  This can be used as a cue to the user on how to
    233      * best use the list, or to provide extra information.</p>
    234      *
    235      * @param hint the text to be displayed to the user
    236      *
    237      * @attr ref android.R.styleable#AutoCompleteTextView_completionHint
    238      */
    239     public void setCompletionHint(CharSequence hint) {
    240         mHintText = hint;
    241     }
    242 
    243     /**
    244      * <p>Returns the current width for the auto-complete drop down list. This can
    245      * be a fixed width, or {@link ViewGroup.LayoutParams#MATCH_PARENT} to fill the screen, or
    246      * {@link ViewGroup.LayoutParams#WRAP_CONTENT} to fit the width of its anchor view.</p>
    247      *
    248      * @return the width for the drop down list
    249      *
    250      * @attr ref android.R.styleable#AutoCompleteTextView_dropDownWidth
    251      */
    252     public int getDropDownWidth() {
    253         return mDropDownWidth;
    254     }
    255 
    256     /**
    257      * <p>Sets the current width for the auto-complete drop down list. This can
    258      * be a fixed width, or {@link ViewGroup.LayoutParams#MATCH_PARENT} to fill the screen, or
    259      * {@link ViewGroup.LayoutParams#WRAP_CONTENT} to fit the width of its anchor view.</p>
    260      *
    261      * @param width the width to use
    262      *
    263      * @attr ref android.R.styleable#AutoCompleteTextView_dropDownWidth
    264      */
    265     public void setDropDownWidth(int width) {
    266         mDropDownWidth = width;
    267     }
    268 
    269     /**
    270      * <p>Returns the current height for the auto-complete drop down list. This can
    271      * be a fixed height, or {@link ViewGroup.LayoutParams#MATCH_PARENT} to fill
    272      * the screen, or {@link ViewGroup.LayoutParams#WRAP_CONTENT} to fit the height
    273      * of the drop down's content.</p>
    274      *
    275      * @return the height for the drop down list
    276      *
    277      * @attr ref android.R.styleable#AutoCompleteTextView_dropDownHeight
    278      */
    279     public int getDropDownHeight() {
    280         return mDropDownHeight;
    281     }
    282 
    283     /**
    284      * <p>Sets the current height for the auto-complete drop down list. This can
    285      * be a fixed height, or {@link ViewGroup.LayoutParams#MATCH_PARENT} to fill
    286      * the screen, or {@link ViewGroup.LayoutParams#WRAP_CONTENT} to fit the height
    287      * of the drop down's content.</p>
    288      *
    289      * @param height the height to use
    290      *
    291      * @attr ref android.R.styleable#AutoCompleteTextView_dropDownHeight
    292      */
    293     public void setDropDownHeight(int height) {
    294         mDropDownHeight = height;
    295     }
    296 
    297     /**
    298      * <p>Returns the id for the view that the auto-complete drop down list is anchored to.</p>
    299      *
    300      * @return the view's id, or {@link View#NO_ID} if none specified
    301      *
    302      * @attr ref android.R.styleable#AutoCompleteTextView_dropDownAnchor
    303      */
    304     public int getDropDownAnchor() {
    305         return mDropDownAnchorId;
    306     }
    307 
    308     /**
    309      * <p>Sets the view to which the auto-complete drop down list should anchor. The view
    310      * corresponding to this id will not be loaded until the next time it is needed to avoid
    311      * loading a view which is not yet instantiated.</p>
    312      *
    313      * @param id the id to anchor the drop down list view to
    314      *
    315      * @attr ref android.R.styleable#AutoCompleteTextView_dropDownAnchor
    316      */
    317     public void setDropDownAnchor(int id) {
    318         mDropDownAnchorId = id;
    319         mDropDownAnchorView = null;
    320     }
    321 
    322     /**
    323      * <p>Gets the background of the auto-complete drop-down list.</p>
    324      *
    325      * @return the background drawable
    326      *
    327      * @attr ref android.R.styleable#PopupWindow_popupBackground
    328      */
    329     public Drawable getDropDownBackground() {
    330         return mPopup.getBackground();
    331     }
    332 
    333     /**
    334      * <p>Sets the background of the auto-complete drop-down list.</p>
    335      *
    336      * @param d the drawable to set as the background
    337      *
    338      * @attr ref android.R.styleable#PopupWindow_popupBackground
    339      */
    340     public void setDropDownBackgroundDrawable(Drawable d) {
    341         mPopup.setBackgroundDrawable(d);
    342     }
    343 
    344     /**
    345      * <p>Sets the background of the auto-complete drop-down list.</p>
    346      *
    347      * @param id the id of the drawable to set as the background
    348      *
    349      * @attr ref android.R.styleable#PopupWindow_popupBackground
    350      */
    351     public void setDropDownBackgroundResource(int id) {
    352         mPopup.setBackgroundDrawable(getResources().getDrawable(id));
    353     }
    354 
    355     /**
    356      * <p>Sets the vertical offset used for the auto-complete drop-down list.</p>
    357      *
    358      * @param offset the vertical offset
    359      */
    360     public void setDropDownVerticalOffset(int offset) {
    361         mDropDownVerticalOffset = offset;
    362     }
    363 
    364     /**
    365      * <p>Gets the vertical offset used for the auto-complete drop-down list.</p>
    366      *
    367      * @return the vertical offset
    368      */
    369     public int getDropDownVerticalOffset() {
    370         return mDropDownVerticalOffset;
    371     }
    372 
    373     /**
    374      * <p>Sets the horizontal offset used for the auto-complete drop-down list.</p>
    375      *
    376      * @param offset the horizontal offset
    377      */
    378     public void setDropDownHorizontalOffset(int offset) {
    379         mDropDownHorizontalOffset = offset;
    380     }
    381 
    382     /**
    383      * <p>Gets the horizontal offset used for the auto-complete drop-down list.</p>
    384      *
    385      * @return the horizontal offset
    386      */
    387     public int getDropDownHorizontalOffset() {
    388         return mDropDownHorizontalOffset;
    389     }
    390 
    391      /**
    392      * <p>Sets the animation style of the auto-complete drop-down list.</p>
    393      *
    394      * <p>If the drop-down is showing, calling this method will take effect only
    395      * the next time the drop-down is shown.</p>
    396      *
    397      * @param animationStyle animation style to use when the drop-down appears
    398      *      and disappears.  Set to -1 for the default animation, 0 for no
    399      *      animation, or a resource identifier for an explicit animation.
    400      *
    401      * @hide Pending API council approval
    402      */
    403     public void setDropDownAnimationStyle(int animationStyle) {
    404         mPopup.setAnimationStyle(animationStyle);
    405     }
    406 
    407     /**
    408      * <p>Returns the animation style that is used when the drop-down list appears and disappears
    409      * </p>
    410      *
    411      * @return the animation style that is used when the drop-down list appears and disappears
    412      *
    413      * @hide Pending API council approval
    414      */
    415     public int getDropDownAnimationStyle() {
    416         return mPopup.getAnimationStyle();
    417     }
    418 
    419     /**
    420      * @return Whether the drop-down is visible as long as there is {@link #enoughToFilter()}
    421      *
    422      * @hide Pending API council approval
    423      */
    424     public boolean isDropDownAlwaysVisible() {
    425         return mDropDownAlwaysVisible;
    426     }
    427 
    428     /**
    429      * Sets whether the drop-down should remain visible as long as there is there is
    430      * {@link #enoughToFilter()}.  This is useful if an unknown number of results are expected
    431      * to show up in the adapter sometime in the future.
    432      *
    433      * The drop-down will occupy the entire screen below {@link #getDropDownAnchor} regardless
    434      * of the size or content of the list.  {@link #getDropDownBackground()} will fill any space
    435      * that is not used by the list.
    436      *
    437      * @param dropDownAlwaysVisible Whether to keep the drop-down visible.
    438      *
    439      * @hide Pending API council approval
    440      */
    441     public void setDropDownAlwaysVisible(boolean dropDownAlwaysVisible) {
    442         mDropDownAlwaysVisible = dropDownAlwaysVisible;
    443     }
    444 
    445     /**
    446      * Checks whether the drop-down is dismissed when a suggestion is clicked.
    447      *
    448      * @hide Pending API council approval
    449      */
    450     public boolean isDropDownDismissedOnCompletion() {
    451         return mDropDownDismissedOnCompletion;
    452     }
    453 
    454     /**
    455      * Sets whether the drop-down is dismissed when a suggestion is clicked. This is
    456      * true by default.
    457      *
    458      * @param dropDownDismissedOnCompletion Whether to dismiss the drop-down.
    459      *
    460      * @hide Pending API council approval
    461      */
    462     public void setDropDownDismissedOnCompletion(boolean dropDownDismissedOnCompletion) {
    463         mDropDownDismissedOnCompletion = dropDownDismissedOnCompletion;
    464     }
    465 
    466     /**
    467      * <p>Returns the number of characters the user must type before the drop
    468      * down list is shown.</p>
    469      *
    470      * @return the minimum number of characters to type to show the drop down
    471      *
    472      * @see #setThreshold(int)
    473      */
    474     public int getThreshold() {
    475         return mThreshold;
    476     }
    477 
    478     /**
    479      * <p>Specifies the minimum number of characters the user has to type in the
    480      * edit box before the drop down list is shown.</p>
    481      *
    482      * <p>When <code>threshold</code> is less than or equals 0, a threshold of
    483      * 1 is applied.</p>
    484      *
    485      * @param threshold the number of characters to type before the drop down
    486      *                  is shown
    487      *
    488      * @see #getThreshold()
    489      *
    490      * @attr ref android.R.styleable#AutoCompleteTextView_completionThreshold
    491      */
    492     public void setThreshold(int threshold) {
    493         if (threshold <= 0) {
    494             threshold = 1;
    495         }
    496 
    497         mThreshold = threshold;
    498     }
    499 
    500     /**
    501      * <p>Sets the listener that will be notified when the user clicks an item
    502      * in the drop down list.</p>
    503      *
    504      * @param l the item click listener
    505      */
    506     public void setOnItemClickListener(AdapterView.OnItemClickListener l) {
    507         mItemClickListener = l;
    508     }
    509 
    510     /**
    511      * <p>Sets the listener that will be notified when the user selects an item
    512      * in the drop down list.</p>
    513      *
    514      * @param l the item selected listener
    515      */
    516     public void setOnItemSelectedListener(AdapterView.OnItemSelectedListener l) {
    517         mItemSelectedListener = l;
    518     }
    519 
    520     /**
    521      * <p>Returns the listener that is notified whenever the user clicks an item
    522      * in the drop down list.</p>
    523      *
    524      * @return the item click listener
    525      *
    526      * @deprecated Use {@link #getOnItemClickListener()} intead
    527      */
    528     @Deprecated
    529     public AdapterView.OnItemClickListener getItemClickListener() {
    530         return mItemClickListener;
    531     }
    532 
    533     /**
    534      * <p>Returns the listener that is notified whenever the user selects an
    535      * item in the drop down list.</p>
    536      *
    537      * @return the item selected listener
    538      *
    539      * @deprecated Use {@link #getOnItemSelectedListener()} intead
    540      */
    541     @Deprecated
    542     public AdapterView.OnItemSelectedListener getItemSelectedListener() {
    543         return mItemSelectedListener;
    544     }
    545 
    546     /**
    547      * <p>Returns the listener that is notified whenever the user clicks an item
    548      * in the drop down list.</p>
    549      *
    550      * @return the item click listener
    551      */
    552     public AdapterView.OnItemClickListener getOnItemClickListener() {
    553         return mItemClickListener;
    554     }
    555 
    556     /**
    557      * <p>Returns the listener that is notified whenever the user selects an
    558      * item in the drop down list.</p>
    559      *
    560      * @return the item selected listener
    561      */
    562     public AdapterView.OnItemSelectedListener getOnItemSelectedListener() {
    563         return mItemSelectedListener;
    564     }
    565 
    566     /**
    567      * <p>Returns a filterable list adapter used for auto completion.</p>
    568      *
    569      * @return a data adapter used for auto completion
    570      */
    571     public ListAdapter getAdapter() {
    572         return mAdapter;
    573     }
    574 
    575     /**
    576      * <p>Changes the list of data used for auto completion. The provided list
    577      * must be a filterable list adapter.</p>
    578      *
    579      * <p>The caller is still responsible for managing any resources used by the adapter.
    580      * Notably, when the AutoCompleteTextView is closed or released, the adapter is not notified.
    581      * A common case is the use of {@link android.widget.CursorAdapter}, which
    582      * contains a {@link android.database.Cursor} that must be closed.  This can be done
    583      * automatically (see
    584      * {@link android.app.Activity#startManagingCursor(android.database.Cursor)
    585      * startManagingCursor()}),
    586      * or by manually closing the cursor when the AutoCompleteTextView is dismissed.</p>
    587      *
    588      * @param adapter the adapter holding the auto completion data
    589      *
    590      * @see #getAdapter()
    591      * @see android.widget.Filterable
    592      * @see android.widget.ListAdapter
    593      */
    594     public <T extends ListAdapter & Filterable> void setAdapter(T adapter) {
    595         if (mObserver == null) {
    596             mObserver = new PopupDataSetObserver();
    597         } else if (mAdapter != null) {
    598             mAdapter.unregisterDataSetObserver(mObserver);
    599         }
    600         mAdapter = adapter;
    601         if (mAdapter != null) {
    602             //noinspection unchecked
    603             mFilter = ((Filterable) mAdapter).getFilter();
    604             adapter.registerDataSetObserver(mObserver);
    605         } else {
    606             mFilter = null;
    607         }
    608 
    609         if (mDropDownList != null) {
    610             mDropDownList.setAdapter(mAdapter);
    611         }
    612     }
    613 
    614     @Override
    615     public boolean onKeyPreIme(int keyCode, KeyEvent event) {
    616         if (keyCode == KeyEvent.KEYCODE_BACK && isPopupShowing()
    617                 && !mDropDownAlwaysVisible) {
    618             // special case for the back key, we do not even try to send it
    619             // to the drop down list but instead, consume it immediately
    620             if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
    621                 getKeyDispatcherState().startTracking(event, this);
    622                 return true;
    623             } else if (event.getAction() == KeyEvent.ACTION_UP) {
    624                 getKeyDispatcherState().handleUpEvent(event);
    625                 if (event.isTracking() && !event.isCanceled()) {
    626                     dismissDropDown();
    627                     return true;
    628                 }
    629             }
    630         }
    631         return super.onKeyPreIme(keyCode, event);
    632     }
    633 
    634     @Override
    635     public boolean onKeyUp(int keyCode, KeyEvent event) {
    636         if (isPopupShowing() && mDropDownList.getSelectedItemPosition() >= 0) {
    637             boolean consumed = mDropDownList.onKeyUp(keyCode, event);
    638             if (consumed) {
    639                 switch (keyCode) {
    640                     // if the list accepts the key events and the key event
    641                     // was a click, the text view gets the selected item
    642                     // from the drop down as its content
    643                     case KeyEvent.KEYCODE_ENTER:
    644                     case KeyEvent.KEYCODE_DPAD_CENTER:
    645                         performCompletion();
    646                         return true;
    647                 }
    648             }
    649         }
    650         return super.onKeyUp(keyCode, event);
    651     }
    652 
    653     @Override
    654     public boolean onKeyDown(int keyCode, KeyEvent event) {
    655         // when the drop down is shown, we drive it directly
    656         if (isPopupShowing()) {
    657             // the key events are forwarded to the list in the drop down view
    658             // note that ListView handles space but we don't want that to happen
    659             // also if selection is not currently in the drop down, then don't
    660             // let center or enter presses go there since that would cause it
    661             // to select one of its items
    662             if (keyCode != KeyEvent.KEYCODE_SPACE
    663                     && (mDropDownList.getSelectedItemPosition() >= 0
    664                             || (keyCode != KeyEvent.KEYCODE_ENTER
    665                                     && keyCode != KeyEvent.KEYCODE_DPAD_CENTER))) {
    666                 int curIndex = mDropDownList.getSelectedItemPosition();
    667                 boolean consumed;
    668 
    669                 final boolean below = !mPopup.isAboveAnchor();
    670 
    671                 final ListAdapter adapter = mAdapter;
    672 
    673                 boolean allEnabled;
    674                 int firstItem = Integer.MAX_VALUE;
    675                 int lastItem = Integer.MIN_VALUE;
    676 
    677                 if (adapter != null) {
    678                     allEnabled = adapter.areAllItemsEnabled();
    679                     firstItem = allEnabled ? 0 :
    680                             mDropDownList.lookForSelectablePosition(0, true);
    681                     lastItem = allEnabled ? adapter.getCount() - 1 :
    682                             mDropDownList.lookForSelectablePosition(adapter.getCount() - 1, false);
    683                 }
    684 
    685                 if ((below && keyCode == KeyEvent.KEYCODE_DPAD_UP && curIndex <= firstItem) ||
    686                         (!below && keyCode == KeyEvent.KEYCODE_DPAD_DOWN && curIndex >= lastItem)) {
    687                     // When the selection is at the top, we block the key
    688                     // event to prevent focus from moving.
    689                     clearListSelection();
    690                     mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
    691                     showDropDown();
    692                     return true;
    693                 } else {
    694                     // WARNING: Please read the comment where mListSelectionHidden
    695                     //          is declared
    696                     mDropDownList.mListSelectionHidden = false;
    697                 }
    698 
    699                 consumed = mDropDownList.onKeyDown(keyCode, event);
    700                 if (DEBUG) Log.v(TAG, "Key down: code=" + keyCode + " list consumed=" + consumed);
    701 
    702                 if (consumed) {
    703                     // If it handled the key event, then the user is
    704                     // navigating in the list, so we should put it in front.
    705                     mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
    706                     // Here's a little trick we need to do to make sure that
    707                     // the list view is actually showing its focus indicator,
    708                     // by ensuring it has focus and getting its window out
    709                     // of touch mode.
    710                     mDropDownList.requestFocusFromTouch();
    711                     showDropDown();
    712 
    713                     switch (keyCode) {
    714                         // avoid passing the focus from the text view to the
    715                         // next component
    716                         case KeyEvent.KEYCODE_ENTER:
    717                         case KeyEvent.KEYCODE_DPAD_CENTER:
    718                         case KeyEvent.KEYCODE_DPAD_DOWN:
    719                         case KeyEvent.KEYCODE_DPAD_UP:
    720                             return true;
    721                     }
    722                 } else {
    723                     if (below && keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
    724                         // when the selection is at the bottom, we block the
    725                         // event to avoid going to the next focusable widget
    726                         if (curIndex == lastItem) {
    727                             return true;
    728                         }
    729                     } else if (!below && keyCode == KeyEvent.KEYCODE_DPAD_UP &&
    730                             curIndex == firstItem) {
    731                         return true;
    732                     }
    733                 }
    734             }
    735         } else {
    736             switch(keyCode) {
    737             case KeyEvent.KEYCODE_DPAD_DOWN:
    738                 performValidation();
    739             }
    740         }
    741 
    742         mLastKeyCode = keyCode;
    743         boolean handled = super.onKeyDown(keyCode, event);
    744         mLastKeyCode = KeyEvent.KEYCODE_UNKNOWN;
    745 
    746         if (handled && isPopupShowing() && mDropDownList != null) {
    747             clearListSelection();
    748         }
    749 
    750         return handled;
    751     }
    752 
    753     /**
    754      * Returns <code>true</code> if the amount of text in the field meets
    755      * or exceeds the {@link #getThreshold} requirement.  You can override
    756      * this to impose a different standard for when filtering will be
    757      * triggered.
    758      */
    759     public boolean enoughToFilter() {
    760         if (DEBUG) Log.v(TAG, "Enough to filter: len=" + getText().length()
    761                 + " threshold=" + mThreshold);
    762         return getText().length() >= mThreshold;
    763     }
    764 
    765     /**
    766      * This is used to watch for edits to the text view.  Note that we call
    767      * to methods on the auto complete text view class so that we can access
    768      * private vars without going through thunks.
    769      */
    770     private class MyWatcher implements TextWatcher {
    771         public void afterTextChanged(Editable s) {
    772             doAfterTextChanged();
    773         }
    774         public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    775             doBeforeTextChanged();
    776         }
    777         public void onTextChanged(CharSequence s, int start, int before, int count) {
    778         }
    779     }
    780 
    781     void doBeforeTextChanged() {
    782         if (mBlockCompletion) return;
    783 
    784         // when text is changed, inserted or deleted, we attempt to show
    785         // the drop down
    786         mOpenBefore = isPopupShowing();
    787         if (DEBUG) Log.v(TAG, "before text changed: open=" + mOpenBefore);
    788     }
    789 
    790     void doAfterTextChanged() {
    791         if (mBlockCompletion) return;
    792 
    793         // if the list was open before the keystroke, but closed afterwards,
    794         // then something in the keystroke processing (an input filter perhaps)
    795         // called performCompletion() and we shouldn't do any more processing.
    796         if (DEBUG) Log.v(TAG, "after text changed: openBefore=" + mOpenBefore
    797                 + " open=" + isPopupShowing());
    798         if (mOpenBefore && !isPopupShowing()) {
    799             return;
    800         }
    801 
    802         // the drop down is shown only when a minimum number of characters
    803         // was typed in the text view
    804         if (enoughToFilter()) {
    805             if (mFilter != null) {
    806                 performFiltering(getText(), mLastKeyCode);
    807             }
    808         } else {
    809             // drop down is automatically dismissed when enough characters
    810             // are deleted from the text view
    811             if (!mDropDownAlwaysVisible) dismissDropDown();
    812             if (mFilter != null) {
    813                 mFilter.filter(null);
    814             }
    815         }
    816     }
    817 
    818     /**
    819      * <p>Indicates whether the popup menu is showing.</p>
    820      *
    821      * @return true if the popup menu is showing, false otherwise
    822      */
    823     public boolean isPopupShowing() {
    824         return mPopup.isShowing();
    825     }
    826 
    827     /**
    828      * <p>Converts the selected item from the drop down list into a sequence
    829      * of character that can be used in the edit box.</p>
    830      *
    831      * @param selectedItem the item selected by the user for completion
    832      *
    833      * @return a sequence of characters representing the selected suggestion
    834      */
    835     protected CharSequence convertSelectionToString(Object selectedItem) {
    836         return mFilter.convertResultToString(selectedItem);
    837     }
    838 
    839     /**
    840      * <p>Clear the list selection.  This may only be temporary, as user input will often bring
    841      * it back.
    842      */
    843     public void clearListSelection() {
    844         final DropDownListView list = mDropDownList;
    845         if (list != null) {
    846             // WARNING: Please read the comment where mListSelectionHidden is declared
    847             list.mListSelectionHidden = true;
    848             list.hideSelector();
    849             list.requestLayout();
    850         }
    851     }
    852 
    853     /**
    854      * Set the position of the dropdown view selection.
    855      *
    856      * @param position The position to move the selector to.
    857      */
    858     public void setListSelection(int position) {
    859         if (mPopup.isShowing() && (mDropDownList != null)) {
    860             mDropDownList.mListSelectionHidden = false;
    861             mDropDownList.setSelection(position);
    862             // ListView.setSelection() will call requestLayout()
    863         }
    864     }
    865 
    866     /**
    867      * Get the position of the dropdown view selection, if there is one.  Returns
    868      * {@link ListView#INVALID_POSITION ListView.INVALID_POSITION} if there is no dropdown or if
    869      * there is no selection.
    870      *
    871      * @return the position of the current selection, if there is one, or
    872      * {@link ListView#INVALID_POSITION ListView.INVALID_POSITION} if not.
    873      *
    874      * @see ListView#getSelectedItemPosition()
    875      */
    876     public int getListSelection() {
    877         if (mPopup.isShowing() && (mDropDownList != null)) {
    878             return mDropDownList.getSelectedItemPosition();
    879         }
    880         return ListView.INVALID_POSITION;
    881     }
    882 
    883     /**
    884      * <p>Starts filtering the content of the drop down list. The filtering
    885      * pattern is the content of the edit box. Subclasses should override this
    886      * method to filter with a different pattern, for instance a substring of
    887      * <code>text</code>.</p>
    888      *
    889      * @param text the filtering pattern
    890      * @param keyCode the last character inserted in the edit box; beware that
    891      * this will be null when text is being added through a soft input method.
    892      */
    893     @SuppressWarnings({ "UnusedDeclaration" })
    894     protected void performFiltering(CharSequence text, int keyCode) {
    895         mFilter.filter(text, this);
    896     }
    897 
    898     /**
    899      * <p>Performs the text completion by converting the selected item from
    900      * the drop down list into a string, replacing the text box's content with
    901      * this string and finally dismissing the drop down menu.</p>
    902      */
    903     public void performCompletion() {
    904         performCompletion(null, -1, -1);
    905     }
    906 
    907     @Override
    908     public void onCommitCompletion(CompletionInfo completion) {
    909         if (isPopupShowing()) {
    910             mBlockCompletion = true;
    911             replaceText(completion.getText());
    912             mBlockCompletion = false;
    913 
    914             if (mItemClickListener != null) {
    915                 final DropDownListView list = mDropDownList;
    916                 // Note that we don't have a View here, so we will need to
    917                 // supply null.  Hopefully no existing apps crash...
    918                 mItemClickListener.onItemClick(list, null, completion.getPosition(),
    919                         completion.getId());
    920             }
    921         }
    922     }
    923 
    924     private void performCompletion(View selectedView, int position, long id) {
    925         if (isPopupShowing()) {
    926             Object selectedItem;
    927             if (position < 0) {
    928                 selectedItem = mDropDownList.getSelectedItem();
    929             } else {
    930                 selectedItem = mAdapter.getItem(position);
    931             }
    932             if (selectedItem == null) {
    933                 Log.w(TAG, "performCompletion: no selected item");
    934                 return;
    935             }
    936 
    937             mBlockCompletion = true;
    938             replaceText(convertSelectionToString(selectedItem));
    939             mBlockCompletion = false;
    940 
    941             if (mItemClickListener != null) {
    942                 final DropDownListView list = mDropDownList;
    943 
    944                 if (selectedView == null || position < 0) {
    945                     selectedView = list.getSelectedView();
    946                     position = list.getSelectedItemPosition();
    947                     id = list.getSelectedItemId();
    948                 }
    949                 mItemClickListener.onItemClick(list, selectedView, position, id);
    950             }
    951         }
    952 
    953         if (mDropDownDismissedOnCompletion && !mDropDownAlwaysVisible) {
    954             dismissDropDown();
    955         }
    956     }
    957 
    958     /**
    959      * Identifies whether the view is currently performing a text completion, so subclasses
    960      * can decide whether to respond to text changed events.
    961      */
    962     public boolean isPerformingCompletion() {
    963         return mBlockCompletion;
    964     }
    965 
    966     /**
    967      * Like {@link #setText(CharSequence)}, except that it can disable filtering.
    968      *
    969      * @param filter If <code>false</code>, no filtering will be performed
    970      *        as a result of this call.
    971      *
    972      * @hide Pending API council approval.
    973      */
    974     public void setText(CharSequence text, boolean filter) {
    975         if (filter) {
    976             setText(text);
    977         } else {
    978             mBlockCompletion = true;
    979             setText(text);
    980             mBlockCompletion = false;
    981         }
    982     }
    983 
    984     /**
    985      * <p>Performs the text completion by replacing the current text by the
    986      * selected item. Subclasses should override this method to avoid replacing
    987      * the whole content of the edit box.</p>
    988      *
    989      * @param text the selected suggestion in the drop down list
    990      */
    991     protected void replaceText(CharSequence text) {
    992         clearComposingText();
    993 
    994         setText(text);
    995         // make sure we keep the caret at the end of the text view
    996         Editable spannable = getText();
    997         Selection.setSelection(spannable, spannable.length());
    998     }
    999 
   1000     /** {@inheritDoc} */
   1001     public void onFilterComplete(int count) {
   1002         updateDropDownForFilter(count);
   1003 
   1004     }
   1005 
   1006     private void updateDropDownForFilter(int count) {
   1007         // Not attached to window, don't update drop-down
   1008         if (getWindowVisibility() == View.GONE) return;
   1009 
   1010         /*
   1011          * This checks enoughToFilter() again because filtering requests
   1012          * are asynchronous, so the result may come back after enough text
   1013          * has since been deleted to make it no longer appropriate
   1014          * to filter.
   1015          */
   1016 
   1017         if ((count > 0 || mDropDownAlwaysVisible) && enoughToFilter()) {
   1018             if (hasFocus() && hasWindowFocus()) {
   1019                 showDropDown();
   1020             }
   1021         } else if (!mDropDownAlwaysVisible) {
   1022             dismissDropDown();
   1023         }
   1024     }
   1025 
   1026     @Override
   1027     public void onWindowFocusChanged(boolean hasWindowFocus) {
   1028         super.onWindowFocusChanged(hasWindowFocus);
   1029         if (!hasWindowFocus && !mDropDownAlwaysVisible) {
   1030             dismissDropDown();
   1031         }
   1032     }
   1033 
   1034     @Override
   1035     protected void onDisplayHint(int hint) {
   1036         super.onDisplayHint(hint);
   1037         switch (hint) {
   1038             case INVISIBLE:
   1039                 if (!mDropDownAlwaysVisible) {
   1040                     dismissDropDown();
   1041                 }
   1042                 break;
   1043         }
   1044     }
   1045 
   1046     @Override
   1047     protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
   1048         super.onFocusChanged(focused, direction, previouslyFocusedRect);
   1049         // Perform validation if the view is losing focus.
   1050         if (!focused) {
   1051             performValidation();
   1052         }
   1053         if (!focused && !mDropDownAlwaysVisible) {
   1054             dismissDropDown();
   1055         }
   1056     }
   1057 
   1058     @Override
   1059     protected void onAttachedToWindow() {
   1060         super.onAttachedToWindow();
   1061     }
   1062 
   1063     @Override
   1064     protected void onDetachedFromWindow() {
   1065         dismissDropDown();
   1066         super.onDetachedFromWindow();
   1067     }
   1068 
   1069     /**
   1070      * <p>Closes the drop down if present on screen.</p>
   1071      */
   1072     public void dismissDropDown() {
   1073         InputMethodManager imm = InputMethodManager.peekInstance();
   1074         if (imm != null) {
   1075             imm.displayCompletions(this, null);
   1076         }
   1077         mPopup.dismiss();
   1078         mPopup.setContentView(null);
   1079         mDropDownList = null;
   1080     }
   1081 
   1082     @Override
   1083     protected boolean setFrame(final int l, int t, final int r, int b) {
   1084         boolean result = super.setFrame(l, t, r, b);
   1085 
   1086         if (mPopup.isShowing()) {
   1087             showDropDown();
   1088         }
   1089 
   1090         return result;
   1091     }
   1092 
   1093     /**
   1094      * <p>Used for lazy instantiation of the anchor view from the id we have. If the value of
   1095      * the id is NO_ID or we can't find a view for the given id, we return this TextView as
   1096      * the default anchoring point.</p>
   1097      */
   1098     private View getDropDownAnchorView() {
   1099         if (mDropDownAnchorView == null && mDropDownAnchorId != View.NO_ID) {
   1100             mDropDownAnchorView = getRootView().findViewById(mDropDownAnchorId);
   1101         }
   1102         return mDropDownAnchorView == null ? this : mDropDownAnchorView;
   1103     }
   1104 
   1105     /**
   1106      * Issues a runnable to show the dropdown as soon as possible.
   1107      *
   1108      * @hide internal used only by SearchDialog
   1109      */
   1110     public void showDropDownAfterLayout() {
   1111         post(mShowDropDownRunnable);
   1112     }
   1113 
   1114     /**
   1115      * Ensures that the drop down is not obscuring the IME.
   1116      * @param visible whether the ime should be in front. If false, the ime is pushed to
   1117      * the background.
   1118      * @hide internal used only here and SearchDialog
   1119      */
   1120     public void ensureImeVisible(boolean visible) {
   1121         mPopup.setInputMethodMode(visible
   1122                 ? PopupWindow.INPUT_METHOD_NEEDED : PopupWindow.INPUT_METHOD_NOT_NEEDED);
   1123         showDropDown();
   1124     }
   1125 
   1126     /**
   1127      * @hide internal used only here and SearchDialog
   1128      */
   1129     public boolean isInputMethodNotNeeded() {
   1130         return mPopup.getInputMethodMode() == PopupWindow.INPUT_METHOD_NOT_NEEDED;
   1131     }
   1132 
   1133     /**
   1134      * <p>Displays the drop down on screen.</p>
   1135      */
   1136     public void showDropDown() {
   1137         int height = buildDropDown();
   1138 
   1139         int widthSpec = 0;
   1140         int heightSpec = 0;
   1141 
   1142         boolean noInputMethod = isInputMethodNotNeeded();
   1143 
   1144         if (mPopup.isShowing()) {
   1145             if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) {
   1146                 // The call to PopupWindow's update method below can accept -1 for any
   1147                 // value you do not want to update.
   1148                 widthSpec = -1;
   1149             } else if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
   1150                 widthSpec = getDropDownAnchorView().getWidth();
   1151             } else {
   1152                 widthSpec = mDropDownWidth;
   1153             }
   1154 
   1155             if (mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
   1156                 // The call to PopupWindow's update method below can accept -1 for any
   1157                 // value you do not want to update.
   1158                 heightSpec = noInputMethod ? height : ViewGroup.LayoutParams.MATCH_PARENT;
   1159                 if (noInputMethod) {
   1160                     mPopup.setWindowLayoutMode(
   1161                             mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ?
   1162                                     ViewGroup.LayoutParams.MATCH_PARENT : 0, 0);
   1163                 } else {
   1164                     mPopup.setWindowLayoutMode(
   1165                             mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ?
   1166                                     ViewGroup.LayoutParams.MATCH_PARENT : 0,
   1167                             ViewGroup.LayoutParams.MATCH_PARENT);
   1168                 }
   1169             } else if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
   1170                 heightSpec = height;
   1171             } else {
   1172                 heightSpec = mDropDownHeight;
   1173             }
   1174 
   1175             mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible);
   1176 
   1177             mPopup.update(getDropDownAnchorView(), mDropDownHorizontalOffset,
   1178                     mDropDownVerticalOffset, widthSpec, heightSpec);
   1179         } else {
   1180             if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) {
   1181                 widthSpec = ViewGroup.LayoutParams.MATCH_PARENT;
   1182             } else {
   1183                 if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
   1184                     mPopup.setWidth(getDropDownAnchorView().getWidth());
   1185                 } else {
   1186                     mPopup.setWidth(mDropDownWidth);
   1187                 }
   1188             }
   1189 
   1190             if (mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
   1191                 heightSpec = ViewGroup.LayoutParams.MATCH_PARENT;
   1192             } else {
   1193                 if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
   1194                     mPopup.setHeight(height);
   1195                 } else {
   1196                     mPopup.setHeight(mDropDownHeight);
   1197                 }
   1198             }
   1199 
   1200             mPopup.setWindowLayoutMode(widthSpec, heightSpec);
   1201             mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
   1202 
   1203             // use outside touchable to dismiss drop down when touching outside of it, so
   1204             // only set this if the dropdown is not always visible
   1205             mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible);
   1206             mPopup.setTouchInterceptor(new PopupTouchInterceptor());
   1207             mPopup.showAsDropDown(getDropDownAnchorView(),
   1208                     mDropDownHorizontalOffset, mDropDownVerticalOffset);
   1209             mDropDownList.setSelection(ListView.INVALID_POSITION);
   1210             clearListSelection();
   1211             post(mHideSelector);
   1212         }
   1213     }
   1214 
   1215     /**
   1216      * Forces outside touches to be ignored. Normally if {@link #isDropDownAlwaysVisible()} is
   1217      * false, we allow outside touch to dismiss the dropdown. If this is set to true, then we
   1218      * ignore outside touch even when the drop down is not set to always visible.
   1219      *
   1220      * @hide used only by SearchDialog
   1221      */
   1222     public void setForceIgnoreOutsideTouch(boolean forceIgnoreOutsideTouch) {
   1223         mForceIgnoreOutsideTouch = forceIgnoreOutsideTouch;
   1224     }
   1225 
   1226     /**
   1227      * <p>Builds the popup window's content and returns the height the popup
   1228      * should have. Returns -1 when the content already exists.</p>
   1229      *
   1230      * @return the content's height or -1 if content already exists
   1231      */
   1232     private int buildDropDown() {
   1233         ViewGroup dropDownView;
   1234         int otherHeights = 0;
   1235 
   1236         final ListAdapter adapter = mAdapter;
   1237         if (adapter != null) {
   1238             InputMethodManager imm = InputMethodManager.peekInstance();
   1239             if (imm != null) {
   1240                 final int count = Math.min(adapter.getCount(), 20);
   1241                 CompletionInfo[] completions = new CompletionInfo[count];
   1242                 int realCount = 0;
   1243 
   1244                 for (int i = 0; i < count; i++) {
   1245                     if (adapter.isEnabled(i)) {
   1246                         realCount++;
   1247                         Object item = adapter.getItem(i);
   1248                         long id = adapter.getItemId(i);
   1249                         completions[i] = new CompletionInfo(id, i,
   1250                                 convertSelectionToString(item));
   1251                     }
   1252                 }
   1253 
   1254                 if (realCount != count) {
   1255                     CompletionInfo[] tmp = new CompletionInfo[realCount];
   1256                     System.arraycopy(completions, 0, tmp, 0, realCount);
   1257                     completions = tmp;
   1258                 }
   1259 
   1260                 imm.displayCompletions(this, completions);
   1261             }
   1262         }
   1263 
   1264         if (mDropDownList == null) {
   1265             Context context = getContext();
   1266 
   1267             mHideSelector = new ListSelectorHider();
   1268 
   1269             /**
   1270              * This Runnable exists for the sole purpose of checking if the view layout has got
   1271              * completed and if so call showDropDown to display the drop down. This is used to show
   1272              * the drop down as soon as possible after user opens up the search dialog, without
   1273              * waiting for the normal UI pipeline to do it's job which is slower than this method.
   1274              */
   1275             mShowDropDownRunnable = new Runnable() {
   1276                 public void run() {
   1277                     // View layout should be all done before displaying the drop down.
   1278                     View view = getDropDownAnchorView();
   1279                     if (view != null && view.getWindowToken() != null) {
   1280                         showDropDown();
   1281                     }
   1282                 }
   1283             };
   1284 
   1285             mDropDownList = new DropDownListView(context);
   1286             mDropDownList.setSelector(mDropDownListHighlight);
   1287             mDropDownList.setAdapter(adapter);
   1288             mDropDownList.setVerticalFadingEdgeEnabled(true);
   1289             mDropDownList.setOnItemClickListener(mDropDownItemClickListener);
   1290             mDropDownList.setFocusable(true);
   1291             mDropDownList.setFocusableInTouchMode(true);
   1292             mDropDownList.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
   1293                 public void onItemSelected(AdapterView<?> parent, View view,
   1294                         int position, long id) {
   1295 
   1296                     if (position != -1) {
   1297                         DropDownListView dropDownList = mDropDownList;
   1298 
   1299                         if (dropDownList != null) {
   1300                             dropDownList.mListSelectionHidden = false;
   1301                         }
   1302                     }
   1303                 }
   1304 
   1305                 public void onNothingSelected(AdapterView<?> parent) {
   1306                 }
   1307             });
   1308             mDropDownList.setOnScrollListener(new PopupScrollListener());
   1309 
   1310             if (mItemSelectedListener != null) {
   1311                 mDropDownList.setOnItemSelectedListener(mItemSelectedListener);
   1312             }
   1313 
   1314             dropDownView = mDropDownList;
   1315 
   1316             View hintView = getHintView(context);
   1317             if (hintView != null) {
   1318                 // if an hint has been specified, we accomodate more space for it and
   1319                 // add a text view in the drop down menu, at the bottom of the list
   1320                 LinearLayout hintContainer = new LinearLayout(context);
   1321                 hintContainer.setOrientation(LinearLayout.VERTICAL);
   1322 
   1323                 LinearLayout.LayoutParams hintParams = new LinearLayout.LayoutParams(
   1324                         ViewGroup.LayoutParams.MATCH_PARENT, 0, 1.0f
   1325                 );
   1326                 hintContainer.addView(dropDownView, hintParams);
   1327                 hintContainer.addView(hintView);
   1328 
   1329                 // measure the hint's height to find how much more vertical space
   1330                 // we need to add to the drop down's height
   1331                 int widthSpec = MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST);
   1332                 int heightSpec = MeasureSpec.UNSPECIFIED;
   1333                 hintView.measure(widthSpec, heightSpec);
   1334 
   1335                 hintParams = (LinearLayout.LayoutParams) hintView.getLayoutParams();
   1336                 otherHeights = hintView.getMeasuredHeight() + hintParams.topMargin
   1337                         + hintParams.bottomMargin;
   1338 
   1339                 dropDownView = hintContainer;
   1340             }
   1341 
   1342             mPopup.setContentView(dropDownView);
   1343         } else {
   1344             dropDownView = (ViewGroup) mPopup.getContentView();
   1345             final View view = dropDownView.findViewById(HINT_VIEW_ID);
   1346             if (view != null) {
   1347                 LinearLayout.LayoutParams hintParams =
   1348                         (LinearLayout.LayoutParams) view.getLayoutParams();
   1349                 otherHeights = view.getMeasuredHeight() + hintParams.topMargin
   1350                         + hintParams.bottomMargin;
   1351             }
   1352         }
   1353 
   1354         // Max height available on the screen for a popup.
   1355         boolean ignoreBottomDecorations =
   1356                 mPopup.getInputMethodMode() == PopupWindow.INPUT_METHOD_NOT_NEEDED;
   1357         final int maxHeight = mPopup.getMaxAvailableHeight(
   1358                 getDropDownAnchorView(), mDropDownVerticalOffset, ignoreBottomDecorations);
   1359 
   1360         // getMaxAvailableHeight() subtracts the padding, so we put it back,
   1361         // to get the available height for the whole window
   1362         int padding = 0;
   1363         Drawable background = mPopup.getBackground();
   1364         if (background != null) {
   1365             background.getPadding(mTempRect);
   1366             padding = mTempRect.top + mTempRect.bottom;
   1367         }
   1368 
   1369         if (mDropDownAlwaysVisible || mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
   1370             return maxHeight + padding;
   1371         }
   1372 
   1373         final int listContent = mDropDownList.measureHeightOfChildren(MeasureSpec.UNSPECIFIED,
   1374                 0, ListView.NO_POSITION, maxHeight - otherHeights, 2);
   1375         // add padding only if the list has items in it, that way we don't show
   1376         // the popup if it is not needed
   1377         if (listContent > 0) otherHeights += padding;
   1378 
   1379         return listContent + otherHeights;
   1380     }
   1381 
   1382     private View getHintView(Context context) {
   1383         if (mHintText != null && mHintText.length() > 0) {
   1384             final TextView hintView = (TextView) LayoutInflater.from(context).inflate(
   1385                     mHintResource, null).findViewById(com.android.internal.R.id.text1);
   1386             hintView.setText(mHintText);
   1387             hintView.setId(HINT_VIEW_ID);
   1388             return hintView;
   1389         } else {
   1390             return null;
   1391         }
   1392     }
   1393 
   1394     /**
   1395      * Sets the validator used to perform text validation.
   1396      *
   1397      * @param validator The validator used to validate the text entered in this widget.
   1398      *
   1399      * @see #getValidator()
   1400      * @see #performValidation()
   1401      */
   1402     public void setValidator(Validator validator) {
   1403         mValidator = validator;
   1404     }
   1405 
   1406     /**
   1407      * Returns the Validator set with {@link #setValidator},
   1408      * or <code>null</code> if it was not set.
   1409      *
   1410      * @see #setValidator(android.widget.AutoCompleteTextView.Validator)
   1411      * @see #performValidation()
   1412      */
   1413     public Validator getValidator() {
   1414         return mValidator;
   1415     }
   1416 
   1417     /**
   1418      * If a validator was set on this view and the current string is not valid,
   1419      * ask the validator to fix it.
   1420      *
   1421      * @see #getValidator()
   1422      * @see #setValidator(android.widget.AutoCompleteTextView.Validator)
   1423      */
   1424     public void performValidation() {
   1425         if (mValidator == null) return;
   1426 
   1427         CharSequence text = getText();
   1428 
   1429         if (!TextUtils.isEmpty(text) && !mValidator.isValid(text)) {
   1430             setText(mValidator.fixText(text));
   1431         }
   1432     }
   1433 
   1434     /**
   1435      * Returns the Filter obtained from {@link Filterable#getFilter},
   1436      * or <code>null</code> if {@link #setAdapter} was not called with
   1437      * a Filterable.
   1438      */
   1439     protected Filter getFilter() {
   1440         return mFilter;
   1441     }
   1442 
   1443     private class ListSelectorHider implements Runnable {
   1444         public void run() {
   1445             clearListSelection();
   1446         }
   1447     }
   1448 
   1449     private class ResizePopupRunnable implements Runnable {
   1450         public void run() {
   1451             mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
   1452             showDropDown();
   1453         }
   1454     }
   1455 
   1456     private class PopupTouchInterceptor implements OnTouchListener {
   1457         public boolean onTouch(View v, MotionEvent event) {
   1458             final int action = event.getAction();
   1459             if (action == MotionEvent.ACTION_DOWN &&
   1460                     mPopup != null && mPopup.isShowing()) {
   1461                 postDelayed(mResizePopupRunnable, EXPAND_LIST_TIMEOUT);
   1462             } else if (action == MotionEvent.ACTION_UP) {
   1463                 removeCallbacks(mResizePopupRunnable);
   1464             }
   1465             return false;
   1466         }
   1467     }
   1468 
   1469     private class PopupScrollListener implements ListView.OnScrollListener {
   1470         public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
   1471                 int totalItemCount) {
   1472 
   1473         }
   1474 
   1475         public void onScrollStateChanged(AbsListView view, int scrollState) {
   1476             if (scrollState == SCROLL_STATE_TOUCH_SCROLL &&
   1477                     !isInputMethodNotNeeded() && mPopup.getContentView() != null) {
   1478                 removeCallbacks(mResizePopupRunnable);
   1479                 mResizePopupRunnable.run();
   1480             }
   1481         }
   1482     }
   1483 
   1484     private class DropDownItemClickListener implements AdapterView.OnItemClickListener {
   1485         public void onItemClick(AdapterView parent, View v, int position, long id) {
   1486             performCompletion(v, position, id);
   1487         }
   1488     }
   1489 
   1490     /**
   1491      * <p>Wrapper class for a ListView. This wrapper hijacks the focus to
   1492      * make sure the list uses the appropriate drawables and states when
   1493      * displayed on screen within a drop down. The focus is never actually
   1494      * passed to the drop down; the list only looks focused.</p>
   1495      */
   1496     private static class DropDownListView extends ListView {
   1497         /*
   1498          * WARNING: This is a workaround for a touch mode issue.
   1499          *
   1500          * Touch mode is propagated lazily to windows. This causes problems in
   1501          * the following scenario:
   1502          * - Type something in the AutoCompleteTextView and get some results
   1503          * - Move down with the d-pad to select an item in the list
   1504          * - Move up with the d-pad until the selection disappears
   1505          * - Type more text in the AutoCompleteTextView *using the soft keyboard*
   1506          *   and get new results; you are now in touch mode
   1507          * - The selection comes back on the first item in the list, even though
   1508          *   the list is supposed to be in touch mode
   1509          *
   1510          * Using the soft keyboard triggers the touch mode change but that change
   1511          * is propagated to our window only after the first list layout, therefore
   1512          * after the list attempts to resurrect the selection.
   1513          *
   1514          * The trick to work around this issue is to pretend the list is in touch
   1515          * mode when we know that the selection should not appear, that is when
   1516          * we know the user moved the selection away from the list.
   1517          *
   1518          * This boolean is set to true whenever we explicitely hide the list's
   1519          * selection and reset to false whenver we know the user moved the
   1520          * selection back to the list.
   1521          *
   1522          * When this boolean is true, isInTouchMode() returns true, otherwise it
   1523          * returns super.isInTouchMode().
   1524          */
   1525         private boolean mListSelectionHidden;
   1526 
   1527         /**
   1528          * <p>Creates a new list view wrapper.</p>
   1529          *
   1530          * @param context this view's context
   1531          */
   1532         public DropDownListView(Context context) {
   1533             super(context, null, com.android.internal.R.attr.dropDownListViewStyle);
   1534         }
   1535 
   1536         /**
   1537          * <p>Avoids jarring scrolling effect by ensuring that list elements
   1538          * made of a text view fit on a single line.</p>
   1539          *
   1540          * @param position the item index in the list to get a view for
   1541          * @return the view for the specified item
   1542          */
   1543         @Override
   1544         View obtainView(int position, boolean[] isScrap) {
   1545             View view = super.obtainView(position, isScrap);
   1546 
   1547             if (view instanceof TextView) {
   1548                 ((TextView) view).setHorizontallyScrolling(true);
   1549             }
   1550 
   1551             return view;
   1552         }
   1553 
   1554         @Override
   1555         public boolean isInTouchMode() {
   1556             // WARNING: Please read the comment where mListSelectionHidden is declared
   1557             return mListSelectionHidden || super.isInTouchMode();
   1558         }
   1559 
   1560         /**
   1561          * <p>Returns the focus state in the drop down.</p>
   1562          *
   1563          * @return true always
   1564          */
   1565         @Override
   1566         public boolean hasWindowFocus() {
   1567             return true;
   1568         }
   1569 
   1570         /**
   1571          * <p>Returns the focus state in the drop down.</p>
   1572          *
   1573          * @return true always
   1574          */
   1575         @Override
   1576         public boolean isFocused() {
   1577             return true;
   1578         }
   1579 
   1580         /**
   1581          * <p>Returns the focus state in the drop down.</p>
   1582          *
   1583          * @return true always
   1584          */
   1585         @Override
   1586         public boolean hasFocus() {
   1587             return true;
   1588         }
   1589 
   1590         protected int[] onCreateDrawableState(int extraSpace) {
   1591             int[] res = super.onCreateDrawableState(extraSpace);
   1592             //noinspection ConstantIfStatement
   1593             if (false) {
   1594                 StringBuilder sb = new StringBuilder("Created drawable state: [");
   1595                 for (int i=0; i<res.length; i++) {
   1596                     if (i > 0) sb.append(", ");
   1597                     sb.append("0x");
   1598                     sb.append(Integer.toHexString(res[i]));
   1599                 }
   1600                 sb.append("]");
   1601                 Log.i(TAG, sb.toString());
   1602             }
   1603             return res;
   1604         }
   1605     }
   1606 
   1607     /**
   1608      * This interface is used to make sure that the text entered in this TextView complies to
   1609      * a certain format.  Since there is no foolproof way to prevent the user from leaving
   1610      * this View with an incorrect value in it, all we can do is try to fix it ourselves
   1611      * when this happens.
   1612      */
   1613     public interface Validator {
   1614         /**
   1615          * Validates the specified text.
   1616          *
   1617          * @return true If the text currently in the text editor is valid.
   1618          *
   1619          * @see #fixText(CharSequence)
   1620          */
   1621         boolean isValid(CharSequence text);
   1622 
   1623         /**
   1624          * Corrects the specified text to make it valid.
   1625          *
   1626          * @param invalidText A string that doesn't pass validation: isValid(invalidText)
   1627          *        returns false
   1628          *
   1629          * @return A string based on invalidText such as invoking isValid() on it returns true.
   1630          *
   1631          * @see #isValid(CharSequence)
   1632          */
   1633         CharSequence fixText(CharSequence invalidText);
   1634     }
   1635 
   1636     /**
   1637      * Allows us a private hook into the on click event without preventing users from setting
   1638      * their own click listener.
   1639      */
   1640     private class PassThroughClickListener implements OnClickListener {
   1641 
   1642         private View.OnClickListener mWrapped;
   1643 
   1644         /** {@inheritDoc} */
   1645         public void onClick(View v) {
   1646             onClickImpl();
   1647 
   1648             if (mWrapped != null) mWrapped.onClick(v);
   1649         }
   1650     }
   1651 
   1652     private class PopupDataSetObserver extends DataSetObserver {
   1653         @Override
   1654         public void onChanged() {
   1655             if (isPopupShowing()) {
   1656                 // This will resize the popup to fit the new adapter's content
   1657                 showDropDown();
   1658             } else if (mAdapter != null) {
   1659                 // If the popup is not showing already, showing it will cause
   1660                 // the list of data set observers attached to the adapter to
   1661                 // change. We can't do it from here, because we are in the middle
   1662                 // of iterating throught he list of observers.
   1663                 post(new Runnable() {
   1664                     public void run() {
   1665                         final ListAdapter adapter = mAdapter;
   1666                         if (adapter != null) {
   1667                             updateDropDownForFilter(adapter.getCount());
   1668                         }
   1669                     }
   1670                 });
   1671             }
   1672         }
   1673 
   1674         @Override
   1675         public void onInvalidated() {
   1676             if (!mDropDownAlwaysVisible) {
   1677                 // There's no data to display so make sure we're not showing
   1678                 // the drop down and its list
   1679                 dismissDropDown();
   1680             }
   1681         }
   1682     }
   1683 }
   1684