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