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