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