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