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