1 /* 2 * Copyright (C) 2006 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.NonNull; 20 import android.annotation.Nullable; 21 import android.annotation.UnsupportedAppUsage; 22 import android.content.Context; 23 import android.database.DataSetObserver; 24 import android.os.Build; 25 import android.os.Parcelable; 26 import android.os.SystemClock; 27 import android.util.AttributeSet; 28 import android.util.SparseArray; 29 import android.view.ContextMenu; 30 import android.view.ContextMenu.ContextMenuInfo; 31 import android.view.SoundEffectConstants; 32 import android.view.View; 33 import android.view.ViewDebug; 34 import android.view.ViewGroup; 35 import android.view.ViewHierarchyEncoder; 36 import android.view.ViewStructure; 37 import android.view.accessibility.AccessibilityEvent; 38 import android.view.accessibility.AccessibilityManager; 39 import android.view.accessibility.AccessibilityNodeInfo; 40 import android.view.autofill.AutofillManager; 41 42 /** 43 * An AdapterView is a view whose children are determined by an {@link Adapter}. 44 * 45 * <p> 46 * See {@link ListView}, {@link GridView}, {@link Spinner} and 47 * {@link Gallery} for commonly used subclasses of AdapterView. 48 * 49 * <div class="special reference"> 50 * <h3>Developer Guides</h3> 51 * <p>For more information about using AdapterView, read the 52 * <a href="{@docRoot}guide/topics/ui/binding.html">Binding to Data with AdapterView</a> 53 * developer guide.</p></div> 54 */ 55 public abstract class AdapterView<T extends Adapter> extends ViewGroup { 56 57 /** 58 * The item view type returned by {@link Adapter#getItemViewType(int)} when 59 * the adapter does not want the item's view recycled. 60 */ 61 public static final int ITEM_VIEW_TYPE_IGNORE = -1; 62 63 /** 64 * The item view type returned by {@link Adapter#getItemViewType(int)} when 65 * the item is a header or footer. 66 */ 67 public static final int ITEM_VIEW_TYPE_HEADER_OR_FOOTER = -2; 68 69 /** 70 * The position of the first child displayed 71 */ 72 @ViewDebug.ExportedProperty(category = "scrolling") 73 @UnsupportedAppUsage 74 int mFirstPosition = 0; 75 76 /** 77 * The offset in pixels from the top of the AdapterView to the top 78 * of the view to select during the next layout. 79 */ 80 int mSpecificTop; 81 82 /** 83 * Position from which to start looking for mSyncRowId 84 */ 85 @UnsupportedAppUsage 86 int mSyncPosition; 87 88 /** 89 * Row id to look for when data has changed 90 */ 91 long mSyncRowId = INVALID_ROW_ID; 92 93 /** 94 * Height of the view when mSyncPosition and mSyncRowId where set 95 */ 96 long mSyncHeight; 97 98 /** 99 * True if we need to sync to mSyncRowId 100 */ 101 @UnsupportedAppUsage 102 boolean mNeedSync = false; 103 104 /** 105 * Indicates whether to sync based on the selection or position. Possible 106 * values are {@link #SYNC_SELECTED_POSITION} or 107 * {@link #SYNC_FIRST_POSITION}. 108 */ 109 int mSyncMode; 110 111 /** 112 * Our height after the last layout 113 */ 114 private int mLayoutHeight; 115 116 /** 117 * Sync based on the selected child 118 */ 119 static final int SYNC_SELECTED_POSITION = 0; 120 121 /** 122 * Sync based on the first child displayed 123 */ 124 static final int SYNC_FIRST_POSITION = 1; 125 126 /** 127 * Maximum amount of time to spend in {@link #findSyncPosition()} 128 */ 129 static final int SYNC_MAX_DURATION_MILLIS = 100; 130 131 /** 132 * Indicates that this view is currently being laid out. 133 */ 134 boolean mInLayout = false; 135 136 /** 137 * The listener that receives notifications when an item is selected. 138 */ 139 @UnsupportedAppUsage 140 OnItemSelectedListener mOnItemSelectedListener; 141 142 /** 143 * The listener that receives notifications when an item is clicked. 144 */ 145 @UnsupportedAppUsage 146 OnItemClickListener mOnItemClickListener; 147 148 /** 149 * The listener that receives notifications when an item is long clicked. 150 */ 151 OnItemLongClickListener mOnItemLongClickListener; 152 153 /** 154 * True if the data has changed since the last layout 155 */ 156 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768524) 157 boolean mDataChanged; 158 159 /** 160 * The position within the adapter's data set of the item to select 161 * during the next layout. 162 */ 163 @ViewDebug.ExportedProperty(category = "list") 164 @UnsupportedAppUsage 165 int mNextSelectedPosition = INVALID_POSITION; 166 167 /** 168 * The item id of the item to select during the next layout. 169 */ 170 @UnsupportedAppUsage 171 long mNextSelectedRowId = INVALID_ROW_ID; 172 173 /** 174 * The position within the adapter's data set of the currently selected item. 175 */ 176 @ViewDebug.ExportedProperty(category = "list") 177 @UnsupportedAppUsage 178 int mSelectedPosition = INVALID_POSITION; 179 180 /** 181 * The item id of the currently selected item. 182 */ 183 long mSelectedRowId = INVALID_ROW_ID; 184 185 /** 186 * View to show if there are no items to show. 187 */ 188 private View mEmptyView; 189 190 /** 191 * The number of items in the current adapter. 192 */ 193 @ViewDebug.ExportedProperty(category = "list") 194 int mItemCount; 195 196 /** 197 * The number of items in the adapter before a data changed event occurred. 198 */ 199 int mOldItemCount; 200 201 /** 202 * Represents an invalid position. All valid positions are in the range 0 to 1 less than the 203 * number of items in the current adapter. 204 */ 205 public static final int INVALID_POSITION = -1; 206 207 /** 208 * Represents an empty or invalid row id 209 */ 210 public static final long INVALID_ROW_ID = Long.MIN_VALUE; 211 212 /** 213 * The last selected position we used when notifying 214 */ 215 @UnsupportedAppUsage 216 int mOldSelectedPosition = INVALID_POSITION; 217 218 /** 219 * The id of the last selected position we used when notifying 220 */ 221 long mOldSelectedRowId = INVALID_ROW_ID; 222 223 /** 224 * Indicates what focusable state is requested when calling setFocusable(). 225 * In addition to this, this view has other criteria for actually 226 * determining the focusable state (such as whether its empty or the text 227 * filter is shown). 228 * 229 * @see #setFocusable(boolean) 230 * @see #checkFocus() 231 */ 232 private int mDesiredFocusableState = FOCUSABLE_AUTO; 233 private boolean mDesiredFocusableInTouchModeState; 234 235 /** Lazily-constructed runnable for dispatching selection events. */ 236 private SelectionNotifier mSelectionNotifier; 237 238 /** Selection notifier that's waiting for the next layout pass. */ 239 private SelectionNotifier mPendingSelectionNotifier; 240 241 /** 242 * When set to true, calls to requestLayout() will not propagate up the parent hierarchy. 243 * This is used to layout the children during a layout pass. 244 */ 245 boolean mBlockLayoutRequests = false; 246 AdapterView(Context context)247 public AdapterView(Context context) { 248 this(context, null); 249 } 250 AdapterView(Context context, AttributeSet attrs)251 public AdapterView(Context context, AttributeSet attrs) { 252 this(context, attrs, 0); 253 } 254 AdapterView(Context context, AttributeSet attrs, int defStyleAttr)255 public AdapterView(Context context, AttributeSet attrs, int defStyleAttr) { 256 this(context, attrs, defStyleAttr, 0); 257 } 258 AdapterView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)259 public AdapterView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 260 super(context, attrs, defStyleAttr, defStyleRes); 261 262 // If not explicitly specified this view is important for accessibility. 263 if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { 264 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); 265 } 266 267 mDesiredFocusableState = getFocusable(); 268 if (mDesiredFocusableState == FOCUSABLE_AUTO) { 269 // Starts off without an adapter, so NOT_FOCUSABLE by default. 270 super.setFocusable(NOT_FOCUSABLE); 271 } 272 } 273 274 /** 275 * Interface definition for a callback to be invoked when an item in this 276 * AdapterView has been clicked. 277 */ 278 public interface OnItemClickListener { 279 280 /** 281 * Callback method to be invoked when an item in this AdapterView has 282 * been clicked. 283 * <p> 284 * Implementers can call getItemAtPosition(position) if they need 285 * to access the data associated with the selected item. 286 * 287 * @param parent The AdapterView where the click happened. 288 * @param view The view within the AdapterView that was clicked (this 289 * will be a view provided by the adapter) 290 * @param position The position of the view in the adapter. 291 * @param id The row id of the item that was clicked. 292 */ onItemClick(AdapterView<?> parent, View view, int position, long id)293 void onItemClick(AdapterView<?> parent, View view, int position, long id); 294 } 295 296 /** 297 * Register a callback to be invoked when an item in this AdapterView has 298 * been clicked. 299 * 300 * @param listener The callback that will be invoked. 301 */ setOnItemClickListener(@ullable OnItemClickListener listener)302 public void setOnItemClickListener(@Nullable OnItemClickListener listener) { 303 mOnItemClickListener = listener; 304 } 305 306 /** 307 * @return The callback to be invoked with an item in this AdapterView has 308 * been clicked, or null if no callback has been set. 309 */ 310 @Nullable getOnItemClickListener()311 public final OnItemClickListener getOnItemClickListener() { 312 return mOnItemClickListener; 313 } 314 315 /** 316 * Call the OnItemClickListener, if it is defined. Performs all normal 317 * actions associated with clicking: reporting accessibility event, playing 318 * a sound, etc. 319 * 320 * @param view The view within the AdapterView that was clicked. 321 * @param position The position of the view in the adapter. 322 * @param id The row id of the item that was clicked. 323 * @return True if there was an assigned OnItemClickListener that was 324 * called, false otherwise is returned. 325 */ performItemClick(View view, int position, long id)326 public boolean performItemClick(View view, int position, long id) { 327 final boolean result; 328 if (mOnItemClickListener != null) { 329 playSoundEffect(SoundEffectConstants.CLICK); 330 mOnItemClickListener.onItemClick(this, view, position, id); 331 result = true; 332 } else { 333 result = false; 334 } 335 336 if (view != null) { 337 view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); 338 } 339 return result; 340 } 341 342 /** 343 * Interface definition for a callback to be invoked when an item in this 344 * view has been clicked and held. 345 */ 346 public interface OnItemLongClickListener { 347 /** 348 * Callback method to be invoked when an item in this view has been 349 * clicked and held. 350 * 351 * Implementers can call getItemAtPosition(position) if they need to access 352 * the data associated with the selected item. 353 * 354 * @param parent The AbsListView where the click happened 355 * @param view The view within the AbsListView that was clicked 356 * @param position The position of the view in the list 357 * @param id The row id of the item that was clicked 358 * 359 * @return true if the callback consumed the long click, false otherwise 360 */ onItemLongClick(AdapterView<?> parent, View view, int position, long id)361 boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id); 362 } 363 364 365 /** 366 * Register a callback to be invoked when an item in this AdapterView has 367 * been clicked and held 368 * 369 * @param listener The callback that will run 370 */ setOnItemLongClickListener(OnItemLongClickListener listener)371 public void setOnItemLongClickListener(OnItemLongClickListener listener) { 372 if (!isLongClickable()) { 373 setLongClickable(true); 374 } 375 mOnItemLongClickListener = listener; 376 } 377 378 /** 379 * @return The callback to be invoked with an item in this AdapterView has 380 * been clicked and held, or null if no callback has been set. 381 */ getOnItemLongClickListener()382 public final OnItemLongClickListener getOnItemLongClickListener() { 383 return mOnItemLongClickListener; 384 } 385 386 /** 387 * Interface definition for a callback to be invoked when 388 * an item in this view has been selected. 389 */ 390 public interface OnItemSelectedListener { 391 /** 392 * <p>Callback method to be invoked when an item in this view has been 393 * selected. This callback is invoked only when the newly selected 394 * position is different from the previously selected position or if 395 * there was no selected item.</p> 396 * 397 * Implementers can call getItemAtPosition(position) if they need to access the 398 * data associated with the selected item. 399 * 400 * @param parent The AdapterView where the selection happened 401 * @param view The view within the AdapterView that was clicked 402 * @param position The position of the view in the adapter 403 * @param id The row id of the item that is selected 404 */ onItemSelected(AdapterView<?> parent, View view, int position, long id)405 void onItemSelected(AdapterView<?> parent, View view, int position, long id); 406 407 /** 408 * Callback method to be invoked when the selection disappears from this 409 * view. The selection can disappear for instance when touch is activated 410 * or when the adapter becomes empty. 411 * 412 * @param parent The AdapterView that now contains no selected item. 413 */ onNothingSelected(AdapterView<?> parent)414 void onNothingSelected(AdapterView<?> parent); 415 } 416 417 418 /** 419 * Register a callback to be invoked when an item in this AdapterView has 420 * been selected. 421 * 422 * @param listener The callback that will run 423 */ setOnItemSelectedListener(@ullable OnItemSelectedListener listener)424 public void setOnItemSelectedListener(@Nullable OnItemSelectedListener listener) { 425 mOnItemSelectedListener = listener; 426 } 427 428 @Nullable getOnItemSelectedListener()429 public final OnItemSelectedListener getOnItemSelectedListener() { 430 return mOnItemSelectedListener; 431 } 432 433 /** 434 * Extra menu information provided to the 435 * {@link android.view.View.OnCreateContextMenuListener#onCreateContextMenu(ContextMenu, View, ContextMenuInfo) } 436 * callback when a context menu is brought up for this AdapterView. 437 * 438 */ 439 public static class AdapterContextMenuInfo implements ContextMenu.ContextMenuInfo { 440 AdapterContextMenuInfo(View targetView, int position, long id)441 public AdapterContextMenuInfo(View targetView, int position, long id) { 442 this.targetView = targetView; 443 this.position = position; 444 this.id = id; 445 } 446 447 /** 448 * The child view for which the context menu is being displayed. This 449 * will be one of the children of this AdapterView. 450 */ 451 public View targetView; 452 453 /** 454 * The position in the adapter for which the context menu is being 455 * displayed. 456 */ 457 public int position; 458 459 /** 460 * The row id of the item for which the context menu is being displayed. 461 */ 462 public long id; 463 } 464 465 /** 466 * Returns the adapter currently associated with this widget. 467 * 468 * @return The adapter used to provide this view's content. 469 */ getAdapter()470 public abstract T getAdapter(); 471 472 /** 473 * Sets the adapter that provides the data and the views to represent the data 474 * in this widget. 475 * 476 * @param adapter The adapter to use to create this view's content. 477 */ setAdapter(T adapter)478 public abstract void setAdapter(T adapter); 479 480 /** 481 * This method is not supported and throws an UnsupportedOperationException when called. 482 * 483 * @param child Ignored. 484 * 485 * @throws UnsupportedOperationException Every time this method is invoked. 486 */ 487 @Override addView(View child)488 public void addView(View child) { 489 throw new UnsupportedOperationException("addView(View) is not supported in AdapterView"); 490 } 491 492 /** 493 * This method is not supported and throws an UnsupportedOperationException when called. 494 * 495 * @param child Ignored. 496 * @param index Ignored. 497 * 498 * @throws UnsupportedOperationException Every time this method is invoked. 499 */ 500 @Override addView(View child, int index)501 public void addView(View child, int index) { 502 throw new UnsupportedOperationException("addView(View, int) is not supported in AdapterView"); 503 } 504 505 /** 506 * This method is not supported and throws an UnsupportedOperationException when called. 507 * 508 * @param child Ignored. 509 * @param params Ignored. 510 * 511 * @throws UnsupportedOperationException Every time this method is invoked. 512 */ 513 @Override addView(View child, LayoutParams params)514 public void addView(View child, LayoutParams params) { 515 throw new UnsupportedOperationException("addView(View, LayoutParams) " 516 + "is not supported in AdapterView"); 517 } 518 519 /** 520 * This method is not supported and throws an UnsupportedOperationException when called. 521 * 522 * @param child Ignored. 523 * @param index Ignored. 524 * @param params Ignored. 525 * 526 * @throws UnsupportedOperationException Every time this method is invoked. 527 */ 528 @Override addView(View child, int index, LayoutParams params)529 public void addView(View child, int index, LayoutParams params) { 530 throw new UnsupportedOperationException("addView(View, int, LayoutParams) " 531 + "is not supported in AdapterView"); 532 } 533 534 /** 535 * This method is not supported and throws an UnsupportedOperationException when called. 536 * 537 * @param child Ignored. 538 * 539 * @throws UnsupportedOperationException Every time this method is invoked. 540 */ 541 @Override removeView(View child)542 public void removeView(View child) { 543 throw new UnsupportedOperationException("removeView(View) is not supported in AdapterView"); 544 } 545 546 /** 547 * This method is not supported and throws an UnsupportedOperationException when called. 548 * 549 * @param index Ignored. 550 * 551 * @throws UnsupportedOperationException Every time this method is invoked. 552 */ 553 @Override removeViewAt(int index)554 public void removeViewAt(int index) { 555 throw new UnsupportedOperationException("removeViewAt(int) is not supported in AdapterView"); 556 } 557 558 /** 559 * This method is not supported and throws an UnsupportedOperationException when called. 560 * 561 * @throws UnsupportedOperationException Every time this method is invoked. 562 */ 563 @Override removeAllViews()564 public void removeAllViews() { 565 throw new UnsupportedOperationException("removeAllViews() is not supported in AdapterView"); 566 } 567 568 @Override onLayout(boolean changed, int left, int top, int right, int bottom)569 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 570 mLayoutHeight = getHeight(); 571 } 572 573 /** 574 * Return the position of the currently selected item within the adapter's data set 575 * 576 * @return int Position (starting at 0), or {@link #INVALID_POSITION} if there is nothing selected. 577 */ 578 @ViewDebug.CapturedViewProperty getSelectedItemPosition()579 public int getSelectedItemPosition() { 580 return mNextSelectedPosition; 581 } 582 583 /** 584 * @return The id corresponding to the currently selected item, or {@link #INVALID_ROW_ID} 585 * if nothing is selected. 586 */ 587 @ViewDebug.CapturedViewProperty getSelectedItemId()588 public long getSelectedItemId() { 589 return mNextSelectedRowId; 590 } 591 592 /** 593 * @return The view corresponding to the currently selected item, or null 594 * if nothing is selected 595 */ getSelectedView()596 public abstract View getSelectedView(); 597 598 /** 599 * @return The data corresponding to the currently selected item, or 600 * null if there is nothing selected. 601 */ getSelectedItem()602 public Object getSelectedItem() { 603 T adapter = getAdapter(); 604 int selection = getSelectedItemPosition(); 605 if (adapter != null && adapter.getCount() > 0 && selection >= 0) { 606 return adapter.getItem(selection); 607 } else { 608 return null; 609 } 610 } 611 612 /** 613 * @return The number of items owned by the Adapter associated with this 614 * AdapterView. (This is the number of data items, which may be 615 * larger than the number of visible views.) 616 */ 617 @ViewDebug.CapturedViewProperty getCount()618 public int getCount() { 619 return mItemCount; 620 } 621 622 /** 623 * Returns the position within the adapter's data set for the view, where 624 * view is a an adapter item or a descendant of an adapter item. 625 * <p> 626 * <strong>Note:</strong> The result of this method only reflects the 627 * position of the data bound to <var>view</var> during the most recent 628 * layout pass. If the adapter's data set has changed without a subsequent 629 * layout pass, the position returned by this method may not match the 630 * current position of the data within the adapter. 631 * 632 * @param view an adapter item, or a descendant of an adapter item. This 633 * must be visible in this AdapterView at the time of the call. 634 * @return the position within the adapter's data set of the view, or 635 * {@link #INVALID_POSITION} if the view does not correspond to a 636 * list item (or it is not currently visible) 637 */ getPositionForView(View view)638 public int getPositionForView(View view) { 639 View listItem = view; 640 try { 641 View v; 642 while ((v = (View) listItem.getParent()) != null && !v.equals(this)) { 643 listItem = v; 644 } 645 } catch (ClassCastException e) { 646 // We made it up to the window without find this list view 647 return INVALID_POSITION; 648 } 649 650 if (listItem != null) { 651 // Search the children for the list item 652 final int childCount = getChildCount(); 653 for (int i = 0; i < childCount; i++) { 654 if (getChildAt(i).equals(listItem)) { 655 return mFirstPosition + i; 656 } 657 } 658 } 659 660 // Child not found! 661 return INVALID_POSITION; 662 } 663 664 /** 665 * Returns the position within the adapter's data set for the first item 666 * displayed on screen. 667 * 668 * @return The position within the adapter's data set 669 */ getFirstVisiblePosition()670 public int getFirstVisiblePosition() { 671 return mFirstPosition; 672 } 673 674 /** 675 * Returns the position within the adapter's data set for the last item 676 * displayed on screen. 677 * 678 * @return The position within the adapter's data set 679 */ getLastVisiblePosition()680 public int getLastVisiblePosition() { 681 return mFirstPosition + getChildCount() - 1; 682 } 683 684 /** 685 * Sets the currently selected item. To support accessibility subclasses that 686 * override this method must invoke the overridden super method first. 687 * 688 * @param position Index (starting at 0) of the data item to be selected. 689 */ setSelection(int position)690 public abstract void setSelection(int position); 691 692 /** 693 * Sets the view to show if the adapter is empty 694 */ 695 @android.view.RemotableViewMethod setEmptyView(View emptyView)696 public void setEmptyView(View emptyView) { 697 mEmptyView = emptyView; 698 699 // If not explicitly specified this view is important for accessibility. 700 if (emptyView != null 701 && emptyView.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { 702 emptyView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); 703 } 704 705 final T adapter = getAdapter(); 706 final boolean empty = ((adapter == null) || adapter.isEmpty()); 707 updateEmptyStatus(empty); 708 } 709 710 /** 711 * When the current adapter is empty, the AdapterView can display a special view 712 * called the empty view. The empty view is used to provide feedback to the user 713 * that no data is available in this AdapterView. 714 * 715 * @return The view to show if the adapter is empty. 716 */ getEmptyView()717 public View getEmptyView() { 718 return mEmptyView; 719 } 720 721 /** 722 * Indicates whether this view is in filter mode. Filter mode can for instance 723 * be enabled by a user when typing on the keyboard. 724 * 725 * @return True if the view is in filter mode, false otherwise. 726 */ isInFilterMode()727 boolean isInFilterMode() { 728 return false; 729 } 730 731 @Override setFocusable(@ocusable int focusable)732 public void setFocusable(@Focusable int focusable) { 733 final T adapter = getAdapter(); 734 final boolean empty = adapter == null || adapter.getCount() == 0; 735 736 mDesiredFocusableState = focusable; 737 if ((focusable & (FOCUSABLE_AUTO | FOCUSABLE)) == 0) { 738 mDesiredFocusableInTouchModeState = false; 739 } 740 741 super.setFocusable((!empty || isInFilterMode()) ? focusable : NOT_FOCUSABLE); 742 } 743 744 @Override setFocusableInTouchMode(boolean focusable)745 public void setFocusableInTouchMode(boolean focusable) { 746 final T adapter = getAdapter(); 747 final boolean empty = adapter == null || adapter.getCount() == 0; 748 749 mDesiredFocusableInTouchModeState = focusable; 750 if (focusable) { 751 mDesiredFocusableState = FOCUSABLE; 752 } 753 754 super.setFocusableInTouchMode(focusable && (!empty || isInFilterMode())); 755 } 756 checkFocus()757 void checkFocus() { 758 final T adapter = getAdapter(); 759 final boolean empty = adapter == null || adapter.getCount() == 0; 760 final boolean focusable = !empty || isInFilterMode(); 761 // The order in which we set focusable in touch mode/focusable may matter 762 // for the client, see View.setFocusableInTouchMode() comments for more 763 // details 764 super.setFocusableInTouchMode(focusable && mDesiredFocusableInTouchModeState); 765 super.setFocusable(focusable ? mDesiredFocusableState : NOT_FOCUSABLE); 766 if (mEmptyView != null) { 767 updateEmptyStatus((adapter == null) || adapter.isEmpty()); 768 } 769 } 770 771 /** 772 * Update the status of the list based on the empty parameter. If empty is true and 773 * we have an empty view, display it. In all the other cases, make sure that the listview 774 * is VISIBLE and that the empty view is GONE (if it's not null). 775 */ updateEmptyStatus(boolean empty)776 private void updateEmptyStatus(boolean empty) { 777 if (isInFilterMode()) { 778 empty = false; 779 } 780 781 if (empty) { 782 if (mEmptyView != null) { 783 mEmptyView.setVisibility(View.VISIBLE); 784 setVisibility(View.GONE); 785 } else { 786 // If the caller just removed our empty view, make sure the list view is visible 787 setVisibility(View.VISIBLE); 788 } 789 790 // We are now GONE, so pending layouts will not be dispatched. 791 // Force one here to make sure that the state of the list matches 792 // the state of the adapter. 793 if (mDataChanged) { 794 this.onLayout(false, mLeft, mTop, mRight, mBottom); 795 } 796 } else { 797 if (mEmptyView != null) mEmptyView.setVisibility(View.GONE); 798 setVisibility(View.VISIBLE); 799 } 800 } 801 802 /** 803 * Gets the data associated with the specified position in the list. 804 * 805 * @param position Which data to get 806 * @return The data associated with the specified position in the list 807 */ getItemAtPosition(int position)808 public Object getItemAtPosition(int position) { 809 T adapter = getAdapter(); 810 return (adapter == null || position < 0) ? null : adapter.getItem(position); 811 } 812 getItemIdAtPosition(int position)813 public long getItemIdAtPosition(int position) { 814 T adapter = getAdapter(); 815 return (adapter == null || position < 0) ? INVALID_ROW_ID : adapter.getItemId(position); 816 } 817 818 @Override setOnClickListener(OnClickListener l)819 public void setOnClickListener(OnClickListener l) { 820 throw new RuntimeException("Don't call setOnClickListener for an AdapterView. " 821 + "You probably want setOnItemClickListener instead"); 822 } 823 824 /** 825 * Override to prevent freezing of any views created by the adapter. 826 */ 827 @Override dispatchSaveInstanceState(SparseArray<Parcelable> container)828 protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) { 829 dispatchFreezeSelfOnly(container); 830 } 831 832 /** 833 * Override to prevent thawing of any views created by the adapter. 834 */ 835 @Override dispatchRestoreInstanceState(SparseArray<Parcelable> container)836 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) { 837 dispatchThawSelfOnly(container); 838 } 839 840 class AdapterDataSetObserver extends DataSetObserver { 841 842 private Parcelable mInstanceState = null; 843 844 @Override onChanged()845 public void onChanged() { 846 mDataChanged = true; 847 mOldItemCount = mItemCount; 848 mItemCount = getAdapter().getCount(); 849 850 // Detect the case where a cursor that was previously invalidated has 851 // been repopulated with new data. 852 if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null 853 && mOldItemCount == 0 && mItemCount > 0) { 854 AdapterView.this.onRestoreInstanceState(mInstanceState); 855 mInstanceState = null; 856 } else { 857 rememberSyncState(); 858 } 859 checkFocus(); 860 requestLayout(); 861 } 862 863 @Override onInvalidated()864 public void onInvalidated() { 865 mDataChanged = true; 866 867 if (AdapterView.this.getAdapter().hasStableIds()) { 868 // Remember the current state for the case where our hosting activity is being 869 // stopped and later restarted 870 mInstanceState = AdapterView.this.onSaveInstanceState(); 871 } 872 873 // Data is invalid so we should reset our state 874 mOldItemCount = mItemCount; 875 mItemCount = 0; 876 mSelectedPosition = INVALID_POSITION; 877 mSelectedRowId = INVALID_ROW_ID; 878 mNextSelectedPosition = INVALID_POSITION; 879 mNextSelectedRowId = INVALID_ROW_ID; 880 mNeedSync = false; 881 882 checkFocus(); 883 requestLayout(); 884 } 885 clearSavedState()886 public void clearSavedState() { 887 mInstanceState = null; 888 } 889 } 890 891 @Override onDetachedFromWindow()892 protected void onDetachedFromWindow() { 893 super.onDetachedFromWindow(); 894 removeCallbacks(mSelectionNotifier); 895 } 896 897 private class SelectionNotifier implements Runnable { run()898 public void run() { 899 mPendingSelectionNotifier = null; 900 901 if (mDataChanged && getViewRootImpl() != null 902 && getViewRootImpl().isLayoutRequested()) { 903 // Data has changed between when this SelectionNotifier was 904 // posted and now. Postpone the notification until the next 905 // layout is complete and we run checkSelectionChanged(). 906 if (getAdapter() != null) { 907 mPendingSelectionNotifier = this; 908 } 909 } else { 910 dispatchOnItemSelected(); 911 } 912 } 913 } 914 915 @UnsupportedAppUsage selectionChanged()916 void selectionChanged() { 917 // We're about to post or run the selection notifier, so we don't need 918 // a pending notifier. 919 mPendingSelectionNotifier = null; 920 921 if (mOnItemSelectedListener != null 922 || AccessibilityManager.getInstance(mContext).isEnabled()) { 923 if (mInLayout || mBlockLayoutRequests) { 924 // If we are in a layout traversal, defer notification 925 // by posting. This ensures that the view tree is 926 // in a consistent state and is able to accommodate 927 // new layout or invalidate requests. 928 if (mSelectionNotifier == null) { 929 mSelectionNotifier = new SelectionNotifier(); 930 } else { 931 removeCallbacks(mSelectionNotifier); 932 } 933 post(mSelectionNotifier); 934 } else { 935 dispatchOnItemSelected(); 936 } 937 } 938 // Always notify AutoFillManager - it will return right away if autofill is disabled. 939 final AutofillManager afm = mContext.getSystemService(AutofillManager.class); 940 if (afm != null) { 941 afm.notifyValueChanged(this); 942 } 943 } 944 dispatchOnItemSelected()945 private void dispatchOnItemSelected() { 946 fireOnSelected(); 947 performAccessibilityActionsOnSelected(); 948 } 949 fireOnSelected()950 private void fireOnSelected() { 951 if (mOnItemSelectedListener == null) { 952 return; 953 } 954 final int selection = getSelectedItemPosition(); 955 if (selection >= 0) { 956 View v = getSelectedView(); 957 mOnItemSelectedListener.onItemSelected(this, v, selection, 958 getAdapter().getItemId(selection)); 959 } else { 960 mOnItemSelectedListener.onNothingSelected(this); 961 } 962 } 963 performAccessibilityActionsOnSelected()964 private void performAccessibilityActionsOnSelected() { 965 if (!AccessibilityManager.getInstance(mContext).isEnabled()) { 966 return; 967 } 968 final int position = getSelectedItemPosition(); 969 if (position >= 0) { 970 // we fire selection events here not in View 971 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); 972 } 973 } 974 975 /** @hide */ 976 @Override dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event)977 public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) { 978 View selectedView = getSelectedView(); 979 if (selectedView != null && selectedView.getVisibility() == VISIBLE 980 && selectedView.dispatchPopulateAccessibilityEvent(event)) { 981 return true; 982 } 983 return false; 984 } 985 986 /** @hide */ 987 @Override onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event)988 public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) { 989 if (super.onRequestSendAccessibilityEventInternal(child, event)) { 990 // Add a record for ourselves as well. 991 AccessibilityEvent record = AccessibilityEvent.obtain(); 992 onInitializeAccessibilityEvent(record); 993 // Populate with the text of the requesting child. 994 child.dispatchPopulateAccessibilityEvent(record); 995 event.appendRecord(record); 996 return true; 997 } 998 return false; 999 } 1000 1001 @Override getAccessibilityClassName()1002 public CharSequence getAccessibilityClassName() { 1003 return AdapterView.class.getName(); 1004 } 1005 1006 /** @hide */ 1007 @Override onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)1008 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { 1009 super.onInitializeAccessibilityNodeInfoInternal(info); 1010 info.setScrollable(isScrollableForAccessibility()); 1011 View selectedView = getSelectedView(); 1012 if (selectedView != null) { 1013 info.setEnabled(selectedView.isEnabled()); 1014 } 1015 } 1016 1017 /** @hide */ 1018 @Override onInitializeAccessibilityEventInternal(AccessibilityEvent event)1019 public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { 1020 super.onInitializeAccessibilityEventInternal(event); 1021 event.setScrollable(isScrollableForAccessibility()); 1022 View selectedView = getSelectedView(); 1023 if (selectedView != null) { 1024 event.setEnabled(selectedView.isEnabled()); 1025 } 1026 event.setCurrentItemIndex(getSelectedItemPosition()); 1027 event.setFromIndex(getFirstVisiblePosition()); 1028 event.setToIndex(getLastVisiblePosition()); 1029 event.setItemCount(getCount()); 1030 } 1031 isScrollableForAccessibility()1032 private boolean isScrollableForAccessibility() { 1033 T adapter = getAdapter(); 1034 if (adapter != null) { 1035 final int itemCount = adapter.getCount(); 1036 return itemCount > 0 1037 && (getFirstVisiblePosition() > 0 || getLastVisiblePosition() < itemCount - 1); 1038 } 1039 return false; 1040 } 1041 1042 @Override canAnimate()1043 protected boolean canAnimate() { 1044 return super.canAnimate() && mItemCount > 0; 1045 } 1046 handleDataChanged()1047 void handleDataChanged() { 1048 final int count = mItemCount; 1049 boolean found = false; 1050 1051 if (count > 0) { 1052 1053 int newPos; 1054 1055 // Find the row we are supposed to sync to 1056 if (mNeedSync) { 1057 // Update this first, since setNextSelectedPositionInt inspects 1058 // it 1059 mNeedSync = false; 1060 1061 // See if we can find a position in the new data with the same 1062 // id as the old selection 1063 newPos = findSyncPosition(); 1064 if (newPos >= 0) { 1065 // Verify that new selection is selectable 1066 int selectablePos = lookForSelectablePosition(newPos, true); 1067 if (selectablePos == newPos) { 1068 // Same row id is selected 1069 setNextSelectedPositionInt(newPos); 1070 found = true; 1071 } 1072 } 1073 } 1074 if (!found) { 1075 // Try to use the same position if we can't find matching data 1076 newPos = getSelectedItemPosition(); 1077 1078 // Pin position to the available range 1079 if (newPos >= count) { 1080 newPos = count - 1; 1081 } 1082 if (newPos < 0) { 1083 newPos = 0; 1084 } 1085 1086 // Make sure we select something selectable -- first look down 1087 int selectablePos = lookForSelectablePosition(newPos, true); 1088 if (selectablePos < 0) { 1089 // Looking down didn't work -- try looking up 1090 selectablePos = lookForSelectablePosition(newPos, false); 1091 } 1092 if (selectablePos >= 0) { 1093 setNextSelectedPositionInt(selectablePos); 1094 checkSelectionChanged(); 1095 found = true; 1096 } 1097 } 1098 } 1099 if (!found) { 1100 // Nothing is selected 1101 mSelectedPosition = INVALID_POSITION; 1102 mSelectedRowId = INVALID_ROW_ID; 1103 mNextSelectedPosition = INVALID_POSITION; 1104 mNextSelectedRowId = INVALID_ROW_ID; 1105 mNeedSync = false; 1106 checkSelectionChanged(); 1107 } 1108 1109 notifySubtreeAccessibilityStateChangedIfNeeded(); 1110 } 1111 1112 /** 1113 * Called after layout to determine whether the selection position needs to 1114 * be updated. Also used to fire any pending selection events. 1115 */ checkSelectionChanged()1116 void checkSelectionChanged() { 1117 if ((mSelectedPosition != mOldSelectedPosition) || (mSelectedRowId != mOldSelectedRowId)) { 1118 selectionChanged(); 1119 mOldSelectedPosition = mSelectedPosition; 1120 mOldSelectedRowId = mSelectedRowId; 1121 } 1122 1123 // If we have a pending selection notification -- and we won't if we 1124 // just fired one in selectionChanged() -- run it now. 1125 if (mPendingSelectionNotifier != null) { 1126 mPendingSelectionNotifier.run(); 1127 } 1128 } 1129 1130 /** 1131 * Searches the adapter for a position matching mSyncRowId. The search starts at mSyncPosition 1132 * and then alternates between moving up and moving down until 1) we find the right position, or 1133 * 2) we run out of time, or 3) we have looked at every position 1134 * 1135 * @return Position of the row that matches mSyncRowId, or {@link #INVALID_POSITION} if it can't 1136 * be found 1137 */ findSyncPosition()1138 int findSyncPosition() { 1139 int count = mItemCount; 1140 1141 if (count == 0) { 1142 return INVALID_POSITION; 1143 } 1144 1145 long idToMatch = mSyncRowId; 1146 int seed = mSyncPosition; 1147 1148 // If there isn't a selection don't hunt for it 1149 if (idToMatch == INVALID_ROW_ID) { 1150 return INVALID_POSITION; 1151 } 1152 1153 // Pin seed to reasonable values 1154 seed = Math.max(0, seed); 1155 seed = Math.min(count - 1, seed); 1156 1157 long endTime = SystemClock.uptimeMillis() + SYNC_MAX_DURATION_MILLIS; 1158 1159 long rowId; 1160 1161 // first position scanned so far 1162 int first = seed; 1163 1164 // last position scanned so far 1165 int last = seed; 1166 1167 // True if we should move down on the next iteration 1168 boolean next = false; 1169 1170 // True when we have looked at the first item in the data 1171 boolean hitFirst; 1172 1173 // True when we have looked at the last item in the data 1174 boolean hitLast; 1175 1176 // Get the item ID locally (instead of getItemIdAtPosition), so 1177 // we need the adapter 1178 T adapter = getAdapter(); 1179 if (adapter == null) { 1180 return INVALID_POSITION; 1181 } 1182 1183 while (SystemClock.uptimeMillis() <= endTime) { 1184 rowId = adapter.getItemId(seed); 1185 if (rowId == idToMatch) { 1186 // Found it! 1187 return seed; 1188 } 1189 1190 hitLast = last == count - 1; 1191 hitFirst = first == 0; 1192 1193 if (hitLast && hitFirst) { 1194 // Looked at everything 1195 break; 1196 } 1197 1198 if (hitFirst || (next && !hitLast)) { 1199 // Either we hit the top, or we are trying to move down 1200 last++; 1201 seed = last; 1202 // Try going up next time 1203 next = false; 1204 } else if (hitLast || (!next && !hitFirst)) { 1205 // Either we hit the bottom, or we are trying to move up 1206 first--; 1207 seed = first; 1208 // Try going down next time 1209 next = true; 1210 } 1211 1212 } 1213 1214 return INVALID_POSITION; 1215 } 1216 1217 /** 1218 * Find a position that can be selected (i.e., is not a separator). 1219 * 1220 * @param position The starting position to look at. 1221 * @param lookDown Whether to look down for other positions. 1222 * @return The next selectable position starting at position and then searching either up or 1223 * down. Returns {@link #INVALID_POSITION} if nothing can be found. 1224 */ lookForSelectablePosition(int position, boolean lookDown)1225 int lookForSelectablePosition(int position, boolean lookDown) { 1226 return position; 1227 } 1228 1229 /** 1230 * Utility to keep mSelectedPosition and mSelectedRowId in sync 1231 * @param position Our current position 1232 */ 1233 @UnsupportedAppUsage setSelectedPositionInt(int position)1234 void setSelectedPositionInt(int position) { 1235 mSelectedPosition = position; 1236 mSelectedRowId = getItemIdAtPosition(position); 1237 } 1238 1239 /** 1240 * Utility to keep mNextSelectedPosition and mNextSelectedRowId in sync 1241 * @param position Intended value for mSelectedPosition the next time we go 1242 * through layout 1243 */ 1244 @UnsupportedAppUsage setNextSelectedPositionInt(int position)1245 void setNextSelectedPositionInt(int position) { 1246 mNextSelectedPosition = position; 1247 mNextSelectedRowId = getItemIdAtPosition(position); 1248 // If we are trying to sync to the selection, update that too 1249 if (mNeedSync && mSyncMode == SYNC_SELECTED_POSITION && position >= 0) { 1250 mSyncPosition = position; 1251 mSyncRowId = mNextSelectedRowId; 1252 } 1253 } 1254 1255 /** 1256 * Remember enough information to restore the screen state when the data has 1257 * changed. 1258 * 1259 */ rememberSyncState()1260 void rememberSyncState() { 1261 if (getChildCount() > 0) { 1262 mNeedSync = true; 1263 mSyncHeight = mLayoutHeight; 1264 if (mSelectedPosition >= 0) { 1265 // Sync the selection state 1266 View v = getChildAt(mSelectedPosition - mFirstPosition); 1267 mSyncRowId = mNextSelectedRowId; 1268 mSyncPosition = mNextSelectedPosition; 1269 if (v != null) { 1270 mSpecificTop = v.getTop(); 1271 } 1272 mSyncMode = SYNC_SELECTED_POSITION; 1273 } else { 1274 // Sync the based on the offset of the first view 1275 View v = getChildAt(0); 1276 T adapter = getAdapter(); 1277 if (mFirstPosition >= 0 && mFirstPosition < adapter.getCount()) { 1278 mSyncRowId = adapter.getItemId(mFirstPosition); 1279 } else { 1280 mSyncRowId = NO_ID; 1281 } 1282 mSyncPosition = mFirstPosition; 1283 if (v != null) { 1284 mSpecificTop = v.getTop(); 1285 } 1286 mSyncMode = SYNC_FIRST_POSITION; 1287 } 1288 } 1289 } 1290 1291 /** @hide */ 1292 @Override encodeProperties(@onNull ViewHierarchyEncoder encoder)1293 protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) { 1294 super.encodeProperties(encoder); 1295 1296 encoder.addProperty("scrolling:firstPosition", mFirstPosition); 1297 encoder.addProperty("list:nextSelectedPosition", mNextSelectedPosition); 1298 encoder.addProperty("list:nextSelectedRowId", mNextSelectedRowId); 1299 encoder.addProperty("list:selectedPosition", mSelectedPosition); 1300 encoder.addProperty("list:itemCount", mItemCount); 1301 } 1302 1303 /** 1304 * {@inheritDoc} 1305 * 1306 * <p>It also sets the autofill options in the structure; when overridden, it should set it as 1307 * well, either explicitly by calling {@link ViewStructure#setAutofillOptions(CharSequence[])} 1308 * or implicitly by calling {@code super.onProvideAutofillStructure(structure, flags)}. 1309 */ 1310 @Override onProvideAutofillStructure(ViewStructure structure, int flags)1311 public void onProvideAutofillStructure(ViewStructure structure, int flags) { 1312 super.onProvideAutofillStructure(structure, flags); 1313 } 1314 1315 /** @hide */ 1316 @Override onProvideStructure(@onNull ViewStructure structure, @ViewStructureType int viewFor, int flags)1317 protected void onProvideStructure(@NonNull ViewStructure structure, 1318 @ViewStructureType int viewFor, int flags) { 1319 super.onProvideStructure(structure, viewFor, flags); 1320 1321 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) { 1322 final Adapter adapter = getAdapter(); 1323 if (adapter == null) return; 1324 1325 final CharSequence[] options = adapter.getAutofillOptions(); 1326 if (options != null) { 1327 structure.setAutofillOptions(options); 1328 } 1329 } 1330 } 1331 } 1332