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