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