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