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