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