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
     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
    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
    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
    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             checkSelectionChanged();
    812 
    813             checkFocus();
    814             requestLayout();
    815         }
    816 
    817         public void clearSavedState() {
    818             mInstanceState = null;
    819         }
    820     }
    821 
    822     private class SelectionNotifier extends Handler implements Runnable {
    823         public void run() {
    824             if (mDataChanged) {
    825                 // Data has changed between when this SelectionNotifier
    826                 // was posted and now. We need to wait until the AdapterView
    827                 // has been synched to the new data.
    828                 post(this);
    829             } else {
    830                 fireOnSelected();
    831             }
    832         }
    833     }
    834 
    835     void selectionChanged() {
    836         if (mOnItemSelectedListener != null) {
    837             if (mInLayout || mBlockLayoutRequests) {
    838                 // If we are in a layout traversal, defer notification
    839                 // by posting. This ensures that the view tree is
    840                 // in a consistent state and is able to accomodate
    841                 // new layout or invalidate requests.
    842                 if (mSelectionNotifier == null) {
    843                     mSelectionNotifier = new SelectionNotifier();
    844                 }
    845                 mSelectionNotifier.post(mSelectionNotifier);
    846             } else {
    847                 fireOnSelected();
    848             }
    849         }
    850 
    851         // we fire selection events here not in View
    852         if (mSelectedPosition != ListView.INVALID_POSITION && isShown() && !isInTouchMode()) {
    853             sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
    854         }
    855     }
    856 
    857     private void fireOnSelected() {
    858         if (mOnItemSelectedListener == null)
    859             return;
    860 
    861         int selection = this.getSelectedItemPosition();
    862         if (selection >= 0) {
    863             View v = getSelectedView();
    864             mOnItemSelectedListener.onItemSelected(this, v, selection,
    865                     getAdapter().getItemId(selection));
    866         } else {
    867             mOnItemSelectedListener.onNothingSelected(this);
    868         }
    869     }
    870 
    871     @Override
    872     public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
    873         boolean populated = false;
    874         // This is an exceptional case which occurs when a window gets the
    875         // focus and sends a focus event via its focused child to announce
    876         // current focus/selection. AdapterView fires selection but not focus
    877         // events so we change the event type here.
    878         if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED) {
    879             event.setEventType(AccessibilityEvent.TYPE_VIEW_SELECTED);
    880         }
    881 
    882         // we send selection events only from AdapterView to avoid
    883         // generation of such event for each child
    884         View selectedView = getSelectedView();
    885         if (selectedView != null) {
    886             populated = selectedView.dispatchPopulateAccessibilityEvent(event);
    887         }
    888 
    889         if (!populated) {
    890             if (selectedView != null) {
    891                 event.setEnabled(selectedView.isEnabled());
    892             }
    893             event.setItemCount(getCount());
    894             event.setCurrentItemIndex(getSelectedItemPosition());
    895         }
    896 
    897         return populated;
    898     }
    899 
    900     @Override
    901     protected boolean canAnimate() {
    902         return super.canAnimate() && mItemCount > 0;
    903     }
    904 
    905     void handleDataChanged() {
    906         final int count = mItemCount;
    907         boolean found = false;
    908 
    909         if (count > 0) {
    910 
    911             int newPos;
    912 
    913             // Find the row we are supposed to sync to
    914             if (mNeedSync) {
    915                 // Update this first, since setNextSelectedPositionInt inspects
    916                 // it
    917                 mNeedSync = false;
    918 
    919                 // See if we can find a position in the new data with the same
    920                 // id as the old selection
    921                 newPos = findSyncPosition();
    922                 if (newPos >= 0) {
    923                     // Verify that new selection is selectable
    924                     int selectablePos = lookForSelectablePosition(newPos, true);
    925                     if (selectablePos == newPos) {
    926                         // Same row id is selected
    927                         setNextSelectedPositionInt(newPos);
    928                         found = true;
    929                     }
    930                 }
    931             }
    932             if (!found) {
    933                 // Try to use the same position if we can't find matching data
    934                 newPos = getSelectedItemPosition();
    935 
    936                 // Pin position to the available range
    937                 if (newPos >= count) {
    938                     newPos = count - 1;
    939                 }
    940                 if (newPos < 0) {
    941                     newPos = 0;
    942                 }
    943 
    944                 // Make sure we select something selectable -- first look down
    945                 int selectablePos = lookForSelectablePosition(newPos, true);
    946                 if (selectablePos < 0) {
    947                     // Looking down didn't work -- try looking up
    948                     selectablePos = lookForSelectablePosition(newPos, false);
    949                 }
    950                 if (selectablePos >= 0) {
    951                     setNextSelectedPositionInt(selectablePos);
    952                     checkSelectionChanged();
    953                     found = true;
    954                 }
    955             }
    956         }
    957         if (!found) {
    958             // Nothing is selected
    959             mSelectedPosition = INVALID_POSITION;
    960             mSelectedRowId = INVALID_ROW_ID;
    961             mNextSelectedPosition = INVALID_POSITION;
    962             mNextSelectedRowId = INVALID_ROW_ID;
    963             mNeedSync = false;
    964             checkSelectionChanged();
    965         }
    966     }
    967 
    968     void checkSelectionChanged() {
    969         if ((mSelectedPosition != mOldSelectedPosition) || (mSelectedRowId != mOldSelectedRowId)) {
    970             selectionChanged();
    971             mOldSelectedPosition = mSelectedPosition;
    972             mOldSelectedRowId = mSelectedRowId;
    973         }
    974     }
    975 
    976     /**
    977      * Searches the adapter for a position matching mSyncRowId. The search starts at mSyncPosition
    978      * and then alternates between moving up and moving down until 1) we find the right position, or
    979      * 2) we run out of time, or 3) we have looked at every position
    980      *
    981      * @return Position of the row that matches mSyncRowId, or {@link #INVALID_POSITION} if it can't
    982      *         be found
    983      */
    984     int findSyncPosition() {
    985         int count = mItemCount;
    986 
    987         if (count == 0) {
    988             return INVALID_POSITION;
    989         }
    990 
    991         long idToMatch = mSyncRowId;
    992         int seed = mSyncPosition;
    993 
    994         // If there isn't a selection don't hunt for it
    995         if (idToMatch == INVALID_ROW_ID) {
    996             return INVALID_POSITION;
    997         }
    998 
    999         // Pin seed to reasonable values
   1000         seed = Math.max(0, seed);
   1001         seed = Math.min(count - 1, seed);
   1002 
   1003         long endTime = SystemClock.uptimeMillis() + SYNC_MAX_DURATION_MILLIS;
   1004 
   1005         long rowId;
   1006 
   1007         // first position scanned so far
   1008         int first = seed;
   1009 
   1010         // last position scanned so far
   1011         int last = seed;
   1012 
   1013         // True if we should move down on the next iteration
   1014         boolean next = false;
   1015 
   1016         // True when we have looked at the first item in the data
   1017         boolean hitFirst;
   1018 
   1019         // True when we have looked at the last item in the data
   1020         boolean hitLast;
   1021 
   1022         // Get the item ID locally (instead of getItemIdAtPosition), so
   1023         // we need the adapter
   1024         T adapter = getAdapter();
   1025         if (adapter == null) {
   1026             return INVALID_POSITION;
   1027         }
   1028 
   1029         while (SystemClock.uptimeMillis() <= endTime) {
   1030             rowId = adapter.getItemId(seed);
   1031             if (rowId == idToMatch) {
   1032                 // Found it!
   1033                 return seed;
   1034             }
   1035 
   1036             hitLast = last == count - 1;
   1037             hitFirst = first == 0;
   1038 
   1039             if (hitLast && hitFirst) {
   1040                 // Looked at everything
   1041                 break;
   1042             }
   1043 
   1044             if (hitFirst || (next && !hitLast)) {
   1045                 // Either we hit the top, or we are trying to move down
   1046                 last++;
   1047                 seed = last;
   1048                 // Try going up next time
   1049                 next = false;
   1050             } else if (hitLast || (!next && !hitFirst)) {
   1051                 // Either we hit the bottom, or we are trying to move up
   1052                 first--;
   1053                 seed = first;
   1054                 // Try going down next time
   1055                 next = true;
   1056             }
   1057 
   1058         }
   1059 
   1060         return INVALID_POSITION;
   1061     }
   1062 
   1063     /**
   1064      * Find a position that can be selected (i.e., is not a separator).
   1065      *
   1066      * @param position The starting position to look at.
   1067      * @param lookDown Whether to look down for other positions.
   1068      * @return The next selectable position starting at position and then searching either up or
   1069      *         down. Returns {@link #INVALID_POSITION} if nothing can be found.
   1070      */
   1071     int lookForSelectablePosition(int position, boolean lookDown) {
   1072         return position;
   1073     }
   1074 
   1075     /**
   1076      * Utility to keep mSelectedPosition and mSelectedRowId in sync
   1077      * @param position Our current position
   1078      */
   1079     void setSelectedPositionInt(int position) {
   1080         mSelectedPosition = position;
   1081         mSelectedRowId = getItemIdAtPosition(position);
   1082     }
   1083 
   1084     /**
   1085      * Utility to keep mNextSelectedPosition and mNextSelectedRowId in sync
   1086      * @param position Intended value for mSelectedPosition the next time we go
   1087      * through layout
   1088      */
   1089     void setNextSelectedPositionInt(int position) {
   1090         mNextSelectedPosition = position;
   1091         mNextSelectedRowId = getItemIdAtPosition(position);
   1092         // If we are trying to sync to the selection, update that too
   1093         if (mNeedSync && mSyncMode == SYNC_SELECTED_POSITION && position >= 0) {
   1094             mSyncPosition = position;
   1095             mSyncRowId = mNextSelectedRowId;
   1096         }
   1097     }
   1098 
   1099     /**
   1100      * Remember enough information to restore the screen state when the data has
   1101      * changed.
   1102      *
   1103      */
   1104     void rememberSyncState() {
   1105         if (getChildCount() > 0) {
   1106             mNeedSync = true;
   1107             mSyncHeight = mLayoutHeight;
   1108             if (mSelectedPosition >= 0) {
   1109                 // Sync the selection state
   1110                 View v = getChildAt(mSelectedPosition - mFirstPosition);
   1111                 mSyncRowId = mNextSelectedRowId;
   1112                 mSyncPosition = mNextSelectedPosition;
   1113                 if (v != null) {
   1114                     mSpecificTop = v.getTop();
   1115                 }
   1116                 mSyncMode = SYNC_SELECTED_POSITION;
   1117             } else {
   1118                 // Sync the based on the offset of the first view
   1119                 View v = getChildAt(0);
   1120                 T adapter = getAdapter();
   1121                 if (mFirstPosition >= 0 && mFirstPosition < adapter.getCount()) {
   1122                     mSyncRowId = adapter.getItemId(mFirstPosition);
   1123                 } else {
   1124                     mSyncRowId = NO_ID;
   1125                 }
   1126                 mSyncPosition = mFirstPosition;
   1127                 if (v != null) {
   1128                     mSpecificTop = v.getTop();
   1129                 }
   1130                 mSyncMode = SYNC_FIRST_POSITION;
   1131             }
   1132         }
   1133     }
   1134 }
   1135