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