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