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