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