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