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