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<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 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 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_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 onKeyDown(int keyCode, KeyEvent event)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 */ enoughToFilter()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 beforeTextChanged(CharSequence s, int start, int count, int after)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 afterTextChanged(Editable s)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 onTextChanged(CharSequence s, int start, int before, int count)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) doBeforeTextChanged()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) doAfterTextChanged()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 */ refreshAutoCompleteResults()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 */ isPopupShowing()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 */ convertSelectionToString(Object selectedItem)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 */ clearListSelection()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 */ setListSelection(int position)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 */ getListSelection()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" }) performFiltering(CharSequence text, int keyCode)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 */ performCompletion()1034 public void performCompletion() { 1035 performCompletion(null, -1, -1); 1036 } 1037 1038 @Override onCommitCompletion(CompletionInfo completion)1039 public void onCommitCompletion(CompletionInfo completion) { 1040 if (isPopupShowing()) { 1041 mPopup.performItemClick(completion.getPosition()); 1042 } 1043 } 1044 performCompletion(View selectedView, int position, long id)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 */ isPerformingCompletion()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 */ setText(CharSequence text, boolean filter)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 */ replaceText(CharSequence text)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} */ onFilterComplete(int count)1120 public void onFilterComplete(int count) { 1121 updateDropDownForFilter(count); 1122 } 1123 updateDropDownForFilter(int count)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 onWindowFocusChanged(boolean hasWindowFocus)1151 public void onWindowFocusChanged(boolean hasWindowFocus) { 1152 super.onWindowFocusChanged(hasWindowFocus); 1153 if (!hasWindowFocus && !mPopup.isDropDownAlwaysVisible()) { 1154 dismissDropDown(); 1155 } 1156 } 1157 1158 @Override onDisplayHint(int hint)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 onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect)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 onAttachedToWindow()1189 protected void onAttachedToWindow() { 1190 super.onAttachedToWindow(); 1191 } 1192 1193 @Override onDetachedFromWindow()1194 protected void onDetachedFromWindow() { 1195 dismissDropDown(); 1196 super.onDetachedFromWindow(); 1197 } 1198 1199 /** 1200 * <p>Closes the drop down if present on screen.</p> 1201 */ dismissDropDown()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 setFrame(final int l, int t, final int r, int b)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 showDropDownAfterLayout()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) ensureImeVisible(boolean visible)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) isInputMethodNotNeeded()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 */ getInputMethodMode()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 */ setInputMethodMode(@nputMethodMode int mode)1294 public void setInputMethodMode(@InputMethodMode int mode) { 1295 mPopup.setInputMethodMode(mode); 1296 } 1297 1298 /** 1299 * <p>Displays the drop down on screen.</p> 1300 */ showDropDown()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 setForceIgnoreOutsideTouch(boolean forceIgnoreOutsideTouch)1328 public void setForceIgnoreOutsideTouch(boolean forceIgnoreOutsideTouch) { 1329 mPopup.setForceIgnoreOutsideTouch(forceIgnoreOutsideTouch); 1330 } 1331 buildImeCompletions()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 */ setValidator(Validator validator)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 */ getValidator()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 */ performValidation()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 */ getFilter()1407 protected Filter getFilter() { 1408 return mFilter; 1409 } 1410 1411 private class DropDownItemClickListener implements AdapterView.OnItemClickListener { onItemClick(AdapterView parent, View v, int position, long id)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 */ isValid(CharSequence text)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 */ fixText(CharSequence invalidText)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 */ onDismiss()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} */ onClick(View v)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 PopupDataSetObserver(AutoCompleteTextView view)1484 private PopupDataSetObserver(AutoCompleteTextView view) { 1485 mViewReference = new WeakReference<AutoCompleteTextView>(view); 1486 } 1487 1488 @Override onChanged()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