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 com.android.internal.R;
     20 import com.android.internal.util.Predicate;
     21 import com.google.android.collect.Lists;
     22 
     23 import android.content.Context;
     24 import android.content.Intent;
     25 import android.content.res.TypedArray;
     26 import android.graphics.Canvas;
     27 import android.graphics.Paint;
     28 import android.graphics.PixelFormat;
     29 import android.graphics.Rect;
     30 import android.graphics.drawable.Drawable;
     31 import android.util.AttributeSet;
     32 import android.util.MathUtils;
     33 import android.util.SparseBooleanArray;
     34 import android.view.FocusFinder;
     35 import android.view.KeyEvent;
     36 import android.view.SoundEffectConstants;
     37 import android.view.View;
     38 import android.view.ViewDebug;
     39 import android.view.ViewGroup;
     40 import android.view.ViewParent;
     41 import android.view.ViewRootImpl;
     42 import android.view.accessibility.AccessibilityEvent;
     43 import android.view.accessibility.AccessibilityNodeInfo;
     44 import android.view.accessibility.AccessibilityNodeProvider;
     45 import android.widget.RemoteViews.RemoteView;
     46 
     47 import java.util.ArrayList;
     48 
     49 /*
     50  * Implementation Notes:
     51  *
     52  * Some terminology:
     53  *
     54  *     index    - index of the items that are currently visible
     55  *     position - index of the items in the cursor
     56  */
     57 
     58 
     59 /**
     60  * A view that shows items in a vertically scrolling list. The items
     61  * come from the {@link ListAdapter} associated with this view.
     62  *
     63  * <p>See the <a href="{@docRoot}guide/topics/ui/layout/listview.html">List View</a>
     64  * guide.</p>
     65  *
     66  * @attr ref android.R.styleable#ListView_entries
     67  * @attr ref android.R.styleable#ListView_divider
     68  * @attr ref android.R.styleable#ListView_dividerHeight
     69  * @attr ref android.R.styleable#ListView_headerDividersEnabled
     70  * @attr ref android.R.styleable#ListView_footerDividersEnabled
     71  */
     72 @RemoteView
     73 public class ListView extends AbsListView {
     74     /**
     75      * Used to indicate a no preference for a position type.
     76      */
     77     static final int NO_POSITION = -1;
     78 
     79     /**
     80      * When arrow scrolling, ListView will never scroll more than this factor
     81      * times the height of the list.
     82      */
     83     private static final float MAX_SCROLL_FACTOR = 0.33f;
     84 
     85     /**
     86      * When arrow scrolling, need a certain amount of pixels to preview next
     87      * items.  This is usually the fading edge, but if that is small enough,
     88      * we want to make sure we preview at least this many pixels.
     89      */
     90     private static final int MIN_SCROLL_PREVIEW_PIXELS = 2;
     91 
     92     /**
     93      * A class that represents a fixed view in a list, for example a header at the top
     94      * or a footer at the bottom.
     95      */
     96     public class FixedViewInfo {
     97         /** The view to add to the list */
     98         public View view;
     99         /** The data backing the view. This is returned from {@link ListAdapter#getItem(int)}. */
    100         public Object data;
    101         /** <code>true</code> if the fixed view should be selectable in the list */
    102         public boolean isSelectable;
    103     }
    104 
    105     private ArrayList<FixedViewInfo> mHeaderViewInfos = Lists.newArrayList();
    106     private ArrayList<FixedViewInfo> mFooterViewInfos = Lists.newArrayList();
    107 
    108     Drawable mDivider;
    109     int mDividerHeight;
    110 
    111     Drawable mOverScrollHeader;
    112     Drawable mOverScrollFooter;
    113 
    114     private boolean mIsCacheColorOpaque;
    115     private boolean mDividerIsOpaque;
    116 
    117     private boolean mHeaderDividersEnabled;
    118     private boolean mFooterDividersEnabled;
    119 
    120     private boolean mAreAllItemsSelectable = true;
    121 
    122     private boolean mItemsCanFocus = false;
    123 
    124     // used for temporary calculations.
    125     private final Rect mTempRect = new Rect();
    126     private Paint mDividerPaint;
    127 
    128     // the single allocated result per list view; kinda cheesey but avoids
    129     // allocating these thingies too often.
    130     private final ArrowScrollFocusResult mArrowScrollFocusResult = new ArrowScrollFocusResult();
    131 
    132     // Keeps focused children visible through resizes
    133     private FocusSelector mFocusSelector;
    134 
    135     public ListView(Context context) {
    136         this(context, null);
    137     }
    138 
    139     public ListView(Context context, AttributeSet attrs) {
    140         this(context, attrs, com.android.internal.R.attr.listViewStyle);
    141     }
    142 
    143     public ListView(Context context, AttributeSet attrs, int defStyle) {
    144         super(context, attrs, defStyle);
    145 
    146         TypedArray a = context.obtainStyledAttributes(attrs,
    147                 com.android.internal.R.styleable.ListView, defStyle, 0);
    148 
    149         CharSequence[] entries = a.getTextArray(
    150                 com.android.internal.R.styleable.ListView_entries);
    151         if (entries != null) {
    152             setAdapter(new ArrayAdapter<CharSequence>(context,
    153                     com.android.internal.R.layout.simple_list_item_1, entries));
    154         }
    155 
    156         final Drawable d = a.getDrawable(com.android.internal.R.styleable.ListView_divider);
    157         if (d != null) {
    158             // If a divider is specified use its intrinsic height for divider height
    159             setDivider(d);
    160         }
    161 
    162         final Drawable osHeader = a.getDrawable(
    163                 com.android.internal.R.styleable.ListView_overScrollHeader);
    164         if (osHeader != null) {
    165             setOverscrollHeader(osHeader);
    166         }
    167 
    168         final Drawable osFooter = a.getDrawable(
    169                 com.android.internal.R.styleable.ListView_overScrollFooter);
    170         if (osFooter != null) {
    171             setOverscrollFooter(osFooter);
    172         }
    173 
    174         // Use the height specified, zero being the default
    175         final int dividerHeight = a.getDimensionPixelSize(
    176                 com.android.internal.R.styleable.ListView_dividerHeight, 0);
    177         if (dividerHeight != 0) {
    178             setDividerHeight(dividerHeight);
    179         }
    180 
    181         mHeaderDividersEnabled = a.getBoolean(R.styleable.ListView_headerDividersEnabled, true);
    182         mFooterDividersEnabled = a.getBoolean(R.styleable.ListView_footerDividersEnabled, true);
    183 
    184         a.recycle();
    185     }
    186 
    187     /**
    188      * @return The maximum amount a list view will scroll in response to
    189      *   an arrow event.
    190      */
    191     public int getMaxScrollAmount() {
    192         return (int) (MAX_SCROLL_FACTOR * (mBottom - mTop));
    193     }
    194 
    195     /**
    196      * Make sure views are touching the top or bottom edge, as appropriate for
    197      * our gravity
    198      */
    199     private void adjustViewsUpOrDown() {
    200         final int childCount = getChildCount();
    201         int delta;
    202 
    203         if (childCount > 0) {
    204             View child;
    205 
    206             if (!mStackFromBottom) {
    207                 // Uh-oh -- we came up short. Slide all views up to make them
    208                 // align with the top
    209                 child = getChildAt(0);
    210                 delta = child.getTop() - mListPadding.top;
    211                 if (mFirstPosition != 0) {
    212                     // It's OK to have some space above the first item if it is
    213                     // part of the vertical spacing
    214                     delta -= mDividerHeight;
    215                 }
    216                 if (delta < 0) {
    217                     // We only are looking to see if we are too low, not too high
    218                     delta = 0;
    219                 }
    220             } else {
    221                 // we are too high, slide all views down to align with bottom
    222                 child = getChildAt(childCount - 1);
    223                 delta = child.getBottom() - (getHeight() - mListPadding.bottom);
    224 
    225                 if (mFirstPosition + childCount < mItemCount) {
    226                     // It's OK to have some space below the last item if it is
    227                     // part of the vertical spacing
    228                     delta += mDividerHeight;
    229                 }
    230 
    231                 if (delta > 0) {
    232                     delta = 0;
    233                 }
    234             }
    235 
    236             if (delta != 0) {
    237                 offsetChildrenTopAndBottom(-delta);
    238             }
    239         }
    240     }
    241 
    242     /**
    243      * Add a fixed view to appear at the top of the list. If addHeaderView is
    244      * called more than once, the views will appear in the order they were
    245      * added. Views added using this call can take focus if they want.
    246      * <p>
    247      * NOTE: Call this before calling setAdapter. This is so ListView can wrap
    248      * the supplied cursor with one that will also account for header and footer
    249      * views.
    250      *
    251      * @param v The view to add.
    252      * @param data Data to associate with this view
    253      * @param isSelectable whether the item is selectable
    254      */
    255     public void addHeaderView(View v, Object data, boolean isSelectable) {
    256 
    257         if (mAdapter != null && ! (mAdapter instanceof HeaderViewListAdapter)) {
    258             throw new IllegalStateException(
    259                     "Cannot add header view to list -- setAdapter has already been called.");
    260         }
    261 
    262         FixedViewInfo info = new FixedViewInfo();
    263         info.view = v;
    264         info.data = data;
    265         info.isSelectable = isSelectable;
    266         mHeaderViewInfos.add(info);
    267 
    268         // in the case of re-adding a header view, or adding one later on,
    269         // we need to notify the observer
    270         if (mAdapter != null && mDataSetObserver != null) {
    271             mDataSetObserver.onChanged();
    272         }
    273     }
    274 
    275     /**
    276      * Add a fixed view to appear at the top of the list. If addHeaderView is
    277      * called more than once, the views will appear in the order they were
    278      * added. Views added using this call can take focus if they want.
    279      * <p>
    280      * NOTE: Call this before calling setAdapter. This is so ListView can wrap
    281      * the supplied cursor with one that will also account for header and footer
    282      * views.
    283      *
    284      * @param v The view to add.
    285      */
    286     public void addHeaderView(View v) {
    287         addHeaderView(v, null, true);
    288     }
    289 
    290     @Override
    291     public int getHeaderViewsCount() {
    292         return mHeaderViewInfos.size();
    293     }
    294 
    295     /**
    296      * Removes a previously-added header view.
    297      *
    298      * @param v The view to remove
    299      * @return true if the view was removed, false if the view was not a header
    300      *         view
    301      */
    302     public boolean removeHeaderView(View v) {
    303         if (mHeaderViewInfos.size() > 0) {
    304             boolean result = false;
    305             if (mAdapter != null && ((HeaderViewListAdapter) mAdapter).removeHeader(v)) {
    306                 if (mDataSetObserver != null) {
    307                     mDataSetObserver.onChanged();
    308                 }
    309                 result = true;
    310             }
    311             removeFixedViewInfo(v, mHeaderViewInfos);
    312             return result;
    313         }
    314         return false;
    315     }
    316 
    317     private void removeFixedViewInfo(View v, ArrayList<FixedViewInfo> where) {
    318         int len = where.size();
    319         for (int i = 0; i < len; ++i) {
    320             FixedViewInfo info = where.get(i);
    321             if (info.view == v) {
    322                 where.remove(i);
    323                 break;
    324             }
    325         }
    326     }
    327 
    328     /**
    329      * Add a fixed view to appear at the bottom of the list. If addFooterView is
    330      * called more than once, the views will appear in the order they were
    331      * added. Views added using this call can take focus if they want.
    332      * <p>
    333      * NOTE: Call this before calling setAdapter. This is so ListView can wrap
    334      * the supplied cursor with one that will also account for header and footer
    335      * views.
    336      *
    337      * @param v The view to add.
    338      * @param data Data to associate with this view
    339      * @param isSelectable true if the footer view can be selected
    340      */
    341     public void addFooterView(View v, Object data, boolean isSelectable) {
    342 
    343         // NOTE: do not enforce the adapter being null here, since unlike in
    344         // addHeaderView, it was never enforced here, and so existing apps are
    345         // relying on being able to add a footer and then calling setAdapter to
    346         // force creation of the HeaderViewListAdapter wrapper
    347 
    348         FixedViewInfo info = new FixedViewInfo();
    349         info.view = v;
    350         info.data = data;
    351         info.isSelectable = isSelectable;
    352         mFooterViewInfos.add(info);
    353 
    354         // in the case of re-adding a footer view, or adding one later on,
    355         // we need to notify the observer
    356         if (mAdapter != null && mDataSetObserver != null) {
    357             mDataSetObserver.onChanged();
    358         }
    359     }
    360 
    361     /**
    362      * Add a fixed view to appear at the bottom of the list. If addFooterView is called more
    363      * than once, the views will appear in the order they were added. Views added using
    364      * this call can take focus if they want.
    365      * <p>NOTE: Call this before calling setAdapter. This is so ListView can wrap the supplied
    366      * cursor with one that will also account for header and footer views.
    367      *
    368      *
    369      * @param v The view to add.
    370      */
    371     public void addFooterView(View v) {
    372         addFooterView(v, null, true);
    373     }
    374 
    375     @Override
    376     public int getFooterViewsCount() {
    377         return mFooterViewInfos.size();
    378     }
    379 
    380     /**
    381      * Removes a previously-added footer view.
    382      *
    383      * @param v The view to remove
    384      * @return
    385      * true if the view was removed, false if the view was not a footer view
    386      */
    387     public boolean removeFooterView(View v) {
    388         if (mFooterViewInfos.size() > 0) {
    389             boolean result = false;
    390             if (mAdapter != null && ((HeaderViewListAdapter) mAdapter).removeFooter(v)) {
    391                 if (mDataSetObserver != null) {
    392                     mDataSetObserver.onChanged();
    393                 }
    394                 result = true;
    395             }
    396             removeFixedViewInfo(v, mFooterViewInfos);
    397             return result;
    398         }
    399         return false;
    400     }
    401 
    402     /**
    403      * Returns the adapter currently in use in this ListView. The returned adapter
    404      * might not be the same adapter passed to {@link #setAdapter(ListAdapter)} but
    405      * might be a {@link WrapperListAdapter}.
    406      *
    407      * @return The adapter currently used to display data in this ListView.
    408      *
    409      * @see #setAdapter(ListAdapter)
    410      */
    411     @Override
    412     public ListAdapter getAdapter() {
    413         return mAdapter;
    414     }
    415 
    416     /**
    417      * Sets up this AbsListView to use a remote views adapter which connects to a RemoteViewsService
    418      * through the specified intent.
    419      * @param intent the intent used to identify the RemoteViewsService for the adapter to connect to.
    420      */
    421     @android.view.RemotableViewMethod
    422     public void setRemoteViewsAdapter(Intent intent) {
    423         super.setRemoteViewsAdapter(intent);
    424     }
    425 
    426     /**
    427      * Sets the data behind this ListView.
    428      *
    429      * The adapter passed to this method may be wrapped by a {@link WrapperListAdapter},
    430      * depending on the ListView features currently in use. For instance, adding
    431      * headers and/or footers will cause the adapter to be wrapped.
    432      *
    433      * @param adapter The ListAdapter which is responsible for maintaining the
    434      *        data backing this list and for producing a view to represent an
    435      *        item in that data set.
    436      *
    437      * @see #getAdapter()
    438      */
    439     @Override
    440     public void setAdapter(ListAdapter adapter) {
    441         if (mAdapter != null && mDataSetObserver != null) {
    442             mAdapter.unregisterDataSetObserver(mDataSetObserver);
    443         }
    444 
    445         resetList();
    446         mRecycler.clear();
    447 
    448         if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
    449             mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
    450         } else {
    451             mAdapter = adapter;
    452         }
    453 
    454         mOldSelectedPosition = INVALID_POSITION;
    455         mOldSelectedRowId = INVALID_ROW_ID;
    456 
    457         // AbsListView#setAdapter will update choice mode states.
    458         super.setAdapter(adapter);
    459 
    460         if (mAdapter != null) {
    461             mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
    462             mOldItemCount = mItemCount;
    463             mItemCount = mAdapter.getCount();
    464             checkFocus();
    465 
    466             mDataSetObserver = new AdapterDataSetObserver();
    467             mAdapter.registerDataSetObserver(mDataSetObserver);
    468 
    469             mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
    470 
    471             int position;
    472             if (mStackFromBottom) {
    473                 position = lookForSelectablePosition(mItemCount - 1, false);
    474             } else {
    475                 position = lookForSelectablePosition(0, true);
    476             }
    477             setSelectedPositionInt(position);
    478             setNextSelectedPositionInt(position);
    479 
    480             if (mItemCount == 0) {
    481                 // Nothing selected
    482                 checkSelectionChanged();
    483             }
    484         } else {
    485             mAreAllItemsSelectable = true;
    486             checkFocus();
    487             // Nothing selected
    488             checkSelectionChanged();
    489         }
    490 
    491         requestLayout();
    492     }
    493 
    494 
    495     /**
    496      * The list is empty. Clear everything out.
    497      */
    498     @Override
    499     void resetList() {
    500         // The parent's resetList() will remove all views from the layout so we need to
    501         // cleanup the state of our footers and headers
    502         clearRecycledState(mHeaderViewInfos);
    503         clearRecycledState(mFooterViewInfos);
    504 
    505         super.resetList();
    506 
    507         mLayoutMode = LAYOUT_NORMAL;
    508     }
    509 
    510     private void clearRecycledState(ArrayList<FixedViewInfo> infos) {
    511         if (infos != null) {
    512             final int count = infos.size();
    513 
    514             for (int i = 0; i < count; i++) {
    515                 final View child = infos.get(i).view;
    516                 final LayoutParams p = (LayoutParams) child.getLayoutParams();
    517                 if (p != null) {
    518                     p.recycledHeaderFooter = false;
    519                 }
    520             }
    521         }
    522     }
    523 
    524     /**
    525      * @return Whether the list needs to show the top fading edge
    526      */
    527     private boolean showingTopFadingEdge() {
    528         final int listTop = mScrollY + mListPadding.top;
    529         return (mFirstPosition > 0) || (getChildAt(0).getTop() > listTop);
    530     }
    531 
    532     /**
    533      * @return Whether the list needs to show the bottom fading edge
    534      */
    535     private boolean showingBottomFadingEdge() {
    536         final int childCount = getChildCount();
    537         final int bottomOfBottomChild = getChildAt(childCount - 1).getBottom();
    538         final int lastVisiblePosition = mFirstPosition + childCount - 1;
    539 
    540         final int listBottom = mScrollY + getHeight() - mListPadding.bottom;
    541 
    542         return (lastVisiblePosition < mItemCount - 1)
    543                          || (bottomOfBottomChild < listBottom);
    544     }
    545 
    546 
    547     @Override
    548     public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) {
    549 
    550         int rectTopWithinChild = rect.top;
    551 
    552         // offset so rect is in coordinates of the this view
    553         rect.offset(child.getLeft(), child.getTop());
    554         rect.offset(-child.getScrollX(), -child.getScrollY());
    555 
    556         final int height = getHeight();
    557         int listUnfadedTop = getScrollY();
    558         int listUnfadedBottom = listUnfadedTop + height;
    559         final int fadingEdge = getVerticalFadingEdgeLength();
    560 
    561         if (showingTopFadingEdge()) {
    562             // leave room for top fading edge as long as rect isn't at very top
    563             if ((mSelectedPosition > 0) || (rectTopWithinChild > fadingEdge)) {
    564                 listUnfadedTop += fadingEdge;
    565             }
    566         }
    567 
    568         int childCount = getChildCount();
    569         int bottomOfBottomChild = getChildAt(childCount - 1).getBottom();
    570 
    571         if (showingBottomFadingEdge()) {
    572             // leave room for bottom fading edge as long as rect isn't at very bottom
    573             if ((mSelectedPosition < mItemCount - 1)
    574                     || (rect.bottom < (bottomOfBottomChild - fadingEdge))) {
    575                 listUnfadedBottom -= fadingEdge;
    576             }
    577         }
    578 
    579         int scrollYDelta = 0;
    580 
    581         if (rect.bottom > listUnfadedBottom && rect.top > listUnfadedTop) {
    582             // need to MOVE DOWN to get it in view: move down just enough so
    583             // that the entire rectangle is in view (or at least the first
    584             // screen size chunk).
    585 
    586             if (rect.height() > height) {
    587                 // just enough to get screen size chunk on
    588                 scrollYDelta += (rect.top - listUnfadedTop);
    589             } else {
    590                 // get entire rect at bottom of screen
    591                 scrollYDelta += (rect.bottom - listUnfadedBottom);
    592             }
    593 
    594             // make sure we aren't scrolling beyond the end of our children
    595             int distanceToBottom = bottomOfBottomChild - listUnfadedBottom;
    596             scrollYDelta = Math.min(scrollYDelta, distanceToBottom);
    597         } else if (rect.top < listUnfadedTop && rect.bottom < listUnfadedBottom) {
    598             // need to MOVE UP to get it in view: move up just enough so that
    599             // entire rectangle is in view (or at least the first screen
    600             // size chunk of it).
    601 
    602             if (rect.height() > height) {
    603                 // screen size chunk
    604                 scrollYDelta -= (listUnfadedBottom - rect.bottom);
    605             } else {
    606                 // entire rect at top
    607                 scrollYDelta -= (listUnfadedTop - rect.top);
    608             }
    609 
    610             // make sure we aren't scrolling any further than the top our children
    611             int top = getChildAt(0).getTop();
    612             int deltaToTop = top - listUnfadedTop;
    613             scrollYDelta = Math.max(scrollYDelta, deltaToTop);
    614         }
    615 
    616         final boolean scroll = scrollYDelta != 0;
    617         if (scroll) {
    618             scrollListItemsBy(-scrollYDelta);
    619             positionSelector(INVALID_POSITION, child);
    620             mSelectedTop = child.getTop();
    621             invalidate();
    622         }
    623         return scroll;
    624     }
    625 
    626     /**
    627      * {@inheritDoc}
    628      */
    629     @Override
    630     void fillGap(boolean down) {
    631         final int count = getChildCount();
    632         if (down) {
    633             int paddingTop = 0;
    634             if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
    635                 paddingTop = getListPaddingTop();
    636             }
    637             final int startOffset = count > 0 ? getChildAt(count - 1).getBottom() + mDividerHeight :
    638                     paddingTop;
    639             fillDown(mFirstPosition + count, startOffset);
    640             correctTooHigh(getChildCount());
    641         } else {
    642             int paddingBottom = 0;
    643             if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
    644                 paddingBottom = getListPaddingBottom();
    645             }
    646             final int startOffset = count > 0 ? getChildAt(0).getTop() - mDividerHeight :
    647                     getHeight() - paddingBottom;
    648             fillUp(mFirstPosition - 1, startOffset);
    649             correctTooLow(getChildCount());
    650         }
    651     }
    652 
    653     /**
    654      * Fills the list from pos down to the end of the list view.
    655      *
    656      * @param pos The first position to put in the list
    657      *
    658      * @param nextTop The location where the top of the item associated with pos
    659      *        should be drawn
    660      *
    661      * @return The view that is currently selected, if it happens to be in the
    662      *         range that we draw.
    663      */
    664     private View fillDown(int pos, int nextTop) {
    665         View selectedView = null;
    666 
    667         int end = (mBottom - mTop);
    668         if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
    669             end -= mListPadding.bottom;
    670         }
    671 
    672         while (nextTop < end && pos < mItemCount) {
    673             // is this the selected item?
    674             boolean selected = pos == mSelectedPosition;
    675             View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);
    676 
    677             nextTop = child.getBottom() + mDividerHeight;
    678             if (selected) {
    679                 selectedView = child;
    680             }
    681             pos++;
    682         }
    683 
    684         setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
    685         return selectedView;
    686     }
    687 
    688     /**
    689      * Fills the list from pos up to the top of the list view.
    690      *
    691      * @param pos The first position to put in the list
    692      *
    693      * @param nextBottom The location where the bottom of the item associated
    694      *        with pos should be drawn
    695      *
    696      * @return The view that is currently selected
    697      */
    698     private View fillUp(int pos, int nextBottom) {
    699         View selectedView = null;
    700 
    701         int end = 0;
    702         if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
    703             end = mListPadding.top;
    704         }
    705 
    706         while (nextBottom > end && pos >= 0) {
    707             // is this the selected item?
    708             boolean selected = pos == mSelectedPosition;
    709             View child = makeAndAddView(pos, nextBottom, false, mListPadding.left, selected);
    710             nextBottom = child.getTop() - mDividerHeight;
    711             if (selected) {
    712                 selectedView = child;
    713             }
    714             pos--;
    715         }
    716 
    717         mFirstPosition = pos + 1;
    718         setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
    719         return selectedView;
    720     }
    721 
    722     /**
    723      * Fills the list from top to bottom, starting with mFirstPosition
    724      *
    725      * @param nextTop The location where the top of the first item should be
    726      *        drawn
    727      *
    728      * @return The view that is currently selected
    729      */
    730     private View fillFromTop(int nextTop) {
    731         mFirstPosition = Math.min(mFirstPosition, mSelectedPosition);
    732         mFirstPosition = Math.min(mFirstPosition, mItemCount - 1);
    733         if (mFirstPosition < 0) {
    734             mFirstPosition = 0;
    735         }
    736         return fillDown(mFirstPosition, nextTop);
    737     }
    738 
    739 
    740     /**
    741      * Put mSelectedPosition in the middle of the screen and then build up and
    742      * down from there. This method forces mSelectedPosition to the center.
    743      *
    744      * @param childrenTop Top of the area in which children can be drawn, as
    745      *        measured in pixels
    746      * @param childrenBottom Bottom of the area in which children can be drawn,
    747      *        as measured in pixels
    748      * @return Currently selected view
    749      */
    750     private View fillFromMiddle(int childrenTop, int childrenBottom) {
    751         int height = childrenBottom - childrenTop;
    752 
    753         int position = reconcileSelectedPosition();
    754 
    755         View sel = makeAndAddView(position, childrenTop, true,
    756                 mListPadding.left, true);
    757         mFirstPosition = position;
    758 
    759         int selHeight = sel.getMeasuredHeight();
    760         if (selHeight <= height) {
    761             sel.offsetTopAndBottom((height - selHeight) / 2);
    762         }
    763 
    764         fillAboveAndBelow(sel, position);
    765 
    766         if (!mStackFromBottom) {
    767             correctTooHigh(getChildCount());
    768         } else {
    769             correctTooLow(getChildCount());
    770         }
    771 
    772         return sel;
    773     }
    774 
    775     /**
    776      * Once the selected view as been placed, fill up the visible area above and
    777      * below it.
    778      *
    779      * @param sel The selected view
    780      * @param position The position corresponding to sel
    781      */
    782     private void fillAboveAndBelow(View sel, int position) {
    783         final int dividerHeight = mDividerHeight;
    784         if (!mStackFromBottom) {
    785             fillUp(position - 1, sel.getTop() - dividerHeight);
    786             adjustViewsUpOrDown();
    787             fillDown(position + 1, sel.getBottom() + dividerHeight);
    788         } else {
    789             fillDown(position + 1, sel.getBottom() + dividerHeight);
    790             adjustViewsUpOrDown();
    791             fillUp(position - 1, sel.getTop() - dividerHeight);
    792         }
    793     }
    794 
    795 
    796     /**
    797      * Fills the grid based on positioning the new selection at a specific
    798      * location. The selection may be moved so that it does not intersect the
    799      * faded edges. The grid is then filled upwards and downwards from there.
    800      *
    801      * @param selectedTop Where the selected item should be
    802      * @param childrenTop Where to start drawing children
    803      * @param childrenBottom Last pixel where children can be drawn
    804      * @return The view that currently has selection
    805      */
    806     private View fillFromSelection(int selectedTop, int childrenTop, int childrenBottom) {
    807         int fadingEdgeLength = getVerticalFadingEdgeLength();
    808         final int selectedPosition = mSelectedPosition;
    809 
    810         View sel;
    811 
    812         final int topSelectionPixel = getTopSelectionPixel(childrenTop, fadingEdgeLength,
    813                 selectedPosition);
    814         final int bottomSelectionPixel = getBottomSelectionPixel(childrenBottom, fadingEdgeLength,
    815                 selectedPosition);
    816 
    817         sel = makeAndAddView(selectedPosition, selectedTop, true, mListPadding.left, true);
    818 
    819 
    820         // Some of the newly selected item extends below the bottom of the list
    821         if (sel.getBottom() > bottomSelectionPixel) {
    822             // Find space available above the selection into which we can scroll
    823             // upwards
    824             final int spaceAbove = sel.getTop() - topSelectionPixel;
    825 
    826             // Find space required to bring the bottom of the selected item
    827             // fully into view
    828             final int spaceBelow = sel.getBottom() - bottomSelectionPixel;
    829             final int offset = Math.min(spaceAbove, spaceBelow);
    830 
    831             // Now offset the selected item to get it into view
    832             sel.offsetTopAndBottom(-offset);
    833         } else if (sel.getTop() < topSelectionPixel) {
    834             // Find space required to bring the top of the selected item fully
    835             // into view
    836             final int spaceAbove = topSelectionPixel - sel.getTop();
    837 
    838             // Find space available below the selection into which we can scroll
    839             // downwards
    840             final int spaceBelow = bottomSelectionPixel - sel.getBottom();
    841             final int offset = Math.min(spaceAbove, spaceBelow);
    842 
    843             // Offset the selected item to get it into view
    844             sel.offsetTopAndBottom(offset);
    845         }
    846 
    847         // Fill in views above and below
    848         fillAboveAndBelow(sel, selectedPosition);
    849 
    850         if (!mStackFromBottom) {
    851             correctTooHigh(getChildCount());
    852         } else {
    853             correctTooLow(getChildCount());
    854         }
    855 
    856         return sel;
    857     }
    858 
    859     /**
    860      * Calculate the bottom-most pixel we can draw the selection into
    861      *
    862      * @param childrenBottom Bottom pixel were children can be drawn
    863      * @param fadingEdgeLength Length of the fading edge in pixels, if present
    864      * @param selectedPosition The position that will be selected
    865      * @return The bottom-most pixel we can draw the selection into
    866      */
    867     private int getBottomSelectionPixel(int childrenBottom, int fadingEdgeLength,
    868             int selectedPosition) {
    869         int bottomSelectionPixel = childrenBottom;
    870         if (selectedPosition != mItemCount - 1) {
    871             bottomSelectionPixel -= fadingEdgeLength;
    872         }
    873         return bottomSelectionPixel;
    874     }
    875 
    876     /**
    877      * Calculate the top-most pixel we can draw the selection into
    878      *
    879      * @param childrenTop Top pixel were children can be drawn
    880      * @param fadingEdgeLength Length of the fading edge in pixels, if present
    881      * @param selectedPosition The position that will be selected
    882      * @return The top-most pixel we can draw the selection into
    883      */
    884     private int getTopSelectionPixel(int childrenTop, int fadingEdgeLength, int selectedPosition) {
    885         // first pixel we can draw the selection into
    886         int topSelectionPixel = childrenTop;
    887         if (selectedPosition > 0) {
    888             topSelectionPixel += fadingEdgeLength;
    889         }
    890         return topSelectionPixel;
    891     }
    892 
    893     /**
    894      * Smoothly scroll to the specified adapter position. The view will
    895      * scroll such that the indicated position is displayed.
    896      * @param position Scroll to this adapter position.
    897      */
    898     @android.view.RemotableViewMethod
    899     public void smoothScrollToPosition(int position) {
    900         super.smoothScrollToPosition(position);
    901     }
    902 
    903     /**
    904      * Smoothly scroll to the specified adapter position offset. The view will
    905      * scroll such that the indicated position is displayed.
    906      * @param offset The amount to offset from the adapter position to scroll to.
    907      */
    908     @android.view.RemotableViewMethod
    909     public void smoothScrollByOffset(int offset) {
    910         super.smoothScrollByOffset(offset);
    911     }
    912 
    913     /**
    914      * Fills the list based on positioning the new selection relative to the old
    915      * selection. The new selection will be placed at, above, or below the
    916      * location of the new selection depending on how the selection is moving.
    917      * The selection will then be pinned to the visible part of the screen,
    918      * excluding the edges that are faded. The list is then filled upwards and
    919      * downwards from there.
    920      *
    921      * @param oldSel The old selected view. Useful for trying to put the new
    922      *        selection in the same place
    923      * @param newSel The view that is to become selected. Useful for trying to
    924      *        put the new selection in the same place
    925      * @param delta Which way we are moving
    926      * @param childrenTop Where to start drawing children
    927      * @param childrenBottom Last pixel where children can be drawn
    928      * @return The view that currently has selection
    929      */
    930     private View moveSelection(View oldSel, View newSel, int delta, int childrenTop,
    931             int childrenBottom) {
    932         int fadingEdgeLength = getVerticalFadingEdgeLength();
    933         final int selectedPosition = mSelectedPosition;
    934 
    935         View sel;
    936 
    937         final int topSelectionPixel = getTopSelectionPixel(childrenTop, fadingEdgeLength,
    938                 selectedPosition);
    939         final int bottomSelectionPixel = getBottomSelectionPixel(childrenTop, fadingEdgeLength,
    940                 selectedPosition);
    941 
    942         if (delta > 0) {
    943             /*
    944              * Case 1: Scrolling down.
    945              */
    946 
    947             /*
    948              *     Before           After
    949              *    |       |        |       |
    950              *    +-------+        +-------+
    951              *    |   A   |        |   A   |
    952              *    |   1   |   =>   +-------+
    953              *    +-------+        |   B   |
    954              *    |   B   |        |   2   |
    955              *    +-------+        +-------+
    956              *    |       |        |       |
    957              *
    958              *    Try to keep the top of the previously selected item where it was.
    959              *    oldSel = A
    960              *    sel = B
    961              */
    962 
    963             // Put oldSel (A) where it belongs
    964             oldSel = makeAndAddView(selectedPosition - 1, oldSel.getTop(), true,
    965                     mListPadding.left, false);
    966 
    967             final int dividerHeight = mDividerHeight;
    968 
    969             // Now put the new selection (B) below that
    970             sel = makeAndAddView(selectedPosition, oldSel.getBottom() + dividerHeight, true,
    971                     mListPadding.left, true);
    972 
    973             // Some of the newly selected item extends below the bottom of the list
    974             if (sel.getBottom() > bottomSelectionPixel) {
    975 
    976                 // Find space available above the selection into which we can scroll upwards
    977                 int spaceAbove = sel.getTop() - topSelectionPixel;
    978 
    979                 // Find space required to bring the bottom of the selected item fully into view
    980                 int spaceBelow = sel.getBottom() - bottomSelectionPixel;
    981 
    982                 // Don't scroll more than half the height of the list
    983                 int halfVerticalSpace = (childrenBottom - childrenTop) / 2;
    984                 int offset = Math.min(spaceAbove, spaceBelow);
    985                 offset = Math.min(offset, halfVerticalSpace);
    986 
    987                 // We placed oldSel, so offset that item
    988                 oldSel.offsetTopAndBottom(-offset);
    989                 // Now offset the selected item to get it into view
    990                 sel.offsetTopAndBottom(-offset);
    991             }
    992 
    993             // Fill in views above and below
    994             if (!mStackFromBottom) {
    995                 fillUp(mSelectedPosition - 2, sel.getTop() - dividerHeight);
    996                 adjustViewsUpOrDown();
    997                 fillDown(mSelectedPosition + 1, sel.getBottom() + dividerHeight);
    998             } else {
    999                 fillDown(mSelectedPosition + 1, sel.getBottom() + dividerHeight);
   1000                 adjustViewsUpOrDown();
   1001                 fillUp(mSelectedPosition - 2, sel.getTop() - dividerHeight);
   1002             }
   1003         } else if (delta < 0) {
   1004             /*
   1005              * Case 2: Scrolling up.
   1006              */
   1007 
   1008             /*
   1009              *     Before           After
   1010              *    |       |        |       |
   1011              *    +-------+        +-------+
   1012              *    |   A   |        |   A   |
   1013              *    +-------+   =>   |   1   |
   1014              *    |   B   |        +-------+
   1015              *    |   2   |        |   B   |
   1016              *    +-------+        +-------+
   1017              *    |       |        |       |
   1018              *
   1019              *    Try to keep the top of the item about to become selected where it was.
   1020              *    newSel = A
   1021              *    olSel = B
   1022              */
   1023 
   1024             if (newSel != null) {
   1025                 // Try to position the top of newSel (A) where it was before it was selected
   1026                 sel = makeAndAddView(selectedPosition, newSel.getTop(), true, mListPadding.left,
   1027                         true);
   1028             } else {
   1029                 // If (A) was not on screen and so did not have a view, position
   1030                 // it above the oldSel (B)
   1031                 sel = makeAndAddView(selectedPosition, oldSel.getTop(), false, mListPadding.left,
   1032                         true);
   1033             }
   1034 
   1035             // Some of the newly selected item extends above the top of the list
   1036             if (sel.getTop() < topSelectionPixel) {
   1037                 // Find space required to bring the top of the selected item fully into view
   1038                 int spaceAbove = topSelectionPixel - sel.getTop();
   1039 
   1040                // Find space available below the selection into which we can scroll downwards
   1041                 int spaceBelow = bottomSelectionPixel - sel.getBottom();
   1042 
   1043                 // Don't scroll more than half the height of the list
   1044                 int halfVerticalSpace = (childrenBottom - childrenTop) / 2;
   1045                 int offset = Math.min(spaceAbove, spaceBelow);
   1046                 offset = Math.min(offset, halfVerticalSpace);
   1047 
   1048                 // Offset the selected item to get it into view
   1049                 sel.offsetTopAndBottom(offset);
   1050             }
   1051 
   1052             // Fill in views above and below
   1053             fillAboveAndBelow(sel, selectedPosition);
   1054         } else {
   1055 
   1056             int oldTop = oldSel.getTop();
   1057 
   1058             /*
   1059              * Case 3: Staying still
   1060              */
   1061             sel = makeAndAddView(selectedPosition, oldTop, true, mListPadding.left, true);
   1062 
   1063             // We're staying still...
   1064             if (oldTop < childrenTop) {
   1065                 // ... but the top of the old selection was off screen.
   1066                 // (This can happen if the data changes size out from under us)
   1067                 int newBottom = sel.getBottom();
   1068                 if (newBottom < childrenTop + 20) {
   1069                     // Not enough visible -- bring it onscreen
   1070                     sel.offsetTopAndBottom(childrenTop - sel.getTop());
   1071                 }
   1072             }
   1073 
   1074             // Fill in views above and below
   1075             fillAboveAndBelow(sel, selectedPosition);
   1076         }
   1077 
   1078         return sel;
   1079     }
   1080 
   1081     private class FocusSelector implements Runnable {
   1082         private int mPosition;
   1083         private int mPositionTop;
   1084 
   1085         public FocusSelector setup(int position, int top) {
   1086             mPosition = position;
   1087             mPositionTop = top;
   1088             return this;
   1089         }
   1090 
   1091         public void run() {
   1092             setSelectionFromTop(mPosition, mPositionTop);
   1093         }
   1094     }
   1095 
   1096     @Override
   1097     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
   1098         if (getChildCount() > 0) {
   1099             View focusedChild = getFocusedChild();
   1100             if (focusedChild != null) {
   1101                 final int childPosition = mFirstPosition + indexOfChild(focusedChild);
   1102                 final int childBottom = focusedChild.getBottom();
   1103                 final int offset = Math.max(0, childBottom - (h - mPaddingTop));
   1104                 final int top = focusedChild.getTop() - offset;
   1105                 if (mFocusSelector == null) {
   1106                     mFocusSelector = new FocusSelector();
   1107                 }
   1108                 post(mFocusSelector.setup(childPosition, top));
   1109             }
   1110         }
   1111         super.onSizeChanged(w, h, oldw, oldh);
   1112     }
   1113 
   1114     @Override
   1115     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
   1116         // Sets up mListPadding
   1117         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
   1118 
   1119         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
   1120         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
   1121         int widthSize = MeasureSpec.getSize(widthMeasureSpec);
   1122         int heightSize = MeasureSpec.getSize(heightMeasureSpec);
   1123 
   1124         int childWidth = 0;
   1125         int childHeight = 0;
   1126         int childState = 0;
   1127 
   1128         mItemCount = mAdapter == null ? 0 : mAdapter.getCount();
   1129         if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED ||
   1130                 heightMode == MeasureSpec.UNSPECIFIED)) {
   1131             final View child = obtainView(0, mIsScrap);
   1132 
   1133             measureScrapChild(child, 0, widthMeasureSpec);
   1134 
   1135             childWidth = child.getMeasuredWidth();
   1136             childHeight = child.getMeasuredHeight();
   1137             childState = combineMeasuredStates(childState, child.getMeasuredState());
   1138 
   1139             if (recycleOnMeasure() && mRecycler.shouldRecycleViewType(
   1140                     ((LayoutParams) child.getLayoutParams()).viewType)) {
   1141                 mRecycler.addScrapView(child, -1);
   1142             }
   1143         }
   1144 
   1145         if (widthMode == MeasureSpec.UNSPECIFIED) {
   1146             widthSize = mListPadding.left + mListPadding.right + childWidth +
   1147                     getVerticalScrollbarWidth();
   1148         } else {
   1149             widthSize |= (childState&MEASURED_STATE_MASK);
   1150         }
   1151 
   1152         if (heightMode == MeasureSpec.UNSPECIFIED) {
   1153             heightSize = mListPadding.top + mListPadding.bottom + childHeight +
   1154                     getVerticalFadingEdgeLength() * 2;
   1155         }
   1156 
   1157         if (heightMode == MeasureSpec.AT_MOST) {
   1158             // TODO: after first layout we should maybe start at the first visible position, not 0
   1159             heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
   1160         }
   1161 
   1162         setMeasuredDimension(widthSize , heightSize);
   1163         mWidthMeasureSpec = widthMeasureSpec;
   1164     }
   1165 
   1166     private void measureScrapChild(View child, int position, int widthMeasureSpec) {
   1167         LayoutParams p = (LayoutParams) child.getLayoutParams();
   1168         if (p == null) {
   1169             p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
   1170             child.setLayoutParams(p);
   1171         }
   1172         p.viewType = mAdapter.getItemViewType(position);
   1173         p.forceAdd = true;
   1174 
   1175         int childWidthSpec = ViewGroup.getChildMeasureSpec(widthMeasureSpec,
   1176                 mListPadding.left + mListPadding.right, p.width);
   1177         int lpHeight = p.height;
   1178         int childHeightSpec;
   1179         if (lpHeight > 0) {
   1180             childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
   1181         } else {
   1182             childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
   1183         }
   1184         child.measure(childWidthSpec, childHeightSpec);
   1185     }
   1186 
   1187     /**
   1188      * @return True to recycle the views used to measure this ListView in
   1189      *         UNSPECIFIED/AT_MOST modes, false otherwise.
   1190      * @hide
   1191      */
   1192     @ViewDebug.ExportedProperty(category = "list")
   1193     protected boolean recycleOnMeasure() {
   1194         return true;
   1195     }
   1196 
   1197     /**
   1198      * Measures the height of the given range of children (inclusive) and
   1199      * returns the height with this ListView's padding and divider heights
   1200      * included. If maxHeight is provided, the measuring will stop when the
   1201      * current height reaches maxHeight.
   1202      *
   1203      * @param widthMeasureSpec The width measure spec to be given to a child's
   1204      *            {@link View#measure(int, int)}.
   1205      * @param startPosition The position of the first child to be shown.
   1206      * @param endPosition The (inclusive) position of the last child to be
   1207      *            shown. Specify {@link #NO_POSITION} if the last child should be
   1208      *            the last available child from the adapter.
   1209      * @param maxHeight The maximum height that will be returned (if all the
   1210      *            children don't fit in this value, this value will be
   1211      *            returned).
   1212      * @param disallowPartialChildPosition In general, whether the returned
   1213      *            height should only contain entire children. This is more
   1214      *            powerful--it is the first inclusive position at which partial
   1215      *            children will not be allowed. Example: it looks nice to have
   1216      *            at least 3 completely visible children, and in portrait this
   1217      *            will most likely fit; but in landscape there could be times
   1218      *            when even 2 children can not be completely shown, so a value
   1219      *            of 2 (remember, inclusive) would be good (assuming
   1220      *            startPosition is 0).
   1221      * @return The height of this ListView with the given children.
   1222      */
   1223     final int measureHeightOfChildren(int widthMeasureSpec, int startPosition, int endPosition,
   1224             final int maxHeight, int disallowPartialChildPosition) {
   1225 
   1226         final ListAdapter adapter = mAdapter;
   1227         if (adapter == null) {
   1228             return mListPadding.top + mListPadding.bottom;
   1229         }
   1230 
   1231         // Include the padding of the list
   1232         int returnedHeight = mListPadding.top + mListPadding.bottom;
   1233         final int dividerHeight = ((mDividerHeight > 0) && mDivider != null) ? mDividerHeight : 0;
   1234         // The previous height value that was less than maxHeight and contained
   1235         // no partial children
   1236         int prevHeightWithoutPartialChild = 0;
   1237         int i;
   1238         View child;
   1239 
   1240         // mItemCount - 1 since endPosition parameter is inclusive
   1241         endPosition = (endPosition == NO_POSITION) ? adapter.getCount() - 1 : endPosition;
   1242         final AbsListView.RecycleBin recycleBin = mRecycler;
   1243         final boolean recyle = recycleOnMeasure();
   1244         final boolean[] isScrap = mIsScrap;
   1245 
   1246         for (i = startPosition; i <= endPosition; ++i) {
   1247             child = obtainView(i, isScrap);
   1248 
   1249             measureScrapChild(child, i, widthMeasureSpec);
   1250 
   1251             if (i > 0) {
   1252                 // Count the divider for all but one child
   1253                 returnedHeight += dividerHeight;
   1254             }
   1255 
   1256             // Recycle the view before we possibly return from the method
   1257             if (recyle && recycleBin.shouldRecycleViewType(
   1258                     ((LayoutParams) child.getLayoutParams()).viewType)) {
   1259                 recycleBin.addScrapView(child, -1);
   1260             }
   1261 
   1262             returnedHeight += child.getMeasuredHeight();
   1263 
   1264             if (returnedHeight >= maxHeight) {
   1265                 // We went over, figure out which height to return.  If returnedHeight > maxHeight,
   1266                 // then the i'th position did not fit completely.
   1267                 return (disallowPartialChildPosition >= 0) // Disallowing is enabled (> -1)
   1268                             && (i > disallowPartialChildPosition) // We've past the min pos
   1269                             && (prevHeightWithoutPartialChild > 0) // We have a prev height
   1270                             && (returnedHeight != maxHeight) // i'th child did not fit completely
   1271                         ? prevHeightWithoutPartialChild
   1272                         : maxHeight;
   1273             }
   1274 
   1275             if ((disallowPartialChildPosition >= 0) && (i >= disallowPartialChildPosition)) {
   1276                 prevHeightWithoutPartialChild = returnedHeight;
   1277             }
   1278         }
   1279 
   1280         // At this point, we went through the range of children, and they each
   1281         // completely fit, so return the returnedHeight
   1282         return returnedHeight;
   1283     }
   1284 
   1285     @Override
   1286     int findMotionRow(int y) {
   1287         int childCount = getChildCount();
   1288         if (childCount > 0) {
   1289             if (!mStackFromBottom) {
   1290                 for (int i = 0; i < childCount; i++) {
   1291                     View v = getChildAt(i);
   1292                     if (y <= v.getBottom()) {
   1293                         return mFirstPosition + i;
   1294                     }
   1295                 }
   1296             } else {
   1297                 for (int i = childCount - 1; i >= 0; i--) {
   1298                     View v = getChildAt(i);
   1299                     if (y >= v.getTop()) {
   1300                         return mFirstPosition + i;
   1301                     }
   1302                 }
   1303             }
   1304         }
   1305         return INVALID_POSITION;
   1306     }
   1307 
   1308     /**
   1309      * Put a specific item at a specific location on the screen and then build
   1310      * up and down from there.
   1311      *
   1312      * @param position The reference view to use as the starting point
   1313      * @param top Pixel offset from the top of this view to the top of the
   1314      *        reference view.
   1315      *
   1316      * @return The selected view, or null if the selected view is outside the
   1317      *         visible area.
   1318      */
   1319     private View fillSpecific(int position, int top) {
   1320         boolean tempIsSelected = position == mSelectedPosition;
   1321         View temp = makeAndAddView(position, top, true, mListPadding.left, tempIsSelected);
   1322         // Possibly changed again in fillUp if we add rows above this one.
   1323         mFirstPosition = position;
   1324 
   1325         View above;
   1326         View below;
   1327 
   1328         final int dividerHeight = mDividerHeight;
   1329         if (!mStackFromBottom) {
   1330             above = fillUp(position - 1, temp.getTop() - dividerHeight);
   1331             // This will correct for the top of the first view not touching the top of the list
   1332             adjustViewsUpOrDown();
   1333             below = fillDown(position + 1, temp.getBottom() + dividerHeight);
   1334             int childCount = getChildCount();
   1335             if (childCount > 0) {
   1336                 correctTooHigh(childCount);
   1337             }
   1338         } else {
   1339             below = fillDown(position + 1, temp.getBottom() + dividerHeight);
   1340             // This will correct for the bottom of the last view not touching the bottom of the list
   1341             adjustViewsUpOrDown();
   1342             above = fillUp(position - 1, temp.getTop() - dividerHeight);
   1343             int childCount = getChildCount();
   1344             if (childCount > 0) {
   1345                  correctTooLow(childCount);
   1346             }
   1347         }
   1348 
   1349         if (tempIsSelected) {
   1350             return temp;
   1351         } else if (above != null) {
   1352             return above;
   1353         } else {
   1354             return below;
   1355         }
   1356     }
   1357 
   1358     /**
   1359      * Check if we have dragged the bottom of the list too high (we have pushed the
   1360      * top element off the top of the screen when we did not need to). Correct by sliding
   1361      * everything back down.
   1362      *
   1363      * @param childCount Number of children
   1364      */
   1365     private void correctTooHigh(int childCount) {
   1366         // First see if the last item is visible. If it is not, it is OK for the
   1367         // top of the list to be pushed up.
   1368         int lastPosition = mFirstPosition + childCount - 1;
   1369         if (lastPosition == mItemCount - 1 && childCount > 0) {
   1370 
   1371             // Get the last child ...
   1372             final View lastChild = getChildAt(childCount - 1);
   1373 
   1374             // ... and its bottom edge
   1375             final int lastBottom = lastChild.getBottom();
   1376 
   1377             // This is bottom of our drawable area
   1378             final int end = (mBottom - mTop) - mListPadding.bottom;
   1379 
   1380             // This is how far the bottom edge of the last view is from the bottom of the
   1381             // drawable area
   1382             int bottomOffset = end - lastBottom;
   1383             View firstChild = getChildAt(0);
   1384             final int firstTop = firstChild.getTop();
   1385 
   1386             // Make sure we are 1) Too high, and 2) Either there are more rows above the
   1387             // first row or the first row is scrolled off the top of the drawable area
   1388             if (bottomOffset > 0 && (mFirstPosition > 0 || firstTop < mListPadding.top))  {
   1389                 if (mFirstPosition == 0) {
   1390                     // Don't pull the top too far down
   1391                     bottomOffset = Math.min(bottomOffset, mListPadding.top - firstTop);
   1392                 }
   1393                 // Move everything down
   1394                 offsetChildrenTopAndBottom(bottomOffset);
   1395                 if (mFirstPosition > 0) {
   1396                     // Fill the gap that was opened above mFirstPosition with more rows, if
   1397                     // possible
   1398                     fillUp(mFirstPosition - 1, firstChild.getTop() - mDividerHeight);
   1399                     // Close up the remaining gap
   1400                     adjustViewsUpOrDown();
   1401                 }
   1402 
   1403             }
   1404         }
   1405     }
   1406 
   1407     /**
   1408      * Check if we have dragged the bottom of the list too low (we have pushed the
   1409      * bottom element off the bottom of the screen when we did not need to). Correct by sliding
   1410      * everything back up.
   1411      *
   1412      * @param childCount Number of children
   1413      */
   1414     private void correctTooLow(int childCount) {
   1415         // First see if the first item is visible. If it is not, it is OK for the
   1416         // bottom of the list to be pushed down.
   1417         if (mFirstPosition == 0 && childCount > 0) {
   1418 
   1419             // Get the first child ...
   1420             final View firstChild = getChildAt(0);
   1421 
   1422             // ... and its top edge
   1423             final int firstTop = firstChild.getTop();
   1424 
   1425             // This is top of our drawable area
   1426             final int start = mListPadding.top;
   1427 
   1428             // This is bottom of our drawable area
   1429             final int end = (mBottom - mTop) - mListPadding.bottom;
   1430 
   1431             // This is how far the top edge of the first view is from the top of the
   1432             // drawable area
   1433             int topOffset = firstTop - start;
   1434             View lastChild = getChildAt(childCount - 1);
   1435             final int lastBottom = lastChild.getBottom();
   1436             int lastPosition = mFirstPosition + childCount - 1;
   1437 
   1438             // Make sure we are 1) Too low, and 2) Either there are more rows below the
   1439             // last row or the last row is scrolled off the bottom of the drawable area
   1440             if (topOffset > 0) {
   1441                 if (lastPosition < mItemCount - 1 || lastBottom > end)  {
   1442                     if (lastPosition == mItemCount - 1) {
   1443                         // Don't pull the bottom too far up
   1444                         topOffset = Math.min(topOffset, lastBottom - end);
   1445                     }
   1446                     // Move everything up
   1447                     offsetChildrenTopAndBottom(-topOffset);
   1448                     if (lastPosition < mItemCount - 1) {
   1449                         // Fill the gap that was opened below the last position with more rows, if
   1450                         // possible
   1451                         fillDown(lastPosition + 1, lastChild.getBottom() + mDividerHeight);
   1452                         // Close up the remaining gap
   1453                         adjustViewsUpOrDown();
   1454                     }
   1455                 } else if (lastPosition == mItemCount - 1) {
   1456                     adjustViewsUpOrDown();
   1457                 }
   1458             }
   1459         }
   1460     }
   1461 
   1462     @Override
   1463     protected void layoutChildren() {
   1464         final boolean blockLayoutRequests = mBlockLayoutRequests;
   1465         if (!blockLayoutRequests) {
   1466             mBlockLayoutRequests = true;
   1467         } else {
   1468             return;
   1469         }
   1470 
   1471         try {
   1472             super.layoutChildren();
   1473 
   1474             invalidate();
   1475 
   1476             if (mAdapter == null) {
   1477                 resetList();
   1478                 invokeOnItemScrollListener();
   1479                 return;
   1480             }
   1481 
   1482             int childrenTop = mListPadding.top;
   1483             int childrenBottom = mBottom - mTop - mListPadding.bottom;
   1484 
   1485             int childCount = getChildCount();
   1486             int index = 0;
   1487             int delta = 0;
   1488 
   1489             View sel;
   1490             View oldSel = null;
   1491             View oldFirst = null;
   1492             View newSel = null;
   1493 
   1494             View focusLayoutRestoreView = null;
   1495 
   1496             AccessibilityNodeInfo accessibilityFocusLayoutRestoreNode = null;
   1497             View accessibilityFocusLayoutRestoreView = null;
   1498             int accessibilityFocusPosition = INVALID_POSITION;
   1499 
   1500             // Remember stuff we will need down below
   1501             switch (mLayoutMode) {
   1502             case LAYOUT_SET_SELECTION:
   1503                 index = mNextSelectedPosition - mFirstPosition;
   1504                 if (index >= 0 && index < childCount) {
   1505                     newSel = getChildAt(index);
   1506                 }
   1507                 break;
   1508             case LAYOUT_FORCE_TOP:
   1509             case LAYOUT_FORCE_BOTTOM:
   1510             case LAYOUT_SPECIFIC:
   1511             case LAYOUT_SYNC:
   1512                 break;
   1513             case LAYOUT_MOVE_SELECTION:
   1514             default:
   1515                 // Remember the previously selected view
   1516                 index = mSelectedPosition - mFirstPosition;
   1517                 if (index >= 0 && index < childCount) {
   1518                     oldSel = getChildAt(index);
   1519                 }
   1520 
   1521                 // Remember the previous first child
   1522                 oldFirst = getChildAt(0);
   1523 
   1524                 if (mNextSelectedPosition >= 0) {
   1525                     delta = mNextSelectedPosition - mSelectedPosition;
   1526                 }
   1527 
   1528                 // Caution: newSel might be null
   1529                 newSel = getChildAt(index + delta);
   1530             }
   1531 
   1532 
   1533             boolean dataChanged = mDataChanged;
   1534             if (dataChanged) {
   1535                 handleDataChanged();
   1536             }
   1537 
   1538             // Handle the empty set by removing all views that are visible
   1539             // and calling it a day
   1540             if (mItemCount == 0) {
   1541                 resetList();
   1542                 invokeOnItemScrollListener();
   1543                 return;
   1544             } else if (mItemCount != mAdapter.getCount()) {
   1545                 throw new IllegalStateException("The content of the adapter has changed but "
   1546                         + "ListView did not receive a notification. Make sure the content of "
   1547                         + "your adapter is not modified from a background thread, but only "
   1548                         + "from the UI thread. [in ListView(" + getId() + ", " + getClass()
   1549                         + ") with Adapter(" + mAdapter.getClass() + ")]");
   1550             }
   1551 
   1552             setSelectedPositionInt(mNextSelectedPosition);
   1553 
   1554             // Remember which child, if any, had accessibility focus. This must
   1555             // occur before recycling any views, since that will clear
   1556             // accessibility focus.
   1557             final ViewRootImpl viewRootImpl = getViewRootImpl();
   1558             if (viewRootImpl != null) {
   1559                 final View accessFocusedView = viewRootImpl.getAccessibilityFocusedHost();
   1560                 if (accessFocusedView != null) {
   1561                     final View accessFocusedChild = findAccessibilityFocusedChild(
   1562                             accessFocusedView);
   1563                     if (accessFocusedChild != null) {
   1564                         if (!dataChanged || isDirectChildHeaderOrFooter(accessFocusedChild)) {
   1565                             // If the views won't be changing, try to maintain
   1566                             // focus on the current view host and (if
   1567                             // applicable) its virtual view.
   1568                             accessibilityFocusLayoutRestoreView = accessFocusedView;
   1569                             accessibilityFocusLayoutRestoreNode = viewRootImpl
   1570                                     .getAccessibilityFocusedVirtualView();
   1571                         } else {
   1572                             // Otherwise, try to maintain focus at the same
   1573                             // position.
   1574                             accessibilityFocusPosition = getPositionForView(accessFocusedChild);
   1575                         }
   1576                     }
   1577                 }
   1578             }
   1579 
   1580             // Pull all children into the RecycleBin.
   1581             // These views will be reused if possible
   1582             final int firstPosition = mFirstPosition;
   1583             final RecycleBin recycleBin = mRecycler;
   1584 
   1585             // reset the focus restoration
   1586             View focusLayoutRestoreDirectChild = null;
   1587 
   1588             // Don't put header or footer views into the Recycler. Those are
   1589             // already cached in mHeaderViews;
   1590             if (dataChanged) {
   1591                 for (int i = 0; i < childCount; i++) {
   1592                     recycleBin.addScrapView(getChildAt(i), firstPosition+i);
   1593                 }
   1594             } else {
   1595                 recycleBin.fillActiveViews(childCount, firstPosition);
   1596             }
   1597 
   1598             // take focus back to us temporarily to avoid the eventual
   1599             // call to clear focus when removing the focused child below
   1600             // from messing things up when ViewAncestor assigns focus back
   1601             // to someone else
   1602             final View focusedChild = getFocusedChild();
   1603             if (focusedChild != null) {
   1604                 // TODO: in some cases focusedChild.getParent() == null
   1605 
   1606                 // we can remember the focused view to restore after relayout if the
   1607                 // data hasn't changed, or if the focused position is a header or footer
   1608                 if (!dataChanged || isDirectChildHeaderOrFooter(focusedChild)) {
   1609                     focusLayoutRestoreDirectChild = focusedChild;
   1610                     // remember the specific view that had focus
   1611                     focusLayoutRestoreView = findFocus();
   1612                     if (focusLayoutRestoreView != null) {
   1613                         // tell it we are going to mess with it
   1614                         focusLayoutRestoreView.onStartTemporaryDetach();
   1615                     }
   1616                 }
   1617                 requestFocus();
   1618             }
   1619 
   1620             // Clear out old views
   1621             detachAllViewsFromParent();
   1622             recycleBin.removeSkippedScrap();
   1623 
   1624             switch (mLayoutMode) {
   1625             case LAYOUT_SET_SELECTION:
   1626                 if (newSel != null) {
   1627                     sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);
   1628                 } else {
   1629                     sel = fillFromMiddle(childrenTop, childrenBottom);
   1630                 }
   1631                 break;
   1632             case LAYOUT_SYNC:
   1633                 sel = fillSpecific(mSyncPosition, mSpecificTop);
   1634                 break;
   1635             case LAYOUT_FORCE_BOTTOM:
   1636                 sel = fillUp(mItemCount - 1, childrenBottom);
   1637                 adjustViewsUpOrDown();
   1638                 break;
   1639             case LAYOUT_FORCE_TOP:
   1640                 mFirstPosition = 0;
   1641                 sel = fillFromTop(childrenTop);
   1642                 adjustViewsUpOrDown();
   1643                 break;
   1644             case LAYOUT_SPECIFIC:
   1645                 sel = fillSpecific(reconcileSelectedPosition(), mSpecificTop);
   1646                 break;
   1647             case LAYOUT_MOVE_SELECTION:
   1648                 sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom);
   1649                 break;
   1650             default:
   1651                 if (childCount == 0) {
   1652                     if (!mStackFromBottom) {
   1653                         final int position = lookForSelectablePosition(0, true);
   1654                         setSelectedPositionInt(position);
   1655                         sel = fillFromTop(childrenTop);
   1656                     } else {
   1657                         final int position = lookForSelectablePosition(mItemCount - 1, false);
   1658                         setSelectedPositionInt(position);
   1659                         sel = fillUp(mItemCount - 1, childrenBottom);
   1660                     }
   1661                 } else {
   1662                     if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {
   1663                         sel = fillSpecific(mSelectedPosition,
   1664                                 oldSel == null ? childrenTop : oldSel.getTop());
   1665                     } else if (mFirstPosition < mItemCount) {
   1666                         sel = fillSpecific(mFirstPosition,
   1667                                 oldFirst == null ? childrenTop : oldFirst.getTop());
   1668                     } else {
   1669                         sel = fillSpecific(0, childrenTop);
   1670                     }
   1671                 }
   1672                 break;
   1673             }
   1674 
   1675             // Flush any cached views that did not get reused above
   1676             recycleBin.scrapActiveViews();
   1677 
   1678             if (sel != null) {
   1679                 // the current selected item should get focus if items
   1680                 // are focusable
   1681                 if (mItemsCanFocus && hasFocus() && !sel.hasFocus()) {
   1682                     final boolean focusWasTaken = (sel == focusLayoutRestoreDirectChild &&
   1683                             focusLayoutRestoreView != null &&
   1684                             focusLayoutRestoreView.requestFocus()) || sel.requestFocus();
   1685                     if (!focusWasTaken) {
   1686                         // selected item didn't take focus, fine, but still want
   1687                         // to make sure something else outside of the selected view
   1688                         // has focus
   1689                         final View focused = getFocusedChild();
   1690                         if (focused != null) {
   1691                             focused.clearFocus();
   1692                         }
   1693                         positionSelector(INVALID_POSITION, sel);
   1694                     } else {
   1695                         sel.setSelected(false);
   1696                         mSelectorRect.setEmpty();
   1697                     }
   1698                 } else {
   1699                     positionSelector(INVALID_POSITION, sel);
   1700                 }
   1701                 mSelectedTop = sel.getTop();
   1702             } else {
   1703                 if (mTouchMode > TOUCH_MODE_DOWN && mTouchMode < TOUCH_MODE_SCROLL) {
   1704                     View child = getChildAt(mMotionPosition - mFirstPosition);
   1705                     if (child != null) positionSelector(mMotionPosition, child);
   1706                 } else {
   1707                     mSelectedTop = 0;
   1708                     mSelectorRect.setEmpty();
   1709                 }
   1710 
   1711                 // even if there is not selected position, we may need to restore
   1712                 // focus (i.e. something focusable in touch mode)
   1713                 if (hasFocus() && focusLayoutRestoreView != null) {
   1714                     focusLayoutRestoreView.requestFocus();
   1715                 }
   1716             }
   1717 
   1718             // Attempt to restore accessibility focus.
   1719             if (accessibilityFocusLayoutRestoreView != null) {
   1720                 final AccessibilityNodeProvider provider =
   1721                         accessibilityFocusLayoutRestoreView.getAccessibilityNodeProvider();
   1722                 if ((accessibilityFocusLayoutRestoreNode != null) && (provider != null)) {
   1723                     final int virtualViewId = AccessibilityNodeInfo.getVirtualDescendantId(
   1724                             accessibilityFocusLayoutRestoreNode.getSourceNodeId());
   1725                     provider.performAction(virtualViewId,
   1726                             AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
   1727                 } else {
   1728                     accessibilityFocusLayoutRestoreView.requestAccessibilityFocus();
   1729                 }
   1730             } else if (accessibilityFocusPosition != INVALID_POSITION) {
   1731                 // Bound the position within the visible children.
   1732                 final int position = MathUtils.constrain(
   1733                         (accessibilityFocusPosition - mFirstPosition), 0, (getChildCount() - 1));
   1734                 final View restoreView = getChildAt(position);
   1735                 if (restoreView != null) {
   1736                     restoreView.requestAccessibilityFocus();
   1737                 }
   1738             }
   1739 
   1740             // tell focus view we are done mucking with it, if it is still in
   1741             // our view hierarchy.
   1742             if (focusLayoutRestoreView != null
   1743                     && focusLayoutRestoreView.getWindowToken() != null) {
   1744                 focusLayoutRestoreView.onFinishTemporaryDetach();
   1745             }
   1746 
   1747             mLayoutMode = LAYOUT_NORMAL;
   1748             mDataChanged = false;
   1749             if (mPositionScrollAfterLayout != null) {
   1750                 post(mPositionScrollAfterLayout);
   1751                 mPositionScrollAfterLayout = null;
   1752             }
   1753             mNeedSync = false;
   1754             setNextSelectedPositionInt(mSelectedPosition);
   1755 
   1756             updateScrollIndicators();
   1757 
   1758             if (mItemCount > 0) {
   1759                 checkSelectionChanged();
   1760             }
   1761 
   1762             invokeOnItemScrollListener();
   1763         } finally {
   1764             if (!blockLayoutRequests) {
   1765                 mBlockLayoutRequests = false;
   1766             }
   1767         }
   1768     }
   1769 
   1770     /**
   1771      * @param focusedView the view that has accessibility focus.
   1772      * @return the direct child that contains accessibility focus.
   1773      */
   1774     private View findAccessibilityFocusedChild(View focusedView) {
   1775         ViewParent viewParent = focusedView.getParent();
   1776         while ((viewParent instanceof View) && (viewParent != this)) {
   1777             focusedView = (View) viewParent;
   1778             viewParent = viewParent.getParent();
   1779         }
   1780         if (!(viewParent instanceof View)) {
   1781             return null;
   1782         }
   1783         return focusedView;
   1784     }
   1785 
   1786     /**
   1787      * @param child a direct child of this list.
   1788      * @return Whether child is a header or footer view.
   1789      */
   1790     private boolean isDirectChildHeaderOrFooter(View child) {
   1791 
   1792         final ArrayList<FixedViewInfo> headers = mHeaderViewInfos;
   1793         final int numHeaders = headers.size();
   1794         for (int i = 0; i < numHeaders; i++) {
   1795             if (child == headers.get(i).view) {
   1796                 return true;
   1797             }
   1798         }
   1799         final ArrayList<FixedViewInfo> footers = mFooterViewInfos;
   1800         final int numFooters = footers.size();
   1801         for (int i = 0; i < numFooters; i++) {
   1802             if (child == footers.get(i).view) {
   1803                 return true;
   1804             }
   1805         }
   1806         return false;
   1807     }
   1808 
   1809     /**
   1810      * Obtain the view and add it to our list of children. The view can be made
   1811      * fresh, converted from an unused view, or used as is if it was in the
   1812      * recycle bin.
   1813      *
   1814      * @param position Logical position in the list
   1815      * @param y Top or bottom edge of the view to add
   1816      * @param flow If flow is true, align top edge to y. If false, align bottom
   1817      *        edge to y.
   1818      * @param childrenLeft Left edge where children should be positioned
   1819      * @param selected Is this position selected?
   1820      * @return View that was added
   1821      */
   1822     private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
   1823             boolean selected) {
   1824         View child;
   1825 
   1826 
   1827         if (!mDataChanged) {
   1828             // Try to use an existing view for this position
   1829             child = mRecycler.getActiveView(position);
   1830             if (child != null) {
   1831                 // Found it -- we're using an existing child
   1832                 // This just needs to be positioned
   1833                 setupChild(child, position, y, flow, childrenLeft, selected, true);
   1834 
   1835                 return child;
   1836             }
   1837         }
   1838 
   1839         // Make a new view for this position, or convert an unused view if possible
   1840         child = obtainView(position, mIsScrap);
   1841 
   1842         // This needs to be positioned and measured
   1843         setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
   1844 
   1845         return child;
   1846     }
   1847 
   1848     /**
   1849      * Add a view as a child and make sure it is measured (if necessary) and
   1850      * positioned properly.
   1851      *
   1852      * @param child The view to add
   1853      * @param position The position of this child
   1854      * @param y The y position relative to which this view will be positioned
   1855      * @param flowDown If true, align top edge to y. If false, align bottom
   1856      *        edge to y.
   1857      * @param childrenLeft Left edge where children should be positioned
   1858      * @param selected Is this position selected?
   1859      * @param recycled Has this view been pulled from the recycle bin? If so it
   1860      *        does not need to be remeasured.
   1861      */
   1862     private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,
   1863             boolean selected, boolean recycled) {
   1864         final boolean isSelected = selected && shouldShowSelector();
   1865         final boolean updateChildSelected = isSelected != child.isSelected();
   1866         final int mode = mTouchMode;
   1867         final boolean isPressed = mode > TOUCH_MODE_DOWN && mode < TOUCH_MODE_SCROLL &&
   1868                 mMotionPosition == position;
   1869         final boolean updateChildPressed = isPressed != child.isPressed();
   1870         final boolean needToMeasure = !recycled || updateChildSelected || child.isLayoutRequested();
   1871 
   1872         // Respect layout params that are already in the view. Otherwise make some up...
   1873         // noinspection unchecked
   1874         AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();
   1875         if (p == null) {
   1876             p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
   1877         }
   1878         p.viewType = mAdapter.getItemViewType(position);
   1879 
   1880         if ((recycled && !p.forceAdd) || (p.recycledHeaderFooter &&
   1881                 p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER)) {
   1882             attachViewToParent(child, flowDown ? -1 : 0, p);
   1883         } else {
   1884             p.forceAdd = false;
   1885             if (p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
   1886                 p.recycledHeaderFooter = true;
   1887             }
   1888             addViewInLayout(child, flowDown ? -1 : 0, p, true);
   1889         }
   1890 
   1891         if (updateChildSelected) {
   1892             child.setSelected(isSelected);
   1893         }
   1894 
   1895         if (updateChildPressed) {
   1896             child.setPressed(isPressed);
   1897         }
   1898 
   1899         if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
   1900             if (child instanceof Checkable) {
   1901                 ((Checkable) child).setChecked(mCheckStates.get(position));
   1902             } else if (getContext().getApplicationInfo().targetSdkVersion
   1903                     >= android.os.Build.VERSION_CODES.HONEYCOMB) {
   1904                 child.setActivated(mCheckStates.get(position));
   1905             }
   1906         }
   1907 
   1908         if (needToMeasure) {
   1909             int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
   1910                     mListPadding.left + mListPadding.right, p.width);
   1911             int lpHeight = p.height;
   1912             int childHeightSpec;
   1913             if (lpHeight > 0) {
   1914                 childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
   1915             } else {
   1916                 childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
   1917             }
   1918             child.measure(childWidthSpec, childHeightSpec);
   1919         } else {
   1920             cleanupLayoutState(child);
   1921         }
   1922 
   1923         final int w = child.getMeasuredWidth();
   1924         final int h = child.getMeasuredHeight();
   1925         final int childTop = flowDown ? y : y - h;
   1926 
   1927         if (needToMeasure) {
   1928             final int childRight = childrenLeft + w;
   1929             final int childBottom = childTop + h;
   1930             child.layout(childrenLeft, childTop, childRight, childBottom);
   1931         } else {
   1932             child.offsetLeftAndRight(childrenLeft - child.getLeft());
   1933             child.offsetTopAndBottom(childTop - child.getTop());
   1934         }
   1935 
   1936         if (mCachingStarted && !child.isDrawingCacheEnabled()) {
   1937             child.setDrawingCacheEnabled(true);
   1938         }
   1939 
   1940         if (recycled && (((AbsListView.LayoutParams)child.getLayoutParams()).scrappedFromPosition)
   1941                 != position) {
   1942             child.jumpDrawablesToCurrentState();
   1943         }
   1944     }
   1945 
   1946     @Override
   1947     protected boolean canAnimate() {
   1948         return super.canAnimate() && mItemCount > 0;
   1949     }
   1950 
   1951     /**
   1952      * Sets the currently selected item. If in touch mode, the item will not be selected
   1953      * but it will still be positioned appropriately. If the specified selection position
   1954      * is less than 0, then the item at position 0 will be selected.
   1955      *
   1956      * @param position Index (starting at 0) of the data item to be selected.
   1957      */
   1958     @Override
   1959     public void setSelection(int position) {
   1960         setSelectionFromTop(position, 0);
   1961     }
   1962 
   1963     /**
   1964      * Sets the selected item and positions the selection y pixels from the top edge
   1965      * of the ListView. (If in touch mode, the item will not be selected but it will
   1966      * still be positioned appropriately.)
   1967      *
   1968      * @param position Index (starting at 0) of the data item to be selected.
   1969      * @param y The distance from the top edge of the ListView (plus padding) that the
   1970      *        item will be positioned.
   1971      */
   1972     public void setSelectionFromTop(int position, int y) {
   1973         if (mAdapter == null) {
   1974             return;
   1975         }
   1976 
   1977         if (!isInTouchMode()) {
   1978             position = lookForSelectablePosition(position, true);
   1979             if (position >= 0) {
   1980                 setNextSelectedPositionInt(position);
   1981             }
   1982         } else {
   1983             mResurrectToPosition = position;
   1984         }
   1985 
   1986         if (position >= 0) {
   1987             mLayoutMode = LAYOUT_SPECIFIC;
   1988             mSpecificTop = mListPadding.top + y;
   1989 
   1990             if (mNeedSync) {
   1991                 mSyncPosition = position;
   1992                 mSyncRowId = mAdapter.getItemId(position);
   1993             }
   1994 
   1995             if (mPositionScroller != null) {
   1996                 mPositionScroller.stop();
   1997             }
   1998             requestLayout();
   1999         }
   2000     }
   2001 
   2002     /**
   2003      * Makes the item at the supplied position selected.
   2004      *
   2005      * @param position the position of the item to select
   2006      */
   2007     @Override
   2008     void setSelectionInt(int position) {
   2009         setNextSelectedPositionInt(position);
   2010         boolean awakeScrollbars = false;
   2011 
   2012         final int selectedPosition = mSelectedPosition;
   2013 
   2014         if (selectedPosition >= 0) {
   2015             if (position == selectedPosition - 1) {
   2016                 awakeScrollbars = true;
   2017             } else if (position == selectedPosition + 1) {
   2018                 awakeScrollbars = true;
   2019             }
   2020         }
   2021 
   2022         if (mPositionScroller != null) {
   2023             mPositionScroller.stop();
   2024         }
   2025 
   2026         layoutChildren();
   2027 
   2028         if (awakeScrollbars) {
   2029             awakenScrollBars();
   2030         }
   2031     }
   2032 
   2033     /**
   2034      * Find a position that can be selected (i.e., is not a separator).
   2035      *
   2036      * @param position The starting position to look at.
   2037      * @param lookDown Whether to look down for other positions.
   2038      * @return The next selectable position starting at position and then searching either up or
   2039      *         down. Returns {@link #INVALID_POSITION} if nothing can be found.
   2040      */
   2041     @Override
   2042     int lookForSelectablePosition(int position, boolean lookDown) {
   2043         final ListAdapter adapter = mAdapter;
   2044         if (adapter == null || isInTouchMode()) {
   2045             return INVALID_POSITION;
   2046         }
   2047 
   2048         final int count = adapter.getCount();
   2049         if (!mAreAllItemsSelectable) {
   2050             if (lookDown) {
   2051                 position = Math.max(0, position);
   2052                 while (position < count && !adapter.isEnabled(position)) {
   2053                     position++;
   2054                 }
   2055             } else {
   2056                 position = Math.min(position, count - 1);
   2057                 while (position >= 0 && !adapter.isEnabled(position)) {
   2058                     position--;
   2059                 }
   2060             }
   2061 
   2062             if (position < 0 || position >= count) {
   2063                 return INVALID_POSITION;
   2064             }
   2065             return position;
   2066         } else {
   2067             if (position < 0 || position >= count) {
   2068                 return INVALID_POSITION;
   2069             }
   2070             return position;
   2071         }
   2072     }
   2073 
   2074     /**
   2075      * setSelectionAfterHeaderView set the selection to be the first list item
   2076      * after the header views.
   2077      */
   2078     public void setSelectionAfterHeaderView() {
   2079         final int count = mHeaderViewInfos.size();
   2080         if (count > 0) {
   2081             mNextSelectedPosition = 0;
   2082             return;
   2083         }
   2084 
   2085         if (mAdapter != null) {
   2086             setSelection(count);
   2087         } else {
   2088             mNextSelectedPosition = count;
   2089             mLayoutMode = LAYOUT_SET_SELECTION;
   2090         }
   2091 
   2092     }
   2093 
   2094     @Override
   2095     public boolean dispatchKeyEvent(KeyEvent event) {
   2096         // Dispatch in the normal way
   2097         boolean handled = super.dispatchKeyEvent(event);
   2098         if (!handled) {
   2099             // If we didn't handle it...
   2100             View focused = getFocusedChild();
   2101             if (focused != null && event.getAction() == KeyEvent.ACTION_DOWN) {
   2102                 // ... and our focused child didn't handle it
   2103                 // ... give it to ourselves so we can scroll if necessary
   2104                 handled = onKeyDown(event.getKeyCode(), event);
   2105             }
   2106         }
   2107         return handled;
   2108     }
   2109 
   2110     @Override
   2111     public boolean onKeyDown(int keyCode, KeyEvent event) {
   2112         return commonKey(keyCode, 1, event);
   2113     }
   2114 
   2115     @Override
   2116     public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
   2117         return commonKey(keyCode, repeatCount, event);
   2118     }
   2119 
   2120     @Override
   2121     public boolean onKeyUp(int keyCode, KeyEvent event) {
   2122         return commonKey(keyCode, 1, event);
   2123     }
   2124 
   2125     private boolean commonKey(int keyCode, int count, KeyEvent event) {
   2126         if (mAdapter == null || !mIsAttached) {
   2127             return false;
   2128         }
   2129 
   2130         if (mDataChanged) {
   2131             layoutChildren();
   2132         }
   2133 
   2134         boolean handled = false;
   2135         int action = event.getAction();
   2136 
   2137         if (action != KeyEvent.ACTION_UP) {
   2138             switch (keyCode) {
   2139             case KeyEvent.KEYCODE_DPAD_UP:
   2140                 if (event.hasNoModifiers()) {
   2141                     handled = resurrectSelectionIfNeeded();
   2142                     if (!handled) {
   2143                         while (count-- > 0) {
   2144                             if (arrowScroll(FOCUS_UP)) {
   2145                                 handled = true;
   2146                             } else {
   2147                                 break;
   2148                             }
   2149                         }
   2150                     }
   2151                 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
   2152                     handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
   2153                 }
   2154                 break;
   2155 
   2156             case KeyEvent.KEYCODE_DPAD_DOWN:
   2157                 if (event.hasNoModifiers()) {
   2158                     handled = resurrectSelectionIfNeeded();
   2159                     if (!handled) {
   2160                         while (count-- > 0) {
   2161                             if (arrowScroll(FOCUS_DOWN)) {
   2162                                 handled = true;
   2163                             } else {
   2164                                 break;
   2165                             }
   2166                         }
   2167                     }
   2168                 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
   2169                     handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
   2170                 }
   2171                 break;
   2172 
   2173             case KeyEvent.KEYCODE_DPAD_LEFT:
   2174                 if (event.hasNoModifiers()) {
   2175                     handled = handleHorizontalFocusWithinListItem(View.FOCUS_LEFT);
   2176                 }
   2177                 break;
   2178 
   2179             case KeyEvent.KEYCODE_DPAD_RIGHT:
   2180                 if (event.hasNoModifiers()) {
   2181                     handled = handleHorizontalFocusWithinListItem(View.FOCUS_RIGHT);
   2182                 }
   2183                 break;
   2184 
   2185             case KeyEvent.KEYCODE_DPAD_CENTER:
   2186             case KeyEvent.KEYCODE_ENTER:
   2187                 if (event.hasNoModifiers()) {
   2188                     handled = resurrectSelectionIfNeeded();
   2189                     if (!handled
   2190                             && event.getRepeatCount() == 0 && getChildCount() > 0) {
   2191                         keyPressed();
   2192                         handled = true;
   2193                     }
   2194                 }
   2195                 break;
   2196 
   2197             case KeyEvent.KEYCODE_SPACE:
   2198                 if (mPopup == null || !mPopup.isShowing()) {
   2199                     if (event.hasNoModifiers()) {
   2200                         handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_DOWN);
   2201                     } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
   2202                         handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_UP);
   2203                     }
   2204                     handled = true;
   2205                 }
   2206                 break;
   2207 
   2208             case KeyEvent.KEYCODE_PAGE_UP:
   2209                 if (event.hasNoModifiers()) {
   2210                     handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_UP);
   2211                 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
   2212                     handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
   2213                 }
   2214                 break;
   2215 
   2216             case KeyEvent.KEYCODE_PAGE_DOWN:
   2217                 if (event.hasNoModifiers()) {
   2218                     handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_DOWN);
   2219                 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
   2220                     handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
   2221                 }
   2222                 break;
   2223 
   2224             case KeyEvent.KEYCODE_MOVE_HOME:
   2225                 if (event.hasNoModifiers()) {
   2226                     handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
   2227                 }
   2228                 break;
   2229 
   2230             case KeyEvent.KEYCODE_MOVE_END:
   2231                 if (event.hasNoModifiers()) {
   2232                     handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
   2233                 }
   2234                 break;
   2235 
   2236             case KeyEvent.KEYCODE_TAB:
   2237                 // XXX Sometimes it is useful to be able to TAB through the items in
   2238                 //     a ListView sequentially.  Unfortunately this can create an
   2239                 //     asymmetry in TAB navigation order unless the list selection
   2240                 //     always reverts to the top or bottom when receiving TAB focus from
   2241                 //     another widget.  Leaving this behavior disabled for now but
   2242                 //     perhaps it should be configurable (and more comprehensive).
   2243                 if (false) {
   2244                     if (event.hasNoModifiers()) {
   2245                         handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_DOWN);
   2246                     } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
   2247                         handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_UP);
   2248                     }
   2249                 }
   2250                 break;
   2251             }
   2252         }
   2253 
   2254         if (handled) {
   2255             return true;
   2256         }
   2257 
   2258         if (sendToTextFilter(keyCode, count, event)) {
   2259             return true;
   2260         }
   2261 
   2262         switch (action) {
   2263             case KeyEvent.ACTION_DOWN:
   2264                 return super.onKeyDown(keyCode, event);
   2265 
   2266             case KeyEvent.ACTION_UP:
   2267                 return super.onKeyUp(keyCode, event);
   2268 
   2269             case KeyEvent.ACTION_MULTIPLE:
   2270                 return super.onKeyMultiple(keyCode, count, event);
   2271 
   2272             default: // shouldn't happen
   2273                 return false;
   2274         }
   2275     }
   2276 
   2277     /**
   2278      * Scrolls up or down by the number of items currently present on screen.
   2279      *
   2280      * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
   2281      * @return whether selection was moved
   2282      */
   2283     boolean pageScroll(int direction) {
   2284         int nextPage = -1;
   2285         boolean down = false;
   2286 
   2287         if (direction == FOCUS_UP) {
   2288             nextPage = Math.max(0, mSelectedPosition - getChildCount() - 1);
   2289         } else if (direction == FOCUS_DOWN) {
   2290             nextPage = Math.min(mItemCount - 1, mSelectedPosition + getChildCount() - 1);
   2291             down = true;
   2292         }
   2293 
   2294         if (nextPage >= 0) {
   2295             int position = lookForSelectablePosition(nextPage, down);
   2296             if (position >= 0) {
   2297                 mLayoutMode = LAYOUT_SPECIFIC;
   2298                 mSpecificTop = mPaddingTop + getVerticalFadingEdgeLength();
   2299 
   2300                 if (down && position > mItemCount - getChildCount()) {
   2301                     mLayoutMode = LAYOUT_FORCE_BOTTOM;
   2302                 }
   2303 
   2304                 if (!down && position < getChildCount()) {
   2305                     mLayoutMode = LAYOUT_FORCE_TOP;
   2306                 }
   2307 
   2308                 setSelectionInt(position);
   2309                 invokeOnItemScrollListener();
   2310                 if (!awakenScrollBars()) {
   2311                     invalidate();
   2312                 }
   2313 
   2314                 return true;
   2315             }
   2316         }
   2317 
   2318         return false;
   2319     }
   2320 
   2321     /**
   2322      * Go to the last or first item if possible (not worrying about panning across or navigating
   2323      * within the internal focus of the currently selected item.)
   2324      *
   2325      * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
   2326      *
   2327      * @return whether selection was moved
   2328      */
   2329     boolean fullScroll(int direction) {
   2330         boolean moved = false;
   2331         if (direction == FOCUS_UP) {
   2332             if (mSelectedPosition != 0) {
   2333                 int position = lookForSelectablePosition(0, true);
   2334                 if (position >= 0) {
   2335                     mLayoutMode = LAYOUT_FORCE_TOP;
   2336                     setSelectionInt(position);
   2337                     invokeOnItemScrollListener();
   2338                 }
   2339                 moved = true;
   2340             }
   2341         } else if (direction == FOCUS_DOWN) {
   2342             if (mSelectedPosition < mItemCount - 1) {
   2343                 int position = lookForSelectablePosition(mItemCount - 1, true);
   2344                 if (position >= 0) {
   2345                     mLayoutMode = LAYOUT_FORCE_BOTTOM;
   2346                     setSelectionInt(position);
   2347                     invokeOnItemScrollListener();
   2348                 }
   2349                 moved = true;
   2350             }
   2351         }
   2352 
   2353         if (moved && !awakenScrollBars()) {
   2354             awakenScrollBars();
   2355             invalidate();
   2356         }
   2357 
   2358         return moved;
   2359     }
   2360 
   2361     /**
   2362      * To avoid horizontal focus searches changing the selected item, we
   2363      * manually focus search within the selected item (as applicable), and
   2364      * prevent focus from jumping to something within another item.
   2365      * @param direction one of {View.FOCUS_LEFT, View.FOCUS_RIGHT}
   2366      * @return Whether this consumes the key event.
   2367      */
   2368     private boolean handleHorizontalFocusWithinListItem(int direction) {
   2369         if (direction != View.FOCUS_LEFT && direction != View.FOCUS_RIGHT)  {
   2370             throw new IllegalArgumentException("direction must be one of"
   2371                     + " {View.FOCUS_LEFT, View.FOCUS_RIGHT}");
   2372         }
   2373 
   2374         final int numChildren = getChildCount();
   2375         if (mItemsCanFocus && numChildren > 0 && mSelectedPosition != INVALID_POSITION) {
   2376             final View selectedView = getSelectedView();
   2377             if (selectedView != null && selectedView.hasFocus() &&
   2378                     selectedView instanceof ViewGroup) {
   2379 
   2380                 final View currentFocus = selectedView.findFocus();
   2381                 final View nextFocus = FocusFinder.getInstance().findNextFocus(
   2382                         (ViewGroup) selectedView, currentFocus, direction);
   2383                 if (nextFocus != null) {
   2384                     // do the math to get interesting rect in next focus' coordinates
   2385                     currentFocus.getFocusedRect(mTempRect);
   2386                     offsetDescendantRectToMyCoords(currentFocus, mTempRect);
   2387                     offsetRectIntoDescendantCoords(nextFocus, mTempRect);
   2388                     if (nextFocus.requestFocus(direction, mTempRect)) {
   2389                         return true;
   2390                     }
   2391                 }
   2392                 // we are blocking the key from being handled (by returning true)
   2393                 // if the global result is going to be some other view within this
   2394                 // list.  this is to acheive the overall goal of having
   2395                 // horizontal d-pad navigation remain in the current item.
   2396                 final View globalNextFocus = FocusFinder.getInstance().findNextFocus(
   2397                         (ViewGroup) getRootView(), currentFocus, direction);
   2398                 if (globalNextFocus != null) {
   2399                     return isViewAncestorOf(globalNextFocus, this);
   2400                 }
   2401             }
   2402         }
   2403         return false;
   2404     }
   2405 
   2406     /**
   2407      * Scrolls to the next or previous item if possible.
   2408      *
   2409      * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
   2410      *
   2411      * @return whether selection was moved
   2412      */
   2413     boolean arrowScroll(int direction) {
   2414         try {
   2415             mInLayout = true;
   2416             final boolean handled = arrowScrollImpl(direction);
   2417             if (handled) {
   2418                 playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
   2419             }
   2420             return handled;
   2421         } finally {
   2422             mInLayout = false;
   2423         }
   2424     }
   2425 
   2426     /**
   2427      * Used by {@link #arrowScrollImpl(int)} to help determine the next selected position
   2428      * to move to. This can return a position currently not represented by a view on screen
   2429      * but only in the direction given.
   2430      *
   2431      * @param selectedPos Current selected position to move from
   2432      * @param direction Direction to move in
   2433      * @return Desired selected position after moving in the given direction
   2434      */
   2435     private final int nextSelectedPositionForDirection(int selectedPos, int direction) {
   2436         int nextSelected;
   2437         if (direction == View.FOCUS_DOWN) {
   2438             nextSelected = selectedPos != INVALID_POSITION && selectedPos >= mFirstPosition ?
   2439                     selectedPos + 1 :
   2440                     mFirstPosition;
   2441         } else {
   2442             final int lastPos = mFirstPosition + getChildCount() - 1;
   2443             nextSelected = selectedPos != INVALID_POSITION && selectedPos <= lastPos ?
   2444                     selectedPos - 1 :
   2445                     lastPos;
   2446         }
   2447 
   2448         if (nextSelected < 0 || nextSelected >= mAdapter.getCount()) {
   2449             return INVALID_POSITION;
   2450         }
   2451         return lookForSelectablePosition(nextSelected, direction == View.FOCUS_DOWN);
   2452     }
   2453 
   2454     /**
   2455      * Handle an arrow scroll going up or down.  Take into account whether items are selectable,
   2456      * whether there are focusable items etc.
   2457      *
   2458      * @param direction Either {@link android.view.View#FOCUS_UP} or {@link android.view.View#FOCUS_DOWN}.
   2459      * @return Whether any scrolling, selection or focus change occured.
   2460      */
   2461     private boolean arrowScrollImpl(int direction) {
   2462         if (getChildCount() <= 0) {
   2463             return false;
   2464         }
   2465 
   2466         View selectedView = getSelectedView();
   2467         int selectedPos = mSelectedPosition;
   2468 
   2469         int nextSelectedPosition = nextSelectedPositionForDirection(selectedPos, direction);
   2470         int amountToScroll = amountToScroll(direction, nextSelectedPosition);
   2471 
   2472         // if we are moving focus, we may OVERRIDE the default behavior
   2473         final ArrowScrollFocusResult focusResult = mItemsCanFocus ? arrowScrollFocused(direction) : null;
   2474         if (focusResult != null) {
   2475             nextSelectedPosition = focusResult.getSelectedPosition();
   2476             amountToScroll = focusResult.getAmountToScroll();
   2477         }
   2478 
   2479         boolean needToRedraw = focusResult != null;
   2480         if (nextSelectedPosition != INVALID_POSITION) {
   2481             handleNewSelectionChange(selectedView, direction, nextSelectedPosition, focusResult != null);
   2482             setSelectedPositionInt(nextSelectedPosition);
   2483             setNextSelectedPositionInt(nextSelectedPosition);
   2484             selectedView = getSelectedView();
   2485             selectedPos = nextSelectedPosition;
   2486             if (mItemsCanFocus && focusResult == null) {
   2487                 // there was no new view found to take focus, make sure we
   2488                 // don't leave focus with the old selection
   2489                 final View focused = getFocusedChild();
   2490                 if (focused != null) {
   2491                     focused.clearFocus();
   2492                 }
   2493             }
   2494             needToRedraw = true;
   2495             checkSelectionChanged();
   2496         }
   2497 
   2498         if (amountToScroll > 0) {
   2499             scrollListItemsBy((direction == View.FOCUS_UP) ? amountToScroll : -amountToScroll);
   2500             needToRedraw = true;
   2501         }
   2502 
   2503         // if we didn't find a new focusable, make sure any existing focused
   2504         // item that was panned off screen gives up focus.
   2505         if (mItemsCanFocus && (focusResult == null)
   2506                 && selectedView != null && selectedView.hasFocus()) {
   2507             final View focused = selectedView.findFocus();
   2508             if (!isViewAncestorOf(focused, this) || distanceToView(focused) > 0) {
   2509                 focused.clearFocus();
   2510             }
   2511         }
   2512 
   2513         // if  the current selection is panned off, we need to remove the selection
   2514         if (nextSelectedPosition == INVALID_POSITION && selectedView != null
   2515                 && !isViewAncestorOf(selectedView, this)) {
   2516             selectedView = null;
   2517             hideSelector();
   2518 
   2519             // but we don't want to set the ressurect position (that would make subsequent
   2520             // unhandled key events bring back the item we just scrolled off!)
   2521             mResurrectToPosition = INVALID_POSITION;
   2522         }
   2523 
   2524         if (needToRedraw) {
   2525             if (selectedView != null) {
   2526                 positionSelector(selectedPos, selectedView);
   2527                 mSelectedTop = selectedView.getTop();
   2528             }
   2529             if (!awakenScrollBars()) {
   2530                 invalidate();
   2531             }
   2532             invokeOnItemScrollListener();
   2533             return true;
   2534         }
   2535 
   2536         return false;
   2537     }
   2538 
   2539     /**
   2540      * When selection changes, it is possible that the previously selected or the
   2541      * next selected item will change its size.  If so, we need to offset some folks,
   2542      * and re-layout the items as appropriate.
   2543      *
   2544      * @param selectedView The currently selected view (before changing selection).
   2545      *   should be <code>null</code> if there was no previous selection.
   2546      * @param direction Either {@link android.view.View#FOCUS_UP} or
   2547      *        {@link android.view.View#FOCUS_DOWN}.
   2548      * @param newSelectedPosition The position of the next selection.
   2549      * @param newFocusAssigned whether new focus was assigned.  This matters because
   2550      *        when something has focus, we don't want to show selection (ugh).
   2551      */
   2552     private void handleNewSelectionChange(View selectedView, int direction, int newSelectedPosition,
   2553             boolean newFocusAssigned) {
   2554         if (newSelectedPosition == INVALID_POSITION) {
   2555             throw new IllegalArgumentException("newSelectedPosition needs to be valid");
   2556         }
   2557 
   2558         // whether or not we are moving down or up, we want to preserve the
   2559         // top of whatever view is on top:
   2560         // - moving down: the view that had selection
   2561         // - moving up: the view that is getting selection
   2562         View topView;
   2563         View bottomView;
   2564         int topViewIndex, bottomViewIndex;
   2565         boolean topSelected = false;
   2566         final int selectedIndex = mSelectedPosition - mFirstPosition;
   2567         final int nextSelectedIndex = newSelectedPosition - mFirstPosition;
   2568         if (direction == View.FOCUS_UP) {
   2569             topViewIndex = nextSelectedIndex;
   2570             bottomViewIndex = selectedIndex;
   2571             topView = getChildAt(topViewIndex);
   2572             bottomView = selectedView;
   2573             topSelected = true;
   2574         } else {
   2575             topViewIndex = selectedIndex;
   2576             bottomViewIndex = nextSelectedIndex;
   2577             topView = selectedView;
   2578             bottomView = getChildAt(bottomViewIndex);
   2579         }
   2580 
   2581         final int numChildren = getChildCount();
   2582 
   2583         // start with top view: is it changing size?
   2584         if (topView != null) {
   2585             topView.setSelected(!newFocusAssigned && topSelected);
   2586             measureAndAdjustDown(topView, topViewIndex, numChildren);
   2587         }
   2588 
   2589         // is the bottom view changing size?
   2590         if (bottomView != null) {
   2591             bottomView.setSelected(!newFocusAssigned && !topSelected);
   2592             measureAndAdjustDown(bottomView, bottomViewIndex, numChildren);
   2593         }
   2594     }
   2595 
   2596     /**
   2597      * Re-measure a child, and if its height changes, lay it out preserving its
   2598      * top, and adjust the children below it appropriately.
   2599      * @param child The child
   2600      * @param childIndex The view group index of the child.
   2601      * @param numChildren The number of children in the view group.
   2602      */
   2603     private void measureAndAdjustDown(View child, int childIndex, int numChildren) {
   2604         int oldHeight = child.getHeight();
   2605         measureItem(child);
   2606         if (child.getMeasuredHeight() != oldHeight) {
   2607             // lay out the view, preserving its top
   2608             relayoutMeasuredItem(child);
   2609 
   2610             // adjust views below appropriately
   2611             final int heightDelta = child.getMeasuredHeight() - oldHeight;
   2612             for (int i = childIndex + 1; i < numChildren; i++) {
   2613                 getChildAt(i).offsetTopAndBottom(heightDelta);
   2614             }
   2615         }
   2616     }
   2617 
   2618     /**
   2619      * Measure a particular list child.
   2620      * TODO: unify with setUpChild.
   2621      * @param child The child.
   2622      */
   2623     private void measureItem(View child) {
   2624         ViewGroup.LayoutParams p = child.getLayoutParams();
   2625         if (p == null) {
   2626             p = new ViewGroup.LayoutParams(
   2627                     ViewGroup.LayoutParams.MATCH_PARENT,
   2628                     ViewGroup.LayoutParams.WRAP_CONTENT);
   2629         }
   2630 
   2631         int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
   2632                 mListPadding.left + mListPadding.right, p.width);
   2633         int lpHeight = p.height;
   2634         int childHeightSpec;
   2635         if (lpHeight > 0) {
   2636             childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
   2637         } else {
   2638             childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
   2639         }
   2640         child.measure(childWidthSpec, childHeightSpec);
   2641     }
   2642 
   2643     /**
   2644      * Layout a child that has been measured, preserving its top position.
   2645      * TODO: unify with setUpChild.
   2646      * @param child The child.
   2647      */
   2648     private void relayoutMeasuredItem(View child) {
   2649         final int w = child.getMeasuredWidth();
   2650         final int h = child.getMeasuredHeight();
   2651         final int childLeft = mListPadding.left;
   2652         final int childRight = childLeft + w;
   2653         final int childTop = child.getTop();
   2654         final int childBottom = childTop + h;
   2655         child.layout(childLeft, childTop, childRight, childBottom);
   2656     }
   2657 
   2658     /**
   2659      * @return The amount to preview next items when arrow srolling.
   2660      */
   2661     private int getArrowScrollPreviewLength() {
   2662         return Math.max(MIN_SCROLL_PREVIEW_PIXELS, getVerticalFadingEdgeLength());
   2663     }
   2664 
   2665     /**
   2666      * Determine how much we need to scroll in order to get the next selected view
   2667      * visible, with a fading edge showing below as applicable.  The amount is
   2668      * capped at {@link #getMaxScrollAmount()} .
   2669      *
   2670      * @param direction either {@link android.view.View#FOCUS_UP} or
   2671      *        {@link android.view.View#FOCUS_DOWN}.
   2672      * @param nextSelectedPosition The position of the next selection, or
   2673      *        {@link #INVALID_POSITION} if there is no next selectable position
   2674      * @return The amount to scroll. Note: this is always positive!  Direction
   2675      *         needs to be taken into account when actually scrolling.
   2676      */
   2677     private int amountToScroll(int direction, int nextSelectedPosition) {
   2678         final int listBottom = getHeight() - mListPadding.bottom;
   2679         final int listTop = mListPadding.top;
   2680 
   2681         int numChildren = getChildCount();
   2682 
   2683         if (direction == View.FOCUS_DOWN) {
   2684             int indexToMakeVisible = numChildren - 1;
   2685             if (nextSelectedPosition != INVALID_POSITION) {
   2686                 indexToMakeVisible = nextSelectedPosition - mFirstPosition;
   2687             }
   2688             while (numChildren <= indexToMakeVisible) {
   2689                 // Child to view is not attached yet.
   2690                 addViewBelow(getChildAt(numChildren - 1), mFirstPosition + numChildren - 1);
   2691                 numChildren++;
   2692             }
   2693             final int positionToMakeVisible = mFirstPosition + indexToMakeVisible;
   2694             final View viewToMakeVisible = getChildAt(indexToMakeVisible);
   2695 
   2696             int goalBottom = listBottom;
   2697             if (positionToMakeVisible < mItemCount - 1) {
   2698                 goalBottom -= getArrowScrollPreviewLength();
   2699             }
   2700 
   2701             if (viewToMakeVisible.getBottom() <= goalBottom) {
   2702                 // item is fully visible.
   2703                 return 0;
   2704             }
   2705 
   2706             if (nextSelectedPosition != INVALID_POSITION
   2707                     && (goalBottom - viewToMakeVisible.getTop()) >= getMaxScrollAmount()) {
   2708                 // item already has enough of it visible, changing selection is good enough
   2709                 return 0;
   2710             }
   2711 
   2712             int amountToScroll = (viewToMakeVisible.getBottom() - goalBottom);
   2713 
   2714             if ((mFirstPosition + numChildren) == mItemCount) {
   2715                 // last is last in list -> make sure we don't scroll past it
   2716                 final int max = getChildAt(numChildren - 1).getBottom() - listBottom;
   2717                 amountToScroll = Math.min(amountToScroll, max);
   2718             }
   2719 
   2720             return Math.min(amountToScroll, getMaxScrollAmount());
   2721         } else {
   2722             int indexToMakeVisible = 0;
   2723             if (nextSelectedPosition != INVALID_POSITION) {
   2724                 indexToMakeVisible = nextSelectedPosition - mFirstPosition;
   2725             }
   2726             while (indexToMakeVisible < 0) {
   2727                 // Child to view is not attached yet.
   2728                 addViewAbove(getChildAt(0), mFirstPosition);
   2729                 mFirstPosition--;
   2730                 indexToMakeVisible = nextSelectedPosition - mFirstPosition;
   2731             }
   2732             final int positionToMakeVisible = mFirstPosition + indexToMakeVisible;
   2733             final View viewToMakeVisible = getChildAt(indexToMakeVisible);
   2734             int goalTop = listTop;
   2735             if (positionToMakeVisible > 0) {
   2736                 goalTop += getArrowScrollPreviewLength();
   2737             }
   2738             if (viewToMakeVisible.getTop() >= goalTop) {
   2739                 // item is fully visible.
   2740                 return 0;
   2741             }
   2742 
   2743             if (nextSelectedPosition != INVALID_POSITION &&
   2744                     (viewToMakeVisible.getBottom() - goalTop) >= getMaxScrollAmount()) {
   2745                 // item already has enough of it visible, changing selection is good enough
   2746                 return 0;
   2747             }
   2748 
   2749             int amountToScroll = (goalTop - viewToMakeVisible.getTop());
   2750             if (mFirstPosition == 0) {
   2751                 // first is first in list -> make sure we don't scroll past it
   2752                 final int max = listTop - getChildAt(0).getTop();
   2753                 amountToScroll = Math.min(amountToScroll,  max);
   2754             }
   2755             return Math.min(amountToScroll, getMaxScrollAmount());
   2756         }
   2757     }
   2758 
   2759     /**
   2760      * Holds results of focus aware arrow scrolling.
   2761      */
   2762     static private class ArrowScrollFocusResult {
   2763         private int mSelectedPosition;
   2764         private int mAmountToScroll;
   2765 
   2766         /**
   2767          * How {@link android.widget.ListView#arrowScrollFocused} returns its values.
   2768          */
   2769         void populate(int selectedPosition, int amountToScroll) {
   2770             mSelectedPosition = selectedPosition;
   2771             mAmountToScroll = amountToScroll;
   2772         }
   2773 
   2774         public int getSelectedPosition() {
   2775             return mSelectedPosition;
   2776         }
   2777 
   2778         public int getAmountToScroll() {
   2779             return mAmountToScroll;
   2780         }
   2781     }
   2782 
   2783     /**
   2784      * @param direction either {@link android.view.View#FOCUS_UP} or
   2785      *        {@link android.view.View#FOCUS_DOWN}.
   2786      * @return The position of the next selectable position of the views that
   2787      *         are currently visible, taking into account the fact that there might
   2788      *         be no selection.  Returns {@link #INVALID_POSITION} if there is no
   2789      *         selectable view on screen in the given direction.
   2790      */
   2791     private int lookForSelectablePositionOnScreen(int direction) {
   2792         final int firstPosition = mFirstPosition;
   2793         if (direction == View.FOCUS_DOWN) {
   2794             int startPos = (mSelectedPosition != INVALID_POSITION) ?
   2795                     mSelectedPosition + 1 :
   2796                     firstPosition;
   2797             if (startPos >= mAdapter.getCount()) {
   2798                 return INVALID_POSITION;
   2799             }
   2800             if (startPos < firstPosition) {
   2801                 startPos = firstPosition;
   2802             }
   2803 
   2804             final int lastVisiblePos = getLastVisiblePosition();
   2805             final ListAdapter adapter = getAdapter();
   2806             for (int pos = startPos; pos <= lastVisiblePos; pos++) {
   2807                 if (adapter.isEnabled(pos)
   2808                         && getChildAt(pos - firstPosition).getVisibility() == View.VISIBLE) {
   2809                     return pos;
   2810                 }
   2811             }
   2812         } else {
   2813             int last = firstPosition + getChildCount() - 1;
   2814             int startPos = (mSelectedPosition != INVALID_POSITION) ?
   2815                     mSelectedPosition - 1 :
   2816                     firstPosition + getChildCount() - 1;
   2817             if (startPos < 0 || startPos >= mAdapter.getCount()) {
   2818                 return INVALID_POSITION;
   2819             }
   2820             if (startPos > last) {
   2821                 startPos = last;
   2822             }
   2823 
   2824             final ListAdapter adapter = getAdapter();
   2825             for (int pos = startPos; pos >= firstPosition; pos--) {
   2826                 if (adapter.isEnabled(pos)
   2827                         && getChildAt(pos - firstPosition).getVisibility() == View.VISIBLE) {
   2828                     return pos;
   2829                 }
   2830             }
   2831         }
   2832         return INVALID_POSITION;
   2833     }
   2834 
   2835     /**
   2836      * Do an arrow scroll based on focus searching.  If a new view is
   2837      * given focus, return the selection delta and amount to scroll via
   2838      * an {@link ArrowScrollFocusResult}, otherwise, return null.
   2839      *
   2840      * @param direction either {@link android.view.View#FOCUS_UP} or
   2841      *        {@link android.view.View#FOCUS_DOWN}.
   2842      * @return The result if focus has changed, or <code>null</code>.
   2843      */
   2844     private ArrowScrollFocusResult arrowScrollFocused(final int direction) {
   2845         final View selectedView = getSelectedView();
   2846         View newFocus;
   2847         if (selectedView != null && selectedView.hasFocus()) {
   2848             View oldFocus = selectedView.findFocus();
   2849             newFocus = FocusFinder.getInstance().findNextFocus(this, oldFocus, direction);
   2850         } else {
   2851             if (direction == View.FOCUS_DOWN) {
   2852                 final boolean topFadingEdgeShowing = (mFirstPosition > 0);
   2853                 final int listTop = mListPadding.top +
   2854                         (topFadingEdgeShowing ? getArrowScrollPreviewLength() : 0);
   2855                 final int ySearchPoint =
   2856                         (selectedView != null && selectedView.getTop() > listTop) ?
   2857                                 selectedView.getTop() :
   2858                                 listTop;
   2859                 mTempRect.set(0, ySearchPoint, 0, ySearchPoint);
   2860             } else {
   2861                 final boolean bottomFadingEdgeShowing =
   2862                         (mFirstPosition + getChildCount() - 1) < mItemCount;
   2863                 final int listBottom = getHeight() - mListPadding.bottom -
   2864                         (bottomFadingEdgeShowing ? getArrowScrollPreviewLength() : 0);
   2865                 final int ySearchPoint =
   2866                         (selectedView != null && selectedView.getBottom() < listBottom) ?
   2867                                 selectedView.getBottom() :
   2868                                 listBottom;
   2869                 mTempRect.set(0, ySearchPoint, 0, ySearchPoint);
   2870             }
   2871             newFocus = FocusFinder.getInstance().findNextFocusFromRect(this, mTempRect, direction);
   2872         }
   2873 
   2874         if (newFocus != null) {
   2875             final int positionOfNewFocus = positionOfNewFocus(newFocus);
   2876 
   2877             // if the focus change is in a different new position, make sure
   2878             // we aren't jumping over another selectable position
   2879             if (mSelectedPosition != INVALID_POSITION && positionOfNewFocus != mSelectedPosition) {
   2880                 final int selectablePosition = lookForSelectablePositionOnScreen(direction);
   2881                 if (selectablePosition != INVALID_POSITION &&
   2882                         ((direction == View.FOCUS_DOWN && selectablePosition < positionOfNewFocus) ||
   2883                         (direction == View.FOCUS_UP && selectablePosition > positionOfNewFocus))) {
   2884                     return null;
   2885                 }
   2886             }
   2887 
   2888             int focusScroll = amountToScrollToNewFocus(direction, newFocus, positionOfNewFocus);
   2889 
   2890             final int maxScrollAmount = getMaxScrollAmount();
   2891             if (focusScroll < maxScrollAmount) {
   2892                 // not moving too far, safe to give next view focus
   2893                 newFocus.requestFocus(direction);
   2894                 mArrowScrollFocusResult.populate(positionOfNewFocus, focusScroll);
   2895                 return mArrowScrollFocusResult;
   2896             } else if (distanceToView(newFocus) < maxScrollAmount){
   2897                 // Case to consider:
   2898                 // too far to get entire next focusable on screen, but by going
   2899                 // max scroll amount, we are getting it at least partially in view,
   2900                 // so give it focus and scroll the max ammount.
   2901                 newFocus.requestFocus(direction);
   2902                 mArrowScrollFocusResult.populate(positionOfNewFocus, maxScrollAmount);
   2903                 return mArrowScrollFocusResult;
   2904             }
   2905         }
   2906         return null;
   2907     }
   2908 
   2909     /**
   2910      * @param newFocus The view that would have focus.
   2911      * @return the position that contains newFocus
   2912      */
   2913     private int positionOfNewFocus(View newFocus) {
   2914         final int numChildren = getChildCount();
   2915         for (int i = 0; i < numChildren; i++) {
   2916             final View child = getChildAt(i);
   2917             if (isViewAncestorOf(newFocus, child)) {
   2918                 return mFirstPosition + i;
   2919             }
   2920         }
   2921         throw new IllegalArgumentException("newFocus is not a child of any of the"
   2922                 + " children of the list!");
   2923     }
   2924 
   2925     /**
   2926      * Return true if child is an ancestor of parent, (or equal to the parent).
   2927      */
   2928     private boolean isViewAncestorOf(View child, View parent) {
   2929         if (child == parent) {
   2930             return true;
   2931         }
   2932 
   2933         final ViewParent theParent = child.getParent();
   2934         return (theParent instanceof ViewGroup) && isViewAncestorOf((View) theParent, parent);
   2935     }
   2936 
   2937     /**
   2938      * Determine how much we need to scroll in order to get newFocus in view.
   2939      * @param direction either {@link android.view.View#FOCUS_UP} or
   2940      *        {@link android.view.View#FOCUS_DOWN}.
   2941      * @param newFocus The view that would take focus.
   2942      * @param positionOfNewFocus The position of the list item containing newFocus
   2943      * @return The amount to scroll.  Note: this is always positive!  Direction
   2944      *   needs to be taken into account when actually scrolling.
   2945      */
   2946     private int amountToScrollToNewFocus(int direction, View newFocus, int positionOfNewFocus) {
   2947         int amountToScroll = 0;
   2948         newFocus.getDrawingRect(mTempRect);
   2949         offsetDescendantRectToMyCoords(newFocus, mTempRect);
   2950         if (direction == View.FOCUS_UP) {
   2951             if (mTempRect.top < mListPadding.top) {
   2952                 amountToScroll = mListPadding.top - mTempRect.top;
   2953                 if (positionOfNewFocus > 0) {
   2954                     amountToScroll += getArrowScrollPreviewLength();
   2955                 }
   2956             }
   2957         } else {
   2958             final int listBottom = getHeight() - mListPadding.bottom;
   2959             if (mTempRect.bottom > listBottom) {
   2960                 amountToScroll = mTempRect.bottom - listBottom;
   2961                 if (positionOfNewFocus < mItemCount - 1) {
   2962                     amountToScroll += getArrowScrollPreviewLength();
   2963                 }
   2964             }
   2965         }
   2966         return amountToScroll;
   2967     }
   2968 
   2969     /**
   2970      * Determine the distance to the nearest edge of a view in a particular
   2971      * direction.
   2972      *
   2973      * @param descendant A descendant of this list.
   2974      * @return The distance, or 0 if the nearest edge is already on screen.
   2975      */
   2976     private int distanceToView(View descendant) {
   2977         int distance = 0;
   2978         descendant.getDrawingRect(mTempRect);
   2979         offsetDescendantRectToMyCoords(descendant, mTempRect);
   2980         final int listBottom = mBottom - mTop - mListPadding.bottom;
   2981         if (mTempRect.bottom < mListPadding.top) {
   2982             distance = mListPadding.top - mTempRect.bottom;
   2983         } else if (mTempRect.top > listBottom) {
   2984             distance = mTempRect.top - listBottom;
   2985         }
   2986         return distance;
   2987     }
   2988 
   2989 
   2990     /**
   2991      * Scroll the children by amount, adding a view at the end and removing
   2992      * views that fall off as necessary.
   2993      *
   2994      * @param amount The amount (positive or negative) to scroll.
   2995      */
   2996     private void scrollListItemsBy(int amount) {
   2997         offsetChildrenTopAndBottom(amount);
   2998 
   2999         final int listBottom = getHeight() - mListPadding.bottom;
   3000         final int listTop = mListPadding.top;
   3001         final AbsListView.RecycleBin recycleBin = mRecycler;
   3002 
   3003         if (amount < 0) {
   3004             // shifted items up
   3005 
   3006             // may need to pan views into the bottom space
   3007             int numChildren = getChildCount();
   3008             View last = getChildAt(numChildren - 1);
   3009             while (last.getBottom() < listBottom) {
   3010                 final int lastVisiblePosition = mFirstPosition + numChildren - 1;
   3011                 if (lastVisiblePosition < mItemCount - 1) {
   3012                     last = addViewBelow(last, lastVisiblePosition);
   3013                     numChildren++;
   3014                 } else {
   3015                     break;
   3016                 }
   3017             }
   3018 
   3019             // may have brought in the last child of the list that is skinnier
   3020             // than the fading edge, thereby leaving space at the end.  need
   3021             // to shift back
   3022             if (last.getBottom() < listBottom) {
   3023                 offsetChildrenTopAndBottom(listBottom - last.getBottom());
   3024             }
   3025 
   3026             // top views may be panned off screen
   3027             View first = getChildAt(0);
   3028             while (first.getBottom() < listTop) {
   3029                 AbsListView.LayoutParams layoutParams = (LayoutParams) first.getLayoutParams();
   3030                 if (recycleBin.shouldRecycleViewType(layoutParams.viewType)) {
   3031                     recycleBin.addScrapView(first, mFirstPosition);
   3032                 }
   3033                 detachViewFromParent(first);
   3034                 first = getChildAt(0);
   3035                 mFirstPosition++;
   3036             }
   3037         } else {
   3038             // shifted items down
   3039             View first = getChildAt(0);
   3040 
   3041             // may need to pan views into top
   3042             while ((first.getTop() > listTop) && (mFirstPosition > 0)) {
   3043                 first = addViewAbove(first, mFirstPosition);
   3044                 mFirstPosition--;
   3045             }
   3046 
   3047             // may have brought the very first child of the list in too far and
   3048             // need to shift it back
   3049             if (first.getTop() > listTop) {
   3050                 offsetChildrenTopAndBottom(listTop - first.getTop());
   3051             }
   3052 
   3053             int lastIndex = getChildCount() - 1;
   3054             View last = getChildAt(lastIndex);
   3055 
   3056             // bottom view may be panned off screen
   3057             while (last.getTop() > listBottom) {
   3058                 AbsListView.LayoutParams layoutParams = (LayoutParams) last.getLayoutParams();
   3059                 if (recycleBin.shouldRecycleViewType(layoutParams.viewType)) {
   3060                     recycleBin.addScrapView(last, mFirstPosition+lastIndex);
   3061                 }
   3062                 detachViewFromParent(last);
   3063                 last = getChildAt(--lastIndex);
   3064             }
   3065         }
   3066     }
   3067 
   3068     private View addViewAbove(View theView, int position) {
   3069         int abovePosition = position - 1;
   3070         View view = obtainView(abovePosition, mIsScrap);
   3071         int edgeOfNewChild = theView.getTop() - mDividerHeight;
   3072         setupChild(view, abovePosition, edgeOfNewChild, false, mListPadding.left,
   3073                 false, mIsScrap[0]);
   3074         return view;
   3075     }
   3076 
   3077     private View addViewBelow(View theView, int position) {
   3078         int belowPosition = position + 1;
   3079         View view = obtainView(belowPosition, mIsScrap);
   3080         int edgeOfNewChild = theView.getBottom() + mDividerHeight;
   3081         setupChild(view, belowPosition, edgeOfNewChild, true, mListPadding.left,
   3082                 false, mIsScrap[0]);
   3083         return view;
   3084     }
   3085 
   3086     /**
   3087      * Indicates that the views created by the ListAdapter can contain focusable
   3088      * items.
   3089      *
   3090      * @param itemsCanFocus true if items can get focus, false otherwise
   3091      */
   3092     public void setItemsCanFocus(boolean itemsCanFocus) {
   3093         mItemsCanFocus = itemsCanFocus;
   3094         if (!itemsCanFocus) {
   3095             setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
   3096         }
   3097     }
   3098 
   3099     /**
   3100      * @return Whether the views created by the ListAdapter can contain focusable
   3101      * items.
   3102      */
   3103     public boolean getItemsCanFocus() {
   3104         return mItemsCanFocus;
   3105     }
   3106 
   3107     @Override
   3108     public boolean isOpaque() {
   3109         boolean retValue = (mCachingActive && mIsCacheColorOpaque && mDividerIsOpaque &&
   3110                 hasOpaqueScrollbars()) || super.isOpaque();
   3111         if (retValue) {
   3112             // only return true if the list items cover the entire area of the view
   3113             final int listTop = mListPadding != null ? mListPadding.top : mPaddingTop;
   3114             View first = getChildAt(0);
   3115             if (first == null || first.getTop() > listTop) {
   3116                 return false;
   3117             }
   3118             final int listBottom = getHeight() -
   3119                     (mListPadding != null ? mListPadding.bottom : mPaddingBottom);
   3120             View last = getChildAt(getChildCount() - 1);
   3121             if (last == null || last.getBottom() < listBottom) {
   3122                 return false;
   3123             }
   3124         }
   3125         return retValue;
   3126     }
   3127 
   3128     @Override
   3129     public void setCacheColorHint(int color) {
   3130         final boolean opaque = (color >>> 24) == 0xFF;
   3131         mIsCacheColorOpaque = opaque;
   3132         if (opaque) {
   3133             if (mDividerPaint == null) {
   3134                 mDividerPaint = new Paint();
   3135             }
   3136             mDividerPaint.setColor(color);
   3137         }
   3138         super.setCacheColorHint(color);
   3139     }
   3140 
   3141     void drawOverscrollHeader(Canvas canvas, Drawable drawable, Rect bounds) {
   3142         final int height = drawable.getMinimumHeight();
   3143 
   3144         canvas.save();
   3145         canvas.clipRect(bounds);
   3146 
   3147         final int span = bounds.bottom - bounds.top;
   3148         if (span < height) {
   3149             bounds.top = bounds.bottom - height;
   3150         }
   3151 
   3152         drawable.setBounds(bounds);
   3153         drawable.draw(canvas);
   3154 
   3155         canvas.restore();
   3156     }
   3157 
   3158     void drawOverscrollFooter(Canvas canvas, Drawable drawable, Rect bounds) {
   3159         final int height = drawable.getMinimumHeight();
   3160 
   3161         canvas.save();
   3162         canvas.clipRect(bounds);
   3163 
   3164         final int span = bounds.bottom - bounds.top;
   3165         if (span < height) {
   3166             bounds.bottom = bounds.top + height;
   3167         }
   3168 
   3169         drawable.setBounds(bounds);
   3170         drawable.draw(canvas);
   3171 
   3172         canvas.restore();
   3173     }
   3174 
   3175     @Override
   3176     protected void dispatchDraw(Canvas canvas) {
   3177         if (mCachingStarted) {
   3178             mCachingActive = true;
   3179         }
   3180 
   3181         // Draw the dividers
   3182         final int dividerHeight = mDividerHeight;
   3183         final Drawable overscrollHeader = mOverScrollHeader;
   3184         final Drawable overscrollFooter = mOverScrollFooter;
   3185         final boolean drawOverscrollHeader = overscrollHeader != null;
   3186         final boolean drawOverscrollFooter = overscrollFooter != null;
   3187         final boolean drawDividers = dividerHeight > 0 && mDivider != null;
   3188 
   3189         if (drawDividers || drawOverscrollHeader || drawOverscrollFooter) {
   3190             // Only modify the top and bottom in the loop, we set the left and right here
   3191             final Rect bounds = mTempRect;
   3192             bounds.left = mPaddingLeft;
   3193             bounds.right = mRight - mLeft - mPaddingRight;
   3194 
   3195             final int count = getChildCount();
   3196             final int headerCount = mHeaderViewInfos.size();
   3197             final int itemCount = mItemCount;
   3198             final int footerLimit = itemCount - mFooterViewInfos.size() - 1;
   3199             final boolean headerDividers = mHeaderDividersEnabled;
   3200             final boolean footerDividers = mFooterDividersEnabled;
   3201             final int first = mFirstPosition;
   3202             final boolean areAllItemsSelectable = mAreAllItemsSelectable;
   3203             final ListAdapter adapter = mAdapter;
   3204             // If the list is opaque *and* the background is not, we want to
   3205             // fill a rect where the dividers would be for non-selectable items
   3206             // If the list is opaque and the background is also opaque, we don't
   3207             // need to draw anything since the background will do it for us
   3208             final boolean fillForMissingDividers = isOpaque() && !super.isOpaque();
   3209 
   3210             if (fillForMissingDividers && mDividerPaint == null && mIsCacheColorOpaque) {
   3211                 mDividerPaint = new Paint();
   3212                 mDividerPaint.setColor(getCacheColorHint());
   3213             }
   3214             final Paint paint = mDividerPaint;
   3215 
   3216             int effectivePaddingTop = 0;
   3217             int effectivePaddingBottom = 0;
   3218             if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
   3219                 effectivePaddingTop = mListPadding.top;
   3220                 effectivePaddingBottom = mListPadding.bottom;
   3221             }
   3222 
   3223             final int listBottom = mBottom - mTop - effectivePaddingBottom + mScrollY;
   3224             if (!mStackFromBottom) {
   3225                 int bottom = 0;
   3226 
   3227                 // Draw top divider or header for overscroll
   3228                 final int scrollY = mScrollY;
   3229                 if (count > 0 && scrollY < 0) {
   3230                     if (drawOverscrollHeader) {
   3231                         bounds.bottom = 0;
   3232                         bounds.top = scrollY;
   3233                         drawOverscrollHeader(canvas, overscrollHeader, bounds);
   3234                     } else if (drawDividers) {
   3235                         bounds.bottom = 0;
   3236                         bounds.top = -dividerHeight;
   3237                         drawDivider(canvas, bounds, -1);
   3238                     }
   3239                 }
   3240 
   3241                 for (int i = 0; i < count; i++) {
   3242                     if ((headerDividers || first + i >= headerCount) &&
   3243                             (footerDividers || first + i < footerLimit)) {
   3244                         View child = getChildAt(i);
   3245                         bottom = child.getBottom();
   3246                         // Don't draw dividers next to items that are not enabled
   3247 
   3248                         if (drawDividers &&
   3249                                 (bottom < listBottom && !(drawOverscrollFooter && i == count - 1))) {
   3250                             if ((areAllItemsSelectable ||
   3251                                     (adapter.isEnabled(first + i) && (i == count - 1 ||
   3252                                             adapter.isEnabled(first + i + 1))))) {
   3253                                 bounds.top = bottom;
   3254                                 bounds.bottom = bottom + dividerHeight;
   3255                                 drawDivider(canvas, bounds, i);
   3256                             } else if (fillForMissingDividers) {
   3257                                 bounds.top = bottom;
   3258                                 bounds.bottom = bottom + dividerHeight;
   3259                                 canvas.drawRect(bounds, paint);
   3260                             }
   3261                         }
   3262                     }
   3263                 }
   3264 
   3265                 final int overFooterBottom = mBottom + mScrollY;
   3266                 if (drawOverscrollFooter && first + count == itemCount &&
   3267                         overFooterBottom > bottom) {
   3268                     bounds.top = bottom;
   3269                     bounds.bottom = overFooterBottom;
   3270                     drawOverscrollFooter(canvas, overscrollFooter, bounds);
   3271                 }
   3272             } else {
   3273                 int top;
   3274 
   3275                 final int scrollY = mScrollY;
   3276 
   3277                 if (count > 0 && drawOverscrollHeader) {
   3278                     bounds.top = scrollY;
   3279                     bounds.bottom = getChildAt(0).getTop();
   3280                     drawOverscrollHeader(canvas, overscrollHeader, bounds);
   3281                 }
   3282 
   3283                 final int start = drawOverscrollHeader ? 1 : 0;
   3284                 for (int i = start; i < count; i++) {
   3285                     if ((headerDividers || first + i >= headerCount) &&
   3286                             (footerDividers || first + i < footerLimit)) {
   3287                         View child = getChildAt(i);
   3288                         top = child.getTop();
   3289                         // Don't draw dividers next to items that are not enabled
   3290                         if (top > effectivePaddingTop) {
   3291                             if ((areAllItemsSelectable ||
   3292                                     (adapter.isEnabled(first + i) && (i == count - 1 ||
   3293                                             adapter.isEnabled(first + i + 1))))) {
   3294                                 bounds.top = top - dividerHeight;
   3295                                 bounds.bottom = top;
   3296                                 // Give the method the child ABOVE the divider, so we
   3297                                 // subtract one from our child
   3298                                 // position. Give -1 when there is no child above the
   3299                                 // divider.
   3300                                 drawDivider(canvas, bounds, i - 1);
   3301                             } else if (fillForMissingDividers) {
   3302                                 bounds.top = top - dividerHeight;
   3303                                 bounds.bottom = top;
   3304                                 canvas.drawRect(bounds, paint);
   3305                             }
   3306                         }
   3307                     }
   3308                 }
   3309 
   3310                 if (count > 0 && scrollY > 0) {
   3311                     if (drawOverscrollFooter) {
   3312                         final int absListBottom = mBottom;
   3313                         bounds.top = absListBottom;
   3314                         bounds.bottom = absListBottom + scrollY;
   3315                         drawOverscrollFooter(canvas, overscrollFooter, bounds);
   3316                     } else if (drawDividers) {
   3317                         bounds.top = listBottom;
   3318                         bounds.bottom = listBottom + dividerHeight;
   3319                         drawDivider(canvas, bounds, -1);
   3320                     }
   3321                 }
   3322             }
   3323         }
   3324 
   3325         // Draw the indicators (these should be drawn above the dividers) and children
   3326         super.dispatchDraw(canvas);
   3327     }
   3328 
   3329     @Override
   3330     protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
   3331         boolean more = super.drawChild(canvas, child, drawingTime);
   3332         if (mCachingActive && child.mCachingFailed) {
   3333             mCachingActive = false;
   3334         }
   3335         return more;
   3336     }
   3337 
   3338     /**
   3339      * Draws a divider for the given child in the given bounds.
   3340      *
   3341      * @param canvas The canvas to draw to.
   3342      * @param bounds The bounds of the divider.
   3343      * @param childIndex The index of child (of the View) above the divider.
   3344      *            This will be -1 if there is no child above the divider to be
   3345      *            drawn.
   3346      */
   3347     void drawDivider(Canvas canvas, Rect bounds, int childIndex) {
   3348         // This widget draws the same divider for all children
   3349         final Drawable divider = mDivider;
   3350 
   3351         divider.setBounds(bounds);
   3352         divider.draw(canvas);
   3353     }
   3354 
   3355     /**
   3356      * Returns the drawable that will be drawn between each item in the list.
   3357      *
   3358      * @return the current drawable drawn between list elements
   3359      */
   3360     public Drawable getDivider() {
   3361         return mDivider;
   3362     }
   3363 
   3364     /**
   3365      * Sets the drawable that will be drawn between each item in the list. If the drawable does
   3366      * not have an intrinsic height, you should also call {@link #setDividerHeight(int)}
   3367      *
   3368      * @param divider The drawable to use.
   3369      */
   3370     public void setDivider(Drawable divider) {
   3371         if (divider != null) {
   3372             mDividerHeight = divider.getIntrinsicHeight();
   3373         } else {
   3374             mDividerHeight = 0;
   3375         }
   3376         mDivider = divider;
   3377         mDividerIsOpaque = divider == null || divider.getOpacity() == PixelFormat.OPAQUE;
   3378         requestLayout();
   3379         invalidate();
   3380     }
   3381 
   3382     /**
   3383      * @return Returns the height of the divider that will be drawn between each item in the list.
   3384      */
   3385     public int getDividerHeight() {
   3386         return mDividerHeight;
   3387     }
   3388 
   3389     /**
   3390      * Sets the height of the divider that will be drawn between each item in the list. Calling
   3391      * this will override the intrinsic height as set by {@link #setDivider(Drawable)}
   3392      *
   3393      * @param height The new height of the divider in pixels.
   3394      */
   3395     public void setDividerHeight(int height) {
   3396         mDividerHeight = height;
   3397         requestLayout();
   3398         invalidate();
   3399     }
   3400 
   3401     /**
   3402      * Enables or disables the drawing of the divider for header views.
   3403      *
   3404      * @param headerDividersEnabled True to draw the headers, false otherwise.
   3405      *
   3406      * @see #setFooterDividersEnabled(boolean)
   3407      * @see #addHeaderView(android.view.View)
   3408      */
   3409     public void setHeaderDividersEnabled(boolean headerDividersEnabled) {
   3410         mHeaderDividersEnabled = headerDividersEnabled;
   3411         invalidate();
   3412     }
   3413 
   3414     /**
   3415      * Enables or disables the drawing of the divider for footer views.
   3416      *
   3417      * @param footerDividersEnabled True to draw the footers, false otherwise.
   3418      *
   3419      * @see #setHeaderDividersEnabled(boolean)
   3420      * @see #addFooterView(android.view.View)
   3421      */
   3422     public void setFooterDividersEnabled(boolean footerDividersEnabled) {
   3423         mFooterDividersEnabled = footerDividersEnabled;
   3424         invalidate();
   3425     }
   3426 
   3427     /**
   3428      * Sets the drawable that will be drawn above all other list content.
   3429      * This area can become visible when the user overscrolls the list.
   3430      *
   3431      * @param header The drawable to use
   3432      */
   3433     public void setOverscrollHeader(Drawable header) {
   3434         mOverScrollHeader = header;
   3435         if (mScrollY < 0) {
   3436             invalidate();
   3437         }
   3438     }
   3439 
   3440     /**
   3441      * @return The drawable that will be drawn above all other list content
   3442      */
   3443     public Drawable getOverscrollHeader() {
   3444         return mOverScrollHeader;
   3445     }
   3446 
   3447     /**
   3448      * Sets the drawable that will be drawn below all other list content.
   3449      * This area can become visible when the user overscrolls the list,
   3450      * or when the list's content does not fully fill the container area.
   3451      *
   3452      * @param footer The drawable to use
   3453      */
   3454     public void setOverscrollFooter(Drawable footer) {
   3455         mOverScrollFooter = footer;
   3456         invalidate();
   3457     }
   3458 
   3459     /**
   3460      * @return The drawable that will be drawn below all other list content
   3461      */
   3462     public Drawable getOverscrollFooter() {
   3463         return mOverScrollFooter;
   3464     }
   3465 
   3466     @Override
   3467     protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
   3468         super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
   3469 
   3470         final ListAdapter adapter = mAdapter;
   3471         int closetChildIndex = -1;
   3472         int closestChildTop = 0;
   3473         if (adapter != null && gainFocus && previouslyFocusedRect != null) {
   3474             previouslyFocusedRect.offset(mScrollX, mScrollY);
   3475 
   3476             // Don't cache the result of getChildCount or mFirstPosition here,
   3477             // it could change in layoutChildren.
   3478             if (adapter.getCount() < getChildCount() + mFirstPosition) {
   3479                 mLayoutMode = LAYOUT_NORMAL;
   3480                 layoutChildren();
   3481             }
   3482 
   3483             // figure out which item should be selected based on previously
   3484             // focused rect
   3485             Rect otherRect = mTempRect;
   3486             int minDistance = Integer.MAX_VALUE;
   3487             final int childCount = getChildCount();
   3488             final int firstPosition = mFirstPosition;
   3489 
   3490             for (int i = 0; i < childCount; i++) {
   3491                 // only consider selectable views
   3492                 if (!adapter.isEnabled(firstPosition + i)) {
   3493                     continue;
   3494                 }
   3495 
   3496                 View other = getChildAt(i);
   3497                 other.getDrawingRect(otherRect);
   3498                 offsetDescendantRectToMyCoords(other, otherRect);
   3499                 int distance = getDistance(previouslyFocusedRect, otherRect, direction);
   3500 
   3501                 if (distance < minDistance) {
   3502                     minDistance = distance;
   3503                     closetChildIndex = i;
   3504                     closestChildTop = other.getTop();
   3505                 }
   3506             }
   3507         }
   3508 
   3509         if (closetChildIndex >= 0) {
   3510             setSelectionFromTop(closetChildIndex + mFirstPosition, closestChildTop);
   3511         } else {
   3512             requestLayout();
   3513         }
   3514     }
   3515 
   3516 
   3517     /*
   3518      * (non-Javadoc)
   3519      *
   3520      * Children specified in XML are assumed to be header views. After we have
   3521      * parsed them move them out of the children list and into mHeaderViews.
   3522      */
   3523     @Override
   3524     protected void onFinishInflate() {
   3525         super.onFinishInflate();
   3526 
   3527         int count = getChildCount();
   3528         if (count > 0) {
   3529             for (int i = 0; i < count; ++i) {
   3530                 addHeaderView(getChildAt(i));
   3531             }
   3532             removeAllViews();
   3533         }
   3534     }
   3535 
   3536     /* (non-Javadoc)
   3537      * @see android.view.View#findViewById(int)
   3538      * First look in our children, then in any header and footer views that may be scrolled off.
   3539      */
   3540     @Override
   3541     protected View findViewTraversal(int id) {
   3542         View v;
   3543         v = super.findViewTraversal(id);
   3544         if (v == null) {
   3545             v = findViewInHeadersOrFooters(mHeaderViewInfos, id);
   3546             if (v != null) {
   3547                 return v;
   3548             }
   3549             v = findViewInHeadersOrFooters(mFooterViewInfos, id);
   3550             if (v != null) {
   3551                 return v;
   3552             }
   3553         }
   3554         return v;
   3555     }
   3556 
   3557     /* (non-Javadoc)
   3558      *
   3559      * Look in the passed in list of headers or footers for the view.
   3560      */
   3561     View findViewInHeadersOrFooters(ArrayList<FixedViewInfo> where, int id) {
   3562         if (where != null) {
   3563             int len = where.size();
   3564             View v;
   3565 
   3566             for (int i = 0; i < len; i++) {
   3567                 v = where.get(i).view;
   3568 
   3569                 if (!v.isRootNamespace()) {
   3570                     v = v.findViewById(id);
   3571 
   3572                     if (v != null) {
   3573                         return v;
   3574                     }
   3575                 }
   3576             }
   3577         }
   3578         return null;
   3579     }
   3580 
   3581     /* (non-Javadoc)
   3582      * @see android.view.View#findViewWithTag(Object)
   3583      * First look in our children, then in any header and footer views that may be scrolled off.
   3584      */
   3585     @Override
   3586     protected View findViewWithTagTraversal(Object tag) {
   3587         View v;
   3588         v = super.findViewWithTagTraversal(tag);
   3589         if (v == null) {
   3590             v = findViewWithTagInHeadersOrFooters(mHeaderViewInfos, tag);
   3591             if (v != null) {
   3592                 return v;
   3593             }
   3594 
   3595             v = findViewWithTagInHeadersOrFooters(mFooterViewInfos, tag);
   3596             if (v != null) {
   3597                 return v;
   3598             }
   3599         }
   3600         return v;
   3601     }
   3602 
   3603     /* (non-Javadoc)
   3604      *
   3605      * Look in the passed in list of headers or footers for the view with the tag.
   3606      */
   3607     View findViewWithTagInHeadersOrFooters(ArrayList<FixedViewInfo> where, Object tag) {
   3608         if (where != null) {
   3609             int len = where.size();
   3610             View v;
   3611 
   3612             for (int i = 0; i < len; i++) {
   3613                 v = where.get(i).view;
   3614 
   3615                 if (!v.isRootNamespace()) {
   3616                     v = v.findViewWithTag(tag);
   3617 
   3618                     if (v != null) {
   3619                         return v;
   3620                     }
   3621                 }
   3622             }
   3623         }
   3624         return null;
   3625     }
   3626 
   3627     /**
   3628      * @hide
   3629      * @see android.view.View#findViewByPredicate(Predicate)
   3630      * First look in our children, then in any header and footer views that may be scrolled off.
   3631      */
   3632     @Override
   3633     protected View findViewByPredicateTraversal(Predicate<View> predicate, View childToSkip) {
   3634         View v;
   3635         v = super.findViewByPredicateTraversal(predicate, childToSkip);
   3636         if (v == null) {
   3637             v = findViewByPredicateInHeadersOrFooters(mHeaderViewInfos, predicate, childToSkip);
   3638             if (v != null) {
   3639                 return v;
   3640             }
   3641 
   3642             v = findViewByPredicateInHeadersOrFooters(mFooterViewInfos, predicate, childToSkip);
   3643             if (v != null) {
   3644                 return v;
   3645             }
   3646         }
   3647         return v;
   3648     }
   3649 
   3650     /* (non-Javadoc)
   3651      *
   3652      * Look in the passed in list of headers or footers for the first view that matches
   3653      * the predicate.
   3654      */
   3655     View findViewByPredicateInHeadersOrFooters(ArrayList<FixedViewInfo> where,
   3656             Predicate<View> predicate, View childToSkip) {
   3657         if (where != null) {
   3658             int len = where.size();
   3659             View v;
   3660 
   3661             for (int i = 0; i < len; i++) {
   3662                 v = where.get(i).view;
   3663 
   3664                 if (v != childToSkip && !v.isRootNamespace()) {
   3665                     v = v.findViewByPredicate(predicate);
   3666 
   3667                     if (v != null) {
   3668                         return v;
   3669                     }
   3670                 }
   3671             }
   3672         }
   3673         return null;
   3674     }
   3675 
   3676     /**
   3677      * Returns the set of checked items ids. The result is only valid if the
   3678      * choice mode has not been set to {@link #CHOICE_MODE_NONE}.
   3679      *
   3680      * @return A new array which contains the id of each checked item in the
   3681      *         list.
   3682      *
   3683      * @deprecated Use {@link #getCheckedItemIds()} instead.
   3684      */
   3685     @Deprecated
   3686     public long[] getCheckItemIds() {
   3687         // Use new behavior that correctly handles stable ID mapping.
   3688         if (mAdapter != null && mAdapter.hasStableIds()) {
   3689             return getCheckedItemIds();
   3690         }
   3691 
   3692         // Old behavior was buggy, but would sort of work for adapters without stable IDs.
   3693         // Fall back to it to support legacy apps.
   3694         if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null && mAdapter != null) {
   3695             final SparseBooleanArray states = mCheckStates;
   3696             final int count = states.size();
   3697             final long[] ids = new long[count];
   3698             final ListAdapter adapter = mAdapter;
   3699 
   3700             int checkedCount = 0;
   3701             for (int i = 0; i < count; i++) {
   3702                 if (states.valueAt(i)) {
   3703                     ids[checkedCount++] = adapter.getItemId(states.keyAt(i));
   3704                 }
   3705             }
   3706 
   3707             // Trim array if needed. mCheckStates may contain false values
   3708             // resulting in checkedCount being smaller than count.
   3709             if (checkedCount == count) {
   3710                 return ids;
   3711             } else {
   3712                 final long[] result = new long[checkedCount];
   3713                 System.arraycopy(ids, 0, result, 0, checkedCount);
   3714 
   3715                 return result;
   3716             }
   3717         }
   3718         return new long[0];
   3719     }
   3720 
   3721     @Override
   3722     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
   3723         super.onInitializeAccessibilityEvent(event);
   3724         event.setClassName(ListView.class.getName());
   3725     }
   3726 
   3727     @Override
   3728     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
   3729         super.onInitializeAccessibilityNodeInfo(info);
   3730         info.setClassName(ListView.class.getName());
   3731     }
   3732 }
   3733