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
17package android.widget;
18
19import android.annotation.NonNull;
20import android.annotation.Nullable;
21import android.content.Context;
22import android.database.DataSetObserver;
23import android.os.Parcelable;
24import android.os.SystemClock;
25import android.util.AttributeSet;
26import android.util.SparseArray;
27import android.view.ContextMenu;
28import android.view.ContextMenu.ContextMenuInfo;
29import android.view.SoundEffectConstants;
30import android.view.View;
31import android.view.ViewDebug;
32import android.view.ViewGroup;
33import android.view.ViewHierarchyEncoder;
34import android.view.accessibility.AccessibilityEvent;
35import android.view.accessibility.AccessibilityManager;
36import 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 */
51public 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
233    public AdapterView(Context context) {
234        this(context, null);
235    }
236
237    public AdapterView(Context context, AttributeSet attrs) {
238        this(context, attrs, 0);
239    }
240
241    public AdapterView(Context context, AttributeSet attrs, int defStyleAttr) {
242        this(context, attrs, defStyleAttr, 0);
243    }
244
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         */
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     */
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
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     */
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         */
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     */
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     */
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         */
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         */
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     */
404    public void setOnItemSelectedListener(@Nullable OnItemSelectedListener listener) {
405        mOnItemSelectedListener = listener;
406    }
407
408    @Nullable
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
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     */
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     */
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
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
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
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
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
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
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
544    public void removeAllViews() {
545        throw new UnsupportedOperationException("removeAllViews() is not supported in AdapterView");
546    }
547
548    @Override
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
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
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     */
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     */
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
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     */
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     */
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     */
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     */
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
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     */
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     */
707    boolean isInFilterMode() {
708        return false;
709    }
710
711    @Override
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
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
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     */
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     */
788    public Object getItemAtPosition(int position) {
789        T adapter = getAdapter();
790        return (adapter == null || position < 0) ? null : adapter.getItem(position);
791    }
792
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
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
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
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
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
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
866        public void clearSavedState() {
867            mInstanceState = null;
868        }
869    }
870
871    @Override
872    protected void onDetachedFromWindow() {
873        super.onDetachedFromWindow();
874        removeCallbacks(mSelectionNotifier);
875    }
876
877    private class SelectionNotifier implements Runnable {
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
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
919    private void dispatchOnItemSelected() {
920        fireOnSelected();
921        performAccessibilityActionsOnSelected();
922    }
923
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
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
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
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
976    public CharSequence getAccessibilityClassName() {
977        return AdapterView.class.getName();
978    }
979
980    /** @hide */
981    @Override
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
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
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
1017    protected boolean canAnimate() {
1018        return super.canAnimate() && mItemCount > 0;
1019    }
1020
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     */
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     */
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     */
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     */
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     */
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     */
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
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