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