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