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