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.compat.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<String> adapter = new ArrayAdapter<String>(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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 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 */ AutoCompleteTextView(Context context)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 */ AutoCompleteTextView(Context context, AttributeSet attrs)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 */ AutoCompleteTextView(Context context, AttributeSet attrs, int defStyleAttr)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 */ AutoCompleteTextView( Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)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 */ AutoCompleteTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes, Theme popupTheme)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 setOnClickListener(OnClickListener listener)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 */ onClickImpl()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 */ setCompletionHint(CharSequence hint)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 getCompletionHint()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 getDropDownWidth()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 */ setDropDownWidth(int width)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 getDropDownHeight()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 */ setDropDownHeight(int height)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 */ getDropDownAnchor()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 */ setDropDownAnchor(int id)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") getDropDownBackground()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 */ setDropDownBackgroundDrawable(Drawable d)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 */ setDropDownBackgroundResource(@rawableRes int id)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 */ setDropDownVerticalOffset(int offset)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 getDropDownVerticalOffset()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 */ setDropDownHorizontalOffset(int offset)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 getDropDownHorizontalOffset()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) setDropDownAnimationStyle(int animationStyle)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 */ getDropDownAnimationStyle()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 */ isDropDownAlwaysVisible()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 setDropDownAlwaysVisible(boolean dropDownAlwaysVisible)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 */ isDropDownDismissedOnCompletion()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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) setDropDownDismissedOnCompletion(boolean dropDownDismissedOnCompletion)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") getThreshold()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 */ setThreshold(int threshold)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 */ setOnItemClickListener(AdapterView.OnItemClickListener l)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 */ setOnItemSelectedListener(AdapterView.OnItemSelectedListener l)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 getItemClickListener()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 getItemSelectedListener()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 */ getOnItemClickListener()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 */ getOnItemSelectedListener()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 */ setOnDismissListener(final OnDismissListener dismissListener)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 */ getAdapter()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 */ setAdapter(T adapter)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 onKeyPreIme(int keyCode, KeyEvent event)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 onKeyUp(int keyCode, KeyEvent event)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_NUMPAD_ENTER: 825 case KeyEvent.KEYCODE_DPAD_CENTER: 826 case KeyEvent.KEYCODE_TAB: 827 if (event.hasNoModifiers()) { 828 performCompletion(); 829 } 830 return true; 831 } 832 } 833 834 if (isPopupShowing() && keyCode == KeyEvent.KEYCODE_TAB && event.hasNoModifiers()) { 835 performCompletion(); 836 return true; 837 } 838 839 return super.onKeyUp(keyCode, event); 840 } 841 842 @Override onKeyDown(int keyCode, KeyEvent event)843 public boolean onKeyDown(int keyCode, KeyEvent event) { 844 if (mPopup.onKeyDown(keyCode, event)) { 845 return true; 846 } 847 848 if (!isPopupShowing()) { 849 switch(keyCode) { 850 case KeyEvent.KEYCODE_DPAD_DOWN: 851 if (event.hasNoModifiers()) { 852 performValidation(); 853 } 854 } 855 } 856 857 if (isPopupShowing() && keyCode == KeyEvent.KEYCODE_TAB && event.hasNoModifiers()) { 858 return true; 859 } 860 861 mLastKeyCode = keyCode; 862 boolean handled = super.onKeyDown(keyCode, event); 863 mLastKeyCode = KeyEvent.KEYCODE_UNKNOWN; 864 865 if (handled && isPopupShowing()) { 866 clearListSelection(); 867 } 868 869 return handled; 870 } 871 872 /** 873 * Returns <code>true</code> if the amount of text in the field meets 874 * or exceeds the {@link #getThreshold} requirement. You can override 875 * this to impose a different standard for when filtering will be 876 * triggered. 877 */ enoughToFilter()878 public boolean enoughToFilter() { 879 if (DEBUG) Log.v(TAG, "Enough to filter: len=" + getText().length() 880 + " threshold=" + mThreshold); 881 return getText().length() >= mThreshold; 882 } 883 884 885 886 /** This is used to watch for edits to the text view. */ 887 private class MyWatcher implements TextWatcher { 888 private boolean mOpenBefore; 889 beforeTextChanged(CharSequence s, int start, int count, int after)890 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 891 if (mBlockCompletion) return; 892 893 // when text is changed, inserted or deleted, we attempt to show 894 // the drop down 895 mOpenBefore = isPopupShowing(); 896 if (DEBUG) Log.v(TAG, "before text changed: open=" + mOpenBefore); 897 } 898 afterTextChanged(Editable s)899 public void afterTextChanged(Editable s) { 900 if (mBlockCompletion) return; 901 902 // if the list was open before the keystroke, but closed afterwards, 903 // then something in the keystroke processing (an input filter perhaps) 904 // called performCompletion() and we shouldn't do any more processing. 905 if (DEBUG) { 906 Log.v(TAG, "after text changed: openBefore=" + mOpenBefore 907 + " open=" + isPopupShowing()); 908 } 909 910 if (mOpenBefore && !isPopupShowing()) return; 911 912 refreshAutoCompleteResults(); 913 } 914 onTextChanged(CharSequence s, int start, int before, int count)915 public void onTextChanged(CharSequence s, int start, int before, int count) { 916 } 917 } 918 919 /** 920 * This function is deprecated. Please use {@link #refreshAutoCompleteResults} instead. 921 * Note: Remove {@link #mAutoCompleteTextWatcher} after removing this function. 922 */ 923 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) doBeforeTextChanged()924 void doBeforeTextChanged() { 925 mAutoCompleteTextWatcher.beforeTextChanged(null, 0, 0, 0); 926 } 927 928 /** 929 * This function is deprecated. Please use {@link #refreshAutoCompleteResults} instead. 930 * Note: Remove {@link #mAutoCompleteTextWatcher} after removing this function. 931 */ 932 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) doAfterTextChanged()933 void doAfterTextChanged() { 934 mAutoCompleteTextWatcher.afterTextChanged(null); 935 } 936 937 /** 938 * Refreshes the auto complete results. You usually shouldn't have to manually refresh the 939 * AutoCompleteResults as this is done automatically whenever the text changes. However if the 940 * results are not available and have to be fetched, you can call this function after fetching 941 * the results. 942 */ refreshAutoCompleteResults()943 public final void refreshAutoCompleteResults() { 944 // the drop down is shown only when a minimum number of characters 945 // was typed in the text view 946 if (enoughToFilter()) { 947 if (mFilter != null) { 948 mPopupCanBeUpdated = true; 949 performFiltering(getText(), mLastKeyCode); 950 } 951 } else { 952 // drop down is automatically dismissed when enough characters 953 // are deleted from the text view 954 if (!mPopup.isDropDownAlwaysVisible()) { 955 dismissDropDown(); 956 } 957 if (mFilter != null) { 958 mFilter.filter(null); 959 } 960 } 961 } 962 963 /** 964 * <p>Indicates whether the popup menu is showing.</p> 965 * 966 * @return true if the popup menu is showing, false otherwise 967 */ isPopupShowing()968 public boolean isPopupShowing() { 969 return mPopup.isShowing(); 970 } 971 972 /** 973 * <p>Converts the selected item from the drop down list into a sequence 974 * of character that can be used in the edit box.</p> 975 * 976 * @param selectedItem the item selected by the user for completion 977 * 978 * @return a sequence of characters representing the selected suggestion 979 */ convertSelectionToString(Object selectedItem)980 protected CharSequence convertSelectionToString(Object selectedItem) { 981 return mFilter.convertResultToString(selectedItem); 982 } 983 984 /** 985 * <p>Clear the list selection. This may only be temporary, as user input will often bring 986 * it back. 987 */ clearListSelection()988 public void clearListSelection() { 989 mPopup.clearListSelection(); 990 } 991 992 /** 993 * Set the position of the dropdown view selection. 994 * 995 * @param position The position to move the selector to. 996 */ setListSelection(int position)997 public void setListSelection(int position) { 998 mPopup.setSelection(position); 999 } 1000 1001 /** 1002 * Get the position of the dropdown view selection, if there is one. Returns 1003 * {@link ListView#INVALID_POSITION ListView.INVALID_POSITION} if there is no dropdown or if 1004 * there is no selection. 1005 * 1006 * @return the position of the current selection, if there is one, or 1007 * {@link ListView#INVALID_POSITION ListView.INVALID_POSITION} if not. 1008 * 1009 * @see ListView#getSelectedItemPosition() 1010 */ getListSelection()1011 public int getListSelection() { 1012 return mPopup.getSelectedItemPosition(); 1013 } 1014 1015 /** 1016 * <p>Starts filtering the content of the drop down list. The filtering 1017 * pattern is the content of the edit box. Subclasses should override this 1018 * method to filter with a different pattern, for instance a substring of 1019 * <code>text</code>.</p> 1020 * 1021 * @param text the filtering pattern 1022 * @param keyCode the last character inserted in the edit box; beware that 1023 * this will be null when text is being added through a soft input method. 1024 */ 1025 @SuppressWarnings({ "UnusedDeclaration" }) performFiltering(CharSequence text, int keyCode)1026 protected void performFiltering(CharSequence text, int keyCode) { 1027 mFilter.filter(text, this); 1028 } 1029 1030 /** 1031 * <p>Performs the text completion by converting the selected item from 1032 * the drop down list into a string, replacing the text box's content with 1033 * this string and finally dismissing the drop down menu.</p> 1034 */ performCompletion()1035 public void performCompletion() { 1036 performCompletion(null, -1, -1); 1037 } 1038 1039 @Override onCommitCompletion(CompletionInfo completion)1040 public void onCommitCompletion(CompletionInfo completion) { 1041 if (isPopupShowing()) { 1042 mPopup.performItemClick(completion.getPosition()); 1043 } 1044 } 1045 performCompletion(View selectedView, int position, long id)1046 private void performCompletion(View selectedView, int position, long id) { 1047 if (isPopupShowing()) { 1048 Object selectedItem; 1049 if (position < 0) { 1050 selectedItem = mPopup.getSelectedItem(); 1051 } else { 1052 selectedItem = mAdapter.getItem(position); 1053 } 1054 if (selectedItem == null) { 1055 Log.w(TAG, "performCompletion: no selected item"); 1056 return; 1057 } 1058 1059 mBlockCompletion = true; 1060 replaceText(convertSelectionToString(selectedItem)); 1061 mBlockCompletion = false; 1062 1063 if (mItemClickListener != null) { 1064 final ListPopupWindow list = mPopup; 1065 1066 if (selectedView == null || position < 0) { 1067 selectedView = list.getSelectedView(); 1068 position = list.getSelectedItemPosition(); 1069 id = list.getSelectedItemId(); 1070 } 1071 mItemClickListener.onItemClick(list.getListView(), selectedView, position, id); 1072 } 1073 } 1074 1075 if (mDropDownDismissedOnCompletion && !mPopup.isDropDownAlwaysVisible()) { 1076 dismissDropDown(); 1077 } 1078 } 1079 1080 /** 1081 * Identifies whether the view is currently performing a text completion, so subclasses 1082 * can decide whether to respond to text changed events. 1083 */ isPerformingCompletion()1084 public boolean isPerformingCompletion() { 1085 return mBlockCompletion; 1086 } 1087 1088 /** 1089 * Like {@link #setText(CharSequence)}, except that it can disable filtering. 1090 * 1091 * @param filter If <code>false</code>, no filtering will be performed 1092 * as a result of this call. 1093 */ setText(CharSequence text, boolean filter)1094 public void setText(CharSequence text, boolean filter) { 1095 if (filter) { 1096 setText(text); 1097 } else { 1098 mBlockCompletion = true; 1099 setText(text); 1100 mBlockCompletion = false; 1101 } 1102 } 1103 1104 /** 1105 * <p>Performs the text completion by replacing the current text by the 1106 * selected item. Subclasses should override this method to avoid replacing 1107 * the whole content of the edit box.</p> 1108 * 1109 * @param text the selected suggestion in the drop down list 1110 */ replaceText(CharSequence text)1111 protected void replaceText(CharSequence text) { 1112 clearComposingText(); 1113 1114 setText(text); 1115 // make sure we keep the caret at the end of the text view 1116 Editable spannable = getText(); 1117 Selection.setSelection(spannable, spannable.length()); 1118 } 1119 1120 /** {@inheritDoc} */ onFilterComplete(int count)1121 public void onFilterComplete(int count) { 1122 updateDropDownForFilter(count); 1123 } 1124 updateDropDownForFilter(int count)1125 private void updateDropDownForFilter(int count) { 1126 // Not attached to window, don't update drop-down 1127 if (getWindowVisibility() == View.GONE) return; 1128 1129 /* 1130 * This checks enoughToFilter() again because filtering requests 1131 * are asynchronous, so the result may come back after enough text 1132 * has since been deleted to make it no longer appropriate 1133 * to filter. 1134 */ 1135 1136 final boolean dropDownAlwaysVisible = mPopup.isDropDownAlwaysVisible(); 1137 final boolean enoughToFilter = enoughToFilter(); 1138 if ((count > 0 || dropDownAlwaysVisible) && enoughToFilter) { 1139 if (hasFocus() && hasWindowFocus() && mPopupCanBeUpdated) { 1140 showDropDown(); 1141 } 1142 } else if (!dropDownAlwaysVisible && isPopupShowing()) { 1143 dismissDropDown(); 1144 // When the filter text is changed, the first update from the adapter may show an empty 1145 // count (when the query is being performed on the network). Future updates when some 1146 // content has been retrieved should still be able to update the list. 1147 mPopupCanBeUpdated = true; 1148 } 1149 } 1150 1151 @Override onWindowFocusChanged(boolean hasWindowFocus)1152 public void onWindowFocusChanged(boolean hasWindowFocus) { 1153 super.onWindowFocusChanged(hasWindowFocus); 1154 if (!hasWindowFocus && !mPopup.isDropDownAlwaysVisible()) { 1155 dismissDropDown(); 1156 } 1157 } 1158 1159 @Override onDisplayHint(int hint)1160 protected void onDisplayHint(int hint) { 1161 super.onDisplayHint(hint); 1162 switch (hint) { 1163 case INVISIBLE: 1164 if (!mPopup.isDropDownAlwaysVisible()) { 1165 dismissDropDown(); 1166 } 1167 break; 1168 } 1169 } 1170 1171 @Override onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect)1172 protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { 1173 super.onFocusChanged(focused, direction, previouslyFocusedRect); 1174 1175 if (isTemporarilyDetached()) { 1176 // If we are temporarily in the detach state, then do nothing. 1177 return; 1178 } 1179 1180 // Perform validation if the view is losing focus. 1181 if (!focused) { 1182 performValidation(); 1183 } 1184 if (!focused && !mPopup.isDropDownAlwaysVisible()) { 1185 dismissDropDown(); 1186 } 1187 } 1188 1189 @Override onAttachedToWindow()1190 protected void onAttachedToWindow() { 1191 super.onAttachedToWindow(); 1192 } 1193 1194 @Override onDetachedFromWindow()1195 protected void onDetachedFromWindow() { 1196 dismissDropDown(); 1197 super.onDetachedFromWindow(); 1198 } 1199 1200 /** 1201 * <p>Closes the drop down if present on screen.</p> 1202 */ dismissDropDown()1203 public void dismissDropDown() { 1204 InputMethodManager imm = getContext().getSystemService(InputMethodManager.class); 1205 if (imm != null) { 1206 imm.displayCompletions(this, null); 1207 } 1208 mPopup.dismiss(); 1209 mPopupCanBeUpdated = false; 1210 } 1211 1212 @Override setFrame(final int l, int t, final int r, int b)1213 protected boolean setFrame(final int l, int t, final int r, int b) { 1214 boolean result = super.setFrame(l, t, r, b); 1215 1216 if (isPopupShowing()) { 1217 showDropDown(); 1218 } 1219 1220 return result; 1221 } 1222 1223 /** 1224 * Issues a runnable to show the dropdown as soon as possible. 1225 * 1226 * @hide internal used only by SearchDialog 1227 */ 1228 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) showDropDownAfterLayout()1229 public void showDropDownAfterLayout() { 1230 mPopup.postShow(); 1231 } 1232 1233 /** 1234 * Ensures that the drop down is not obscuring the IME. 1235 * @param visible whether the ime should be in front. If false, the ime is pushed to 1236 * the background. 1237 * 1238 * This method is deprecated. Please use the following methods instead. 1239 * Use {@link #setInputMethodMode} to ensure that the drop down is not obscuring the IME. 1240 * Use {@link #showDropDown()} to show the drop down immediately 1241 * A combination of {@link #isDropDownAlwaysVisible()} and {@link #enoughToFilter()} to decide 1242 * whether to manually trigger {@link #showDropDown()} or not. 1243 * 1244 * @hide internal used only here and SearchDialog 1245 */ 1246 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768913) ensureImeVisible(boolean visible)1247 public void ensureImeVisible(boolean visible) { 1248 mPopup.setInputMethodMode(visible 1249 ? ListPopupWindow.INPUT_METHOD_NEEDED : ListPopupWindow.INPUT_METHOD_NOT_NEEDED); 1250 if (mPopup.isDropDownAlwaysVisible() || (mFilter != null && enoughToFilter())) { 1251 showDropDown(); 1252 } 1253 } 1254 1255 /** 1256 * This method is deprecated. Please use {@link #getInputMethodMode()} instead. 1257 * 1258 * @hide This API is not being used and can be removed. 1259 */ 1260 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) isInputMethodNotNeeded()1261 public boolean isInputMethodNotNeeded() { 1262 return mPopup.getInputMethodMode() == ListPopupWindow.INPUT_METHOD_NOT_NEEDED; 1263 } 1264 1265 /** 1266 * The valid input method modes for the {@link AutoCompleteTextView}: 1267 * 1268 * {@hide} 1269 */ 1270 @IntDef({ListPopupWindow.INPUT_METHOD_FROM_FOCUSABLE, 1271 ListPopupWindow.INPUT_METHOD_NEEDED, 1272 ListPopupWindow.INPUT_METHOD_NOT_NEEDED}) 1273 @Retention(RetentionPolicy.SOURCE) 1274 public @interface InputMethodMode {} 1275 1276 /** 1277 * Returns the input method mode used by the auto complete dropdown. 1278 */ getInputMethodMode()1279 public @InputMethodMode int getInputMethodMode() { 1280 return mPopup.getInputMethodMode(); 1281 } 1282 1283 /** 1284 * Use this method to specify when the IME should be displayed. This function can be used to 1285 * prevent the dropdown from obscuring the IME. 1286 * 1287 * @param mode speficies the input method mode. use one of the following values: 1288 * 1289 * {@link ListPopupWindow#INPUT_METHOD_FROM_FOCUSABLE} IME Displayed if the auto-complete box is 1290 * focusable. 1291 * {@link ListPopupWindow#INPUT_METHOD_NEEDED} Always display the IME. 1292 * {@link ListPopupWindow#INPUT_METHOD_NOT_NEEDED}. The auto-complete suggestions are always 1293 * displayed, even if the suggestions cover/hide the input method. 1294 */ setInputMethodMode(@nputMethodMode int mode)1295 public void setInputMethodMode(@InputMethodMode int mode) { 1296 mPopup.setInputMethodMode(mode); 1297 } 1298 1299 /** 1300 * <p>Displays the drop down on screen.</p> 1301 */ showDropDown()1302 public void showDropDown() { 1303 buildImeCompletions(); 1304 1305 if (mPopup.getAnchorView() == null) { 1306 if (mDropDownAnchorId != View.NO_ID) { 1307 mPopup.setAnchorView(getRootView().findViewById(mDropDownAnchorId)); 1308 } else { 1309 mPopup.setAnchorView(this); 1310 } 1311 } 1312 if (!isPopupShowing()) { 1313 // Make sure the list does not obscure the IME when shown for the first time. 1314 mPopup.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NEEDED); 1315 mPopup.setListItemExpandMax(EXPAND_MAX); 1316 } 1317 mPopup.show(); 1318 mPopup.getListView().setOverScrollMode(View.OVER_SCROLL_ALWAYS); 1319 } 1320 1321 /** 1322 * Forces outside touches to be ignored. Normally if {@link #isDropDownAlwaysVisible()} is 1323 * false, we allow outside touch to dismiss the dropdown. If this is set to true, then we 1324 * ignore outside touch even when the drop down is not set to always visible. 1325 * 1326 * @hide used only by SearchDialog 1327 */ 1328 @UnsupportedAppUsage setForceIgnoreOutsideTouch(boolean forceIgnoreOutsideTouch)1329 public void setForceIgnoreOutsideTouch(boolean forceIgnoreOutsideTouch) { 1330 mPopup.setForceIgnoreOutsideTouch(forceIgnoreOutsideTouch); 1331 } 1332 buildImeCompletions()1333 private void buildImeCompletions() { 1334 final ListAdapter adapter = mAdapter; 1335 if (adapter != null) { 1336 InputMethodManager imm = getContext().getSystemService(InputMethodManager.class); 1337 if (imm != null) { 1338 final int count = Math.min(adapter.getCount(), 20); 1339 CompletionInfo[] completions = new CompletionInfo[count]; 1340 int realCount = 0; 1341 1342 for (int i = 0; i < count; i++) { 1343 if (adapter.isEnabled(i)) { 1344 Object item = adapter.getItem(i); 1345 long id = adapter.getItemId(i); 1346 completions[realCount] = new CompletionInfo(id, realCount, 1347 convertSelectionToString(item)); 1348 realCount++; 1349 } 1350 } 1351 1352 if (realCount != count) { 1353 CompletionInfo[] tmp = new CompletionInfo[realCount]; 1354 System.arraycopy(completions, 0, tmp, 0, realCount); 1355 completions = tmp; 1356 } 1357 1358 imm.displayCompletions(this, completions); 1359 } 1360 } 1361 } 1362 1363 /** 1364 * Sets the validator used to perform text validation. 1365 * 1366 * @param validator The validator used to validate the text entered in this widget. 1367 * 1368 * @see #getValidator() 1369 * @see #performValidation() 1370 */ setValidator(Validator validator)1371 public void setValidator(Validator validator) { 1372 mValidator = validator; 1373 } 1374 1375 /** 1376 * Returns the Validator set with {@link #setValidator}, 1377 * or <code>null</code> if it was not set. 1378 * 1379 * @see #setValidator(android.widget.AutoCompleteTextView.Validator) 1380 * @see #performValidation() 1381 */ getValidator()1382 public Validator getValidator() { 1383 return mValidator; 1384 } 1385 1386 /** 1387 * If a validator was set on this view and the current string is not valid, 1388 * ask the validator to fix it. 1389 * 1390 * @see #getValidator() 1391 * @see #setValidator(android.widget.AutoCompleteTextView.Validator) 1392 */ performValidation()1393 public void performValidation() { 1394 if (mValidator == null) return; 1395 1396 CharSequence text = getText(); 1397 1398 if (!TextUtils.isEmpty(text) && !mValidator.isValid(text)) { 1399 setText(mValidator.fixText(text)); 1400 } 1401 } 1402 1403 /** 1404 * Returns the Filter obtained from {@link Filterable#getFilter}, 1405 * or <code>null</code> if {@link #setAdapter} was not called with 1406 * a Filterable. 1407 */ getFilter()1408 protected Filter getFilter() { 1409 return mFilter; 1410 } 1411 1412 @Override getAccessibilityClassName()1413 public CharSequence getAccessibilityClassName() { 1414 return AutoCompleteTextView.class.getName(); 1415 } 1416 1417 private class DropDownItemClickListener implements AdapterView.OnItemClickListener { onItemClick(AdapterView parent, View v, int position, long id)1418 public void onItemClick(AdapterView parent, View v, int position, long id) { 1419 performCompletion(v, position, id); 1420 } 1421 } 1422 1423 /** 1424 * This interface is used to make sure that the text entered in this TextView complies to 1425 * a certain format. Since there is no foolproof way to prevent the user from leaving 1426 * this View with an incorrect value in it, all we can do is try to fix it ourselves 1427 * when this happens. 1428 */ 1429 public interface Validator { 1430 /** 1431 * Validates the specified text. 1432 * 1433 * @return true If the text currently in the text editor is valid. 1434 * 1435 * @see #fixText(CharSequence) 1436 */ isValid(CharSequence text)1437 boolean isValid(CharSequence text); 1438 1439 /** 1440 * Corrects the specified text to make it valid. 1441 * 1442 * @param invalidText A string that doesn't pass validation: isValid(invalidText) 1443 * returns false 1444 * 1445 * @return A string based on invalidText such as invoking isValid() on it returns true. 1446 * 1447 * @see #isValid(CharSequence) 1448 */ fixText(CharSequence invalidText)1449 CharSequence fixText(CharSequence invalidText); 1450 } 1451 1452 /** 1453 * Listener to respond to the AutoCompleteTextView's completion list being dismissed. 1454 * @see AutoCompleteTextView#setOnDismissListener(OnDismissListener) 1455 */ 1456 public interface OnDismissListener { 1457 /** 1458 * This method will be invoked whenever the AutoCompleteTextView's list 1459 * of completion options has been dismissed and is no longer available 1460 * for user interaction. 1461 */ onDismiss()1462 void onDismiss(); 1463 } 1464 1465 /** 1466 * Allows us a private hook into the on click event without preventing users from setting 1467 * their own click listener. 1468 */ 1469 private class PassThroughClickListener implements OnClickListener { 1470 1471 private View.OnClickListener mWrapped; 1472 1473 /** {@inheritDoc} */ onClick(View v)1474 public void onClick(View v) { 1475 onClickImpl(); 1476 1477 if (mWrapped != null) mWrapped.onClick(v); 1478 } 1479 } 1480 1481 /** 1482 * Static inner listener that keeps a WeakReference to the actual AutoCompleteTextView. 1483 * <p> 1484 * This way, if adapter has a longer life span than the View, we won't leak the View, instead 1485 * we will just leak a small Observer with 1 field. 1486 */ 1487 private static class PopupDataSetObserver extends DataSetObserver { 1488 private final WeakReference<AutoCompleteTextView> mViewReference; 1489 PopupDataSetObserver(AutoCompleteTextView view)1490 private PopupDataSetObserver(AutoCompleteTextView view) { 1491 mViewReference = new WeakReference<AutoCompleteTextView>(view); 1492 } 1493 1494 @Override onChanged()1495 public void onChanged() { 1496 final AutoCompleteTextView textView = mViewReference.get(); 1497 if (textView != null && textView.mAdapter != null) { 1498 // If the popup is not showing already, showing it will cause 1499 // the list of data set observers attached to the adapter to 1500 // change. We can't do it from here, because we are in the middle 1501 // of iterating through the list of observers. 1502 textView.post(updateRunnable); 1503 } 1504 } 1505 1506 private final Runnable updateRunnable = new Runnable() { 1507 @Override 1508 public void run() { 1509 final AutoCompleteTextView textView = mViewReference.get(); 1510 if (textView == null) { 1511 return; 1512 } 1513 final ListAdapter adapter = textView.mAdapter; 1514 if (adapter == null) { 1515 return; 1516 } 1517 textView.updateDropDownForFilter(adapter.getCount()); 1518 } 1519 }; 1520 } 1521 } 1522