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