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