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      * Get the position within the adapter's data set for the view, where view is a an adapter item
    604      * or a descendant of an adapter item.
    605      *
    606      * @param view an adapter item, or a descendant of an adapter item. This must be visible in this
    607      *        AdapterView at the time of the call.
    608      * @return the position within the adapter's data set of the view, or {@link #INVALID_POSITION}
    609      *         if the view does not correspond to a list item (or it is not currently visible).
    610      */
    611     public int getPositionForView(View view) {
    612         View listItem = view;
    613         try {
    614             View v;
    615             while ((v = (View) listItem.getParent()) != null && !v.equals(this)) {
    616                 listItem = v;
    617             }
    618         } catch (ClassCastException e) {
    619             // We made it up to the window without find this list view
    620             return INVALID_POSITION;
    621         }
    622 
    623         if (listItem != null) {
    624             // Search the children for the list item
    625             final int childCount = getChildCount();
    626             for (int i = 0; i < childCount; i++) {
    627                 if (getChildAt(i).equals(listItem)) {
    628                     return mFirstPosition + i;
    629                 }
    630             }
    631         }
    632 
    633         // Child not found!
    634         return INVALID_POSITION;
    635     }
    636 
    637     /**
    638      * Returns the position within the adapter's data set for the first item
    639      * displayed on screen.
    640      *
    641      * @return The position within the adapter's data set
    642      */
    643     public int getFirstVisiblePosition() {
    644         return mFirstPosition;
    645     }
    646 
    647     /**
    648      * Returns the position within the adapter's data set for the last item
    649      * displayed on screen.
    650      *
    651      * @return The position within the adapter's data set
    652      */
    653     public int getLastVisiblePosition() {
    654         return mFirstPosition + getChildCount() - 1;
    655     }
    656 
    657     /**
    658      * Sets the currently selected item. To support accessibility subclasses that
    659      * override this method must invoke the overriden super method first.
    660      *
    661      * @param position Index (starting at 0) of the data item to be selected.
    662      */
    663     public abstract void setSelection(int position);
    664 
    665     /**
    666      * Sets the view to show if the adapter is empty
    667      */
    668     @android.view.RemotableViewMethod
    669     public void setEmptyView(View emptyView) {
    670         mEmptyView = emptyView;
    671 
    672         // If not explicitly specified this view is important for accessibility.
    673         if (emptyView != null
    674                 && emptyView.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
    675             emptyView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
    676         }
    677 
    678         final T adapter = getAdapter();
    679         final boolean empty = ((adapter == null) || adapter.isEmpty());
    680         updateEmptyStatus(empty);
    681     }
    682 
    683     /**
    684      * When the current adapter is empty, the AdapterView can display a special view
    685      * called the empty view. The empty view is used to provide feedback to the user
    686      * that no data is available in this AdapterView.
    687      *
    688      * @return The view to show if the adapter is empty.
    689      */
    690     public View getEmptyView() {
    691         return mEmptyView;
    692     }
    693 
    694     /**
    695      * Indicates whether this view is in filter mode. Filter mode can for instance
    696      * be enabled by a user when typing on the keyboard.
    697      *
    698      * @return True if the view is in filter mode, false otherwise.
    699      */
    700     boolean isInFilterMode() {
    701         return false;
    702     }
    703 
    704     @Override
    705     public void setFocusable(boolean focusable) {
    706         final T adapter = getAdapter();
    707         final boolean empty = adapter == null || adapter.getCount() == 0;
    708 
    709         mDesiredFocusableState = focusable;
    710         if (!focusable) {
    711             mDesiredFocusableInTouchModeState = false;
    712         }
    713 
    714         super.setFocusable(focusable && (!empty || isInFilterMode()));
    715     }
    716 
    717     @Override
    718     public void setFocusableInTouchMode(boolean focusable) {
    719         final T adapter = getAdapter();
    720         final boolean empty = adapter == null || adapter.getCount() == 0;
    721 
    722         mDesiredFocusableInTouchModeState = focusable;
    723         if (focusable) {
    724             mDesiredFocusableState = true;
    725         }
    726 
    727         super.setFocusableInTouchMode(focusable && (!empty || isInFilterMode()));
    728     }
    729 
    730     void checkFocus() {
    731         final T adapter = getAdapter();
    732         final boolean empty = adapter == null || adapter.getCount() == 0;
    733         final boolean focusable = !empty || isInFilterMode();
    734         // The order in which we set focusable in touch mode/focusable may matter
    735         // for the client, see View.setFocusableInTouchMode() comments for more
    736         // details
    737         super.setFocusableInTouchMode(focusable && mDesiredFocusableInTouchModeState);
    738         super.setFocusable(focusable && mDesiredFocusableState);
    739         if (mEmptyView != null) {
    740             updateEmptyStatus((adapter == null) || adapter.isEmpty());
    741         }
    742     }
    743 
    744     /**
    745      * Update the status of the list based on the empty parameter.  If empty is true and
    746      * we have an empty view, display it.  In all the other cases, make sure that the listview
    747      * is VISIBLE and that the empty view is GONE (if it's not null).
    748      */
    749     private void updateEmptyStatus(boolean empty) {
    750         if (isInFilterMode()) {
    751             empty = false;
    752         }
    753 
    754         if (empty) {
    755             if (mEmptyView != null) {
    756                 mEmptyView.setVisibility(View.VISIBLE);
    757                 setVisibility(View.GONE);
    758             } else {
    759                 // If the caller just removed our empty view, make sure the list view is visible
    760                 setVisibility(View.VISIBLE);
    761             }
    762 
    763             // We are now GONE, so pending layouts will not be dispatched.
    764             // Force one here to make sure that the state of the list matches
    765             // the state of the adapter.
    766             if (mDataChanged) {
    767                 this.onLayout(false, mLeft, mTop, mRight, mBottom);
    768             }
    769         } else {
    770             if (mEmptyView != null) mEmptyView.setVisibility(View.GONE);
    771             setVisibility(View.VISIBLE);
    772         }
    773     }
    774 
    775     /**
    776      * Gets the data associated with the specified position in the list.
    777      *
    778      * @param position Which data to get
    779      * @return The data associated with the specified position in the list
    780      */
    781     public Object getItemAtPosition(int position) {
    782         T adapter = getAdapter();
    783         return (adapter == null || position < 0) ? null : adapter.getItem(position);
    784     }
    785 
    786     public long getItemIdAtPosition(int position) {
    787         T adapter = getAdapter();
    788         return (adapter == null || position < 0) ? INVALID_ROW_ID : adapter.getItemId(position);
    789     }
    790 
    791     @Override
    792     public void setOnClickListener(OnClickListener l) {
    793         throw new RuntimeException("Don't call setOnClickListener for an AdapterView. "
    794                 + "You probably want setOnItemClickListener instead");
    795     }
    796 
    797     /**
    798      * Override to prevent freezing of any views created by the adapter.
    799      */
    800     @Override
    801     protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
    802         dispatchFreezeSelfOnly(container);
    803     }
    804 
    805     /**
    806      * Override to prevent thawing of any views created by the adapter.
    807      */
    808     @Override
    809     protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
    810         dispatchThawSelfOnly(container);
    811     }
    812 
    813     class AdapterDataSetObserver extends DataSetObserver {
    814 
    815         private Parcelable mInstanceState = null;
    816 
    817         @Override
    818         public void onChanged() {
    819             mDataChanged = true;
    820             mOldItemCount = mItemCount;
    821             mItemCount = getAdapter().getCount();
    822 
    823             // Detect the case where a cursor that was previously invalidated has
    824             // been repopulated with new data.
    825             if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
    826                     && mOldItemCount == 0 && mItemCount > 0) {
    827                 AdapterView.this.onRestoreInstanceState(mInstanceState);
    828                 mInstanceState = null;
    829             } else {
    830                 rememberSyncState();
    831             }
    832             checkFocus();
    833             requestLayout();
    834         }
    835 
    836         @Override
    837         public void onInvalidated() {
    838             mDataChanged = true;
    839 
    840             if (AdapterView.this.getAdapter().hasStableIds()) {
    841                 // Remember the current state for the case where our hosting activity is being
    842                 // stopped and later restarted
    843                 mInstanceState = AdapterView.this.onSaveInstanceState();
    844             }
    845 
    846             // Data is invalid so we should reset our state
    847             mOldItemCount = mItemCount;
    848             mItemCount = 0;
    849             mSelectedPosition = INVALID_POSITION;
    850             mSelectedRowId = INVALID_ROW_ID;
    851             mNextSelectedPosition = INVALID_POSITION;
    852             mNextSelectedRowId = INVALID_ROW_ID;
    853             mNeedSync = false;
    854 
    855             checkFocus();
    856             requestLayout();
    857         }
    858 
    859         public void clearSavedState() {
    860             mInstanceState = null;
    861         }
    862     }
    863 
    864     @Override
    865     protected void onDetachedFromWindow() {
    866         super.onDetachedFromWindow();
    867         removeCallbacks(mSelectionNotifier);
    868     }
    869 
    870     private class SelectionNotifier implements Runnable {
    871         public void run() {
    872             mPendingSelectionNotifier = null;
    873 
    874             if (mDataChanged && getViewRootImpl() != null
    875                     && getViewRootImpl().isLayoutRequested()) {
    876                 // Data has changed between when this SelectionNotifier was
    877                 // posted and now. Postpone the notification until the next
    878                 // layout is complete and we run checkSelectionChanged().
    879                 if (getAdapter() != null) {
    880                     mPendingSelectionNotifier = this;
    881                 }
    882             } else {
    883                 dispatchOnItemSelected();
    884             }
    885         }
    886     }
    887 
    888     void selectionChanged() {
    889         // We're about to post or run the selection notifier, so we don't need
    890         // a pending notifier.
    891         mPendingSelectionNotifier = null;
    892 
    893         if (mOnItemSelectedListener != null
    894                 || AccessibilityManager.getInstance(mContext).isEnabled()) {
    895             if (mInLayout || mBlockLayoutRequests) {
    896                 // If we are in a layout traversal, defer notification
    897                 // by posting. This ensures that the view tree is
    898                 // in a consistent state and is able to accommodate
    899                 // new layout or invalidate requests.
    900                 if (mSelectionNotifier == null) {
    901                     mSelectionNotifier = new SelectionNotifier();
    902                 } else {
    903                     removeCallbacks(mSelectionNotifier);
    904                 }
    905                 post(mSelectionNotifier);
    906             } else {
    907                 dispatchOnItemSelected();
    908             }
    909         }
    910     }
    911 
    912     private void dispatchOnItemSelected() {
    913         fireOnSelected();
    914         performAccessibilityActionsOnSelected();
    915     }
    916 
    917     private void fireOnSelected() {
    918         if (mOnItemSelectedListener == null) {
    919             return;
    920         }
    921         final int selection = getSelectedItemPosition();
    922         if (selection >= 0) {
    923             View v = getSelectedView();
    924             mOnItemSelectedListener.onItemSelected(this, v, selection,
    925                     getAdapter().getItemId(selection));
    926         } else {
    927             mOnItemSelectedListener.onNothingSelected(this);
    928         }
    929     }
    930 
    931     private void performAccessibilityActionsOnSelected() {
    932         if (!AccessibilityManager.getInstance(mContext).isEnabled()) {
    933             return;
    934         }
    935         final int position = getSelectedItemPosition();
    936         if (position >= 0) {
    937             // we fire selection events here not in View
    938             sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
    939         }
    940     }
    941 
    942     /** @hide */
    943     @Override
    944     public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
    945         View selectedView = getSelectedView();
    946         if (selectedView != null && selectedView.getVisibility() == VISIBLE
    947                 && selectedView.dispatchPopulateAccessibilityEvent(event)) {
    948             return true;
    949         }
    950         return false;
    951     }
    952 
    953     /** @hide */
    954     @Override
    955     public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) {
    956         if (super.onRequestSendAccessibilityEventInternal(child, event)) {
    957             // Add a record for ourselves as well.
    958             AccessibilityEvent record = AccessibilityEvent.obtain();
    959             onInitializeAccessibilityEvent(record);
    960             // Populate with the text of the requesting child.
    961             child.dispatchPopulateAccessibilityEvent(record);
    962             event.appendRecord(record);
    963             return true;
    964         }
    965         return false;
    966     }
    967 
    968     @Override
    969     public CharSequence getAccessibilityClassName() {
    970         return AdapterView.class.getName();
    971     }
    972 
    973     /** @hide */
    974     @Override
    975     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
    976         super.onInitializeAccessibilityNodeInfoInternal(info);
    977         info.setScrollable(isScrollableForAccessibility());
    978         View selectedView = getSelectedView();
    979         if (selectedView != null) {
    980             info.setEnabled(selectedView.isEnabled());
    981         }
    982     }
    983 
    984     /** @hide */
    985     @Override
    986     public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
    987         super.onInitializeAccessibilityEventInternal(event);
    988         event.setScrollable(isScrollableForAccessibility());
    989         View selectedView = getSelectedView();
    990         if (selectedView != null) {
    991             event.setEnabled(selectedView.isEnabled());
    992         }
    993         event.setCurrentItemIndex(getSelectedItemPosition());
    994         event.setFromIndex(getFirstVisiblePosition());
    995         event.setToIndex(getLastVisiblePosition());
    996         event.setItemCount(getCount());
    997     }
    998 
    999     private boolean isScrollableForAccessibility() {
   1000         T adapter = getAdapter();
   1001         if (adapter != null) {
   1002             final int itemCount = adapter.getCount();
   1003             return itemCount > 0
   1004                 && (getFirstVisiblePosition() > 0 || getLastVisiblePosition() < itemCount - 1);
   1005         }
   1006         return false;
   1007     }
   1008 
   1009     @Override
   1010     protected boolean canAnimate() {
   1011         return super.canAnimate() && mItemCount > 0;
   1012     }
   1013 
   1014     void handleDataChanged() {
   1015         final int count = mItemCount;
   1016         boolean found = false;
   1017 
   1018         if (count > 0) {
   1019 
   1020             int newPos;
   1021 
   1022             // Find the row we are supposed to sync to
   1023             if (mNeedSync) {
   1024                 // Update this first, since setNextSelectedPositionInt inspects
   1025                 // it
   1026                 mNeedSync = false;
   1027 
   1028                 // See if we can find a position in the new data with the same
   1029                 // id as the old selection
   1030                 newPos = findSyncPosition();
   1031                 if (newPos >= 0) {
   1032                     // Verify that new selection is selectable
   1033                     int selectablePos = lookForSelectablePosition(newPos, true);
   1034                     if (selectablePos == newPos) {
   1035                         // Same row id is selected
   1036                         setNextSelectedPositionInt(newPos);
   1037                         found = true;
   1038                     }
   1039                 }
   1040             }
   1041             if (!found) {
   1042                 // Try to use the same position if we can't find matching data
   1043                 newPos = getSelectedItemPosition();
   1044 
   1045                 // Pin position to the available range
   1046                 if (newPos >= count) {
   1047                     newPos = count - 1;
   1048                 }
   1049                 if (newPos < 0) {
   1050                     newPos = 0;
   1051                 }
   1052 
   1053                 // Make sure we select something selectable -- first look down
   1054                 int selectablePos = lookForSelectablePosition(newPos, true);
   1055                 if (selectablePos < 0) {
   1056                     // Looking down didn't work -- try looking up
   1057                     selectablePos = lookForSelectablePosition(newPos, false);
   1058                 }
   1059                 if (selectablePos >= 0) {
   1060                     setNextSelectedPositionInt(selectablePos);
   1061                     checkSelectionChanged();
   1062                     found = true;
   1063                 }
   1064             }
   1065         }
   1066         if (!found) {
   1067             // Nothing is selected
   1068             mSelectedPosition = INVALID_POSITION;
   1069             mSelectedRowId = INVALID_ROW_ID;
   1070             mNextSelectedPosition = INVALID_POSITION;
   1071             mNextSelectedRowId = INVALID_ROW_ID;
   1072             mNeedSync = false;
   1073             checkSelectionChanged();
   1074         }
   1075 
   1076         notifySubtreeAccessibilityStateChangedIfNeeded();
   1077     }
   1078 
   1079     /**
   1080      * Called after layout to determine whether the selection position needs to
   1081      * be updated. Also used to fire any pending selection events.
   1082      */
   1083     void checkSelectionChanged() {
   1084         if ((mSelectedPosition != mOldSelectedPosition) || (mSelectedRowId != mOldSelectedRowId)) {
   1085             selectionChanged();
   1086             mOldSelectedPosition = mSelectedPosition;
   1087             mOldSelectedRowId = mSelectedRowId;
   1088         }
   1089 
   1090         // If we have a pending selection notification -- and we won't if we
   1091         // just fired one in selectionChanged() -- run it now.
   1092         if (mPendingSelectionNotifier != null) {
   1093             mPendingSelectionNotifier.run();
   1094         }
   1095     }
   1096 
   1097     /**
   1098      * Searches the adapter for a position matching mSyncRowId. The search starts at mSyncPosition
   1099      * and then alternates between moving up and moving down until 1) we find the right position, or
   1100      * 2) we run out of time, or 3) we have looked at every position
   1101      *
   1102      * @return Position of the row that matches mSyncRowId, or {@link #INVALID_POSITION} if it can't
   1103      *         be found
   1104      */
   1105     int findSyncPosition() {
   1106         int count = mItemCount;
   1107 
   1108         if (count == 0) {
   1109             return INVALID_POSITION;
   1110         }
   1111 
   1112         long idToMatch = mSyncRowId;
   1113         int seed = mSyncPosition;
   1114 
   1115         // If there isn't a selection don't hunt for it
   1116         if (idToMatch == INVALID_ROW_ID) {
   1117             return INVALID_POSITION;
   1118         }
   1119 
   1120         // Pin seed to reasonable values
   1121         seed = Math.max(0, seed);
   1122         seed = Math.min(count - 1, seed);
   1123 
   1124         long endTime = SystemClock.uptimeMillis() + SYNC_MAX_DURATION_MILLIS;
   1125 
   1126         long rowId;
   1127 
   1128         // first position scanned so far
   1129         int first = seed;
   1130 
   1131         // last position scanned so far
   1132         int last = seed;
   1133 
   1134         // True if we should move down on the next iteration
   1135         boolean next = false;
   1136 
   1137         // True when we have looked at the first item in the data
   1138         boolean hitFirst;
   1139 
   1140         // True when we have looked at the last item in the data
   1141         boolean hitLast;
   1142 
   1143         // Get the item ID locally (instead of getItemIdAtPosition), so
   1144         // we need the adapter
   1145         T adapter = getAdapter();
   1146         if (adapter == null) {
   1147             return INVALID_POSITION;
   1148         }
   1149 
   1150         while (SystemClock.uptimeMillis() <= endTime) {
   1151             rowId = adapter.getItemId(seed);
   1152             if (rowId == idToMatch) {
   1153                 // Found it!
   1154                 return seed;
   1155             }
   1156 
   1157             hitLast = last == count - 1;
   1158             hitFirst = first == 0;
   1159 
   1160             if (hitLast && hitFirst) {
   1161                 // Looked at everything
   1162                 break;
   1163             }
   1164 
   1165             if (hitFirst || (next && !hitLast)) {
   1166                 // Either we hit the top, or we are trying to move down
   1167                 last++;
   1168                 seed = last;
   1169                 // Try going up next time
   1170                 next = false;
   1171             } else if (hitLast || (!next && !hitFirst)) {
   1172                 // Either we hit the bottom, or we are trying to move up
   1173                 first--;
   1174                 seed = first;
   1175                 // Try going down next time
   1176                 next = true;
   1177             }
   1178 
   1179         }
   1180 
   1181         return INVALID_POSITION;
   1182     }
   1183 
   1184     /**
   1185      * Find a position that can be selected (i.e., is not a separator).
   1186      *
   1187      * @param position The starting position to look at.
   1188      * @param lookDown Whether to look down for other positions.
   1189      * @return The next selectable position starting at position and then searching either up or
   1190      *         down. Returns {@link #INVALID_POSITION} if nothing can be found.
   1191      */
   1192     int lookForSelectablePosition(int position, boolean lookDown) {
   1193         return position;
   1194     }
   1195 
   1196     /**
   1197      * Utility to keep mSelectedPosition and mSelectedRowId in sync
   1198      * @param position Our current position
   1199      */
   1200     void setSelectedPositionInt(int position) {
   1201         mSelectedPosition = position;
   1202         mSelectedRowId = getItemIdAtPosition(position);
   1203     }
   1204 
   1205     /**
   1206      * Utility to keep mNextSelectedPosition and mNextSelectedRowId in sync
   1207      * @param position Intended value for mSelectedPosition the next time we go
   1208      * through layout
   1209      */
   1210     void setNextSelectedPositionInt(int position) {
   1211         mNextSelectedPosition = position;
   1212         mNextSelectedRowId = getItemIdAtPosition(position);
   1213         // If we are trying to sync to the selection, update that too
   1214         if (mNeedSync && mSyncMode == SYNC_SELECTED_POSITION && position >= 0) {
   1215             mSyncPosition = position;
   1216             mSyncRowId = mNextSelectedRowId;
   1217         }
   1218     }
   1219 
   1220     /**
   1221      * Remember enough information to restore the screen state when the data has
   1222      * changed.
   1223      *
   1224      */
   1225     void rememberSyncState() {
   1226         if (getChildCount() > 0) {
   1227             mNeedSync = true;
   1228             mSyncHeight = mLayoutHeight;
   1229             if (mSelectedPosition >= 0) {
   1230                 // Sync the selection state
   1231                 View v = getChildAt(mSelectedPosition - mFirstPosition);
   1232                 mSyncRowId = mNextSelectedRowId;
   1233                 mSyncPosition = mNextSelectedPosition;
   1234                 if (v != null) {
   1235                     mSpecificTop = v.getTop();
   1236                 }
   1237                 mSyncMode = SYNC_SELECTED_POSITION;
   1238             } else {
   1239                 // Sync the based on the offset of the first view
   1240                 View v = getChildAt(0);
   1241                 T adapter = getAdapter();
   1242                 if (mFirstPosition >= 0 && mFirstPosition < adapter.getCount()) {
   1243                     mSyncRowId = adapter.getItemId(mFirstPosition);
   1244                 } else {
   1245                     mSyncRowId = NO_ID;
   1246                 }
   1247                 mSyncPosition = mFirstPosition;
   1248                 if (v != null) {
   1249                     mSpecificTop = v.getTop();
   1250                 }
   1251                 mSyncMode = SYNC_FIRST_POSITION;
   1252             }
   1253         }
   1254     }
   1255 
   1256     /** @hide */
   1257     @Override
   1258     protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
   1259         super.encodeProperties(encoder);
   1260 
   1261         encoder.addProperty("scrolling:firstPosition", mFirstPosition);
   1262         encoder.addProperty("list:nextSelectedPosition", mNextSelectedPosition);
   1263         encoder.addProperty("list:nextSelectedRowId", mNextSelectedRowId);
   1264         encoder.addProperty("list:selectedPosition", mSelectedPosition);
   1265         encoder.addProperty("list:itemCount", mItemCount);
   1266     }
   1267 }
   1268