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