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