• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2006 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.widget;
18 
19 import android.annotation.ColorInt;
20 import android.annotation.DrawableRes;
21 import android.annotation.NonNull;
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.Rect;
27 import android.graphics.drawable.Drawable;
28 import android.graphics.drawable.TransitionDrawable;
29 import android.os.Bundle;
30 import android.os.Debug;
31 import android.os.Parcel;
32 import android.os.Parcelable;
33 import android.os.StrictMode;
34 import android.os.Trace;
35 import android.text.Editable;
36 import android.text.InputType;
37 import android.text.TextUtils;
38 import android.text.TextWatcher;
39 import android.util.AttributeSet;
40 import android.util.Log;
41 import android.util.LongSparseArray;
42 import android.util.SparseArray;
43 import android.util.SparseBooleanArray;
44 import android.util.StateSet;
45 import android.view.ActionMode;
46 import android.view.ContextMenu.ContextMenuInfo;
47 import android.view.Gravity;
48 import android.view.HapticFeedbackConstants;
49 import android.view.InputDevice;
50 import android.view.KeyEvent;
51 import android.view.LayoutInflater;
52 import android.view.Menu;
53 import android.view.MenuItem;
54 import android.view.MotionEvent;
55 import android.view.VelocityTracker;
56 import android.view.View;
57 import android.view.ViewConfiguration;
58 import android.view.ViewDebug;
59 import android.view.ViewGroup;
60 import android.view.ViewHierarchyEncoder;
61 import android.view.ViewParent;
62 import android.view.ViewTreeObserver;
63 import android.view.accessibility.AccessibilityEvent;
64 import android.view.accessibility.AccessibilityManager;
65 import android.view.accessibility.AccessibilityNodeInfo;
66 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
67 import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
68 import android.view.animation.Interpolator;
69 import android.view.animation.LinearInterpolator;
70 import android.view.inputmethod.BaseInputConnection;
71 import android.view.inputmethod.CompletionInfo;
72 import android.view.inputmethod.CorrectionInfo;
73 import android.view.inputmethod.EditorInfo;
74 import android.view.inputmethod.ExtractedText;
75 import android.view.inputmethod.ExtractedTextRequest;
76 import android.view.inputmethod.InputConnection;
77 import android.view.inputmethod.InputMethodManager;
78 import android.widget.RemoteViews.OnClickHandler;
79 
80 import com.android.internal.R;
81 
82 import java.util.ArrayList;
83 import java.util.List;
84 
85 /**
86  * Base class that can be used to implement virtualized lists of items. A list does
87  * not have a spatial definition here. For instance, subclases of this class can
88  * display the content of the list in a grid, in a carousel, as stack, etc.
89  *
90  * @attr ref android.R.styleable#AbsListView_listSelector
91  * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop
92  * @attr ref android.R.styleable#AbsListView_stackFromBottom
93  * @attr ref android.R.styleable#AbsListView_scrollingCache
94  * @attr ref android.R.styleable#AbsListView_textFilterEnabled
95  * @attr ref android.R.styleable#AbsListView_transcriptMode
96  * @attr ref android.R.styleable#AbsListView_cacheColorHint
97  * @attr ref android.R.styleable#AbsListView_fastScrollEnabled
98  * @attr ref android.R.styleable#AbsListView_smoothScrollbar
99  * @attr ref android.R.styleable#AbsListView_choiceMode
100  */
101 public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher,
102         ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener,
103         ViewTreeObserver.OnTouchModeChangeListener,
104         RemoteViewsAdapter.RemoteAdapterConnectionCallback {
105 
106     @SuppressWarnings("UnusedDeclaration")
107     private static final String TAG = "AbsListView";
108 
109     /**
110      * Disables the transcript mode.
111      *
112      * @see #setTranscriptMode(int)
113      */
114     public static final int TRANSCRIPT_MODE_DISABLED = 0;
115 
116     /**
117      * The list will automatically scroll to the bottom when a data set change
118      * notification is received and only if the last item is already visible
119      * on screen.
120      *
121      * @see #setTranscriptMode(int)
122      */
123     public static final int TRANSCRIPT_MODE_NORMAL = 1;
124 
125     /**
126      * The list will automatically scroll to the bottom, no matter what items
127      * are currently visible.
128      *
129      * @see #setTranscriptMode(int)
130      */
131     public static final int TRANSCRIPT_MODE_ALWAYS_SCROLL = 2;
132 
133     /**
134      * Indicates that we are not in the middle of a touch gesture
135      */
136     static final int TOUCH_MODE_REST = -1;
137 
138     /**
139      * Indicates we just received the touch event and we are waiting to see if the it is a tap or a
140      * scroll gesture.
141      */
142     static final int TOUCH_MODE_DOWN = 0;
143 
144     /**
145      * Indicates the touch has been recognized as a tap and we are now waiting to see if the touch
146      * is a longpress
147      */
148     static final int TOUCH_MODE_TAP = 1;
149 
150     /**
151      * Indicates we have waited for everything we can wait for, but the user's finger is still down
152      */
153     static final int TOUCH_MODE_DONE_WAITING = 2;
154 
155     /**
156      * Indicates the touch gesture is a scroll
157      */
158     static final int TOUCH_MODE_SCROLL = 3;
159 
160     /**
161      * Indicates the view is in the process of being flung
162      */
163     static final int TOUCH_MODE_FLING = 4;
164 
165     /**
166      * Indicates the touch gesture is an overscroll - a scroll beyond the beginning or end.
167      */
168     static final int TOUCH_MODE_OVERSCROLL = 5;
169 
170     /**
171      * Indicates the view is being flung outside of normal content bounds
172      * and will spring back.
173      */
174     static final int TOUCH_MODE_OVERFLING = 6;
175 
176     /**
177      * Regular layout - usually an unsolicited layout from the view system
178      */
179     static final int LAYOUT_NORMAL = 0;
180 
181     /**
182      * Show the first item
183      */
184     static final int LAYOUT_FORCE_TOP = 1;
185 
186     /**
187      * Force the selected item to be on somewhere on the screen
188      */
189     static final int LAYOUT_SET_SELECTION = 2;
190 
191     /**
192      * Show the last item
193      */
194     static final int LAYOUT_FORCE_BOTTOM = 3;
195 
196     /**
197      * Make a mSelectedItem appear in a specific location and build the rest of
198      * the views from there. The top is specified by mSpecificTop.
199      */
200     static final int LAYOUT_SPECIFIC = 4;
201 
202     /**
203      * Layout to sync as a result of a data change. Restore mSyncPosition to have its top
204      * at mSpecificTop
205      */
206     static final int LAYOUT_SYNC = 5;
207 
208     /**
209      * Layout as a result of using the navigation keys
210      */
211     static final int LAYOUT_MOVE_SELECTION = 6;
212 
213     /**
214      * Normal list that does not indicate choices
215      */
216     public static final int CHOICE_MODE_NONE = 0;
217 
218     /**
219      * The list allows up to one choice
220      */
221     public static final int CHOICE_MODE_SINGLE = 1;
222 
223     /**
224      * The list allows multiple choices
225      */
226     public static final int CHOICE_MODE_MULTIPLE = 2;
227 
228     /**
229      * The list allows multiple choices in a modal selection mode
230      */
231     public static final int CHOICE_MODE_MULTIPLE_MODAL = 3;
232 
233     /**
234      * The thread that created this view.
235      */
236     private final Thread mOwnerThread;
237 
238     /**
239      * Controls if/how the user may choose/check items in the list
240      */
241     int mChoiceMode = CHOICE_MODE_NONE;
242 
243     /**
244      * Controls CHOICE_MODE_MULTIPLE_MODAL. null when inactive.
245      */
246     ActionMode mChoiceActionMode;
247 
248     /**
249      * Wrapper for the multiple choice mode callback; AbsListView needs to perform
250      * a few extra actions around what application code does.
251      */
252     MultiChoiceModeWrapper mMultiChoiceModeCallback;
253 
254     /**
255      * Running count of how many items are currently checked
256      */
257     int mCheckedItemCount;
258 
259     /**
260      * Running state of which positions are currently checked
261      */
262     SparseBooleanArray mCheckStates;
263 
264     /**
265      * Running state of which IDs are currently checked.
266      * If there is a value for a given key, the checked state for that ID is true
267      * and the value holds the last known position in the adapter for that id.
268      */
269     LongSparseArray<Integer> mCheckedIdStates;
270 
271     /**
272      * Controls how the next layout will happen
273      */
274     int mLayoutMode = LAYOUT_NORMAL;
275 
276     /**
277      * Should be used by subclasses to listen to changes in the dataset
278      */
279     AdapterDataSetObserver mDataSetObserver;
280 
281     /**
282      * The adapter containing the data to be displayed by this view
283      */
284     ListAdapter mAdapter;
285 
286     /**
287      * The remote adapter containing the data to be displayed by this view to be set
288      */
289     private RemoteViewsAdapter mRemoteAdapter;
290 
291     /**
292      * If mAdapter != null, whenever this is true the adapter has stable IDs.
293      */
294     boolean mAdapterHasStableIds;
295 
296     /**
297      * This flag indicates the a full notify is required when the RemoteViewsAdapter connects
298      */
299     private boolean mDeferNotifyDataSetChanged = false;
300 
301     /**
302      * Indicates whether the list selector should be drawn on top of the children or behind
303      */
304     boolean mDrawSelectorOnTop = false;
305 
306     /**
307      * The drawable used to draw the selector
308      */
309     Drawable mSelector;
310 
311     /**
312      * The current position of the selector in the list.
313      */
314     int mSelectorPosition = INVALID_POSITION;
315 
316     /**
317      * Defines the selector's location and dimension at drawing time
318      */
319     Rect mSelectorRect = new Rect();
320 
321     /**
322      * The data set used to store unused views that should be reused during the next layout
323      * to avoid creating new ones
324      */
325     final RecycleBin mRecycler = new RecycleBin();
326 
327     /**
328      * The selection's left padding
329      */
330     int mSelectionLeftPadding = 0;
331 
332     /**
333      * The selection's top padding
334      */
335     int mSelectionTopPadding = 0;
336 
337     /**
338      * The selection's right padding
339      */
340     int mSelectionRightPadding = 0;
341 
342     /**
343      * The selection's bottom padding
344      */
345     int mSelectionBottomPadding = 0;
346 
347     /**
348      * This view's padding
349      */
350     Rect mListPadding = new Rect();
351 
352     /**
353      * Subclasses must retain their measure spec from onMeasure() into this member
354      */
355     int mWidthMeasureSpec = 0;
356 
357     /**
358      * The top scroll indicator
359      */
360     View mScrollUp;
361 
362     /**
363      * The down scroll indicator
364      */
365     View mScrollDown;
366 
367     /**
368      * When the view is scrolling, this flag is set to true to indicate subclasses that
369      * the drawing cache was enabled on the children
370      */
371     boolean mCachingStarted;
372     boolean mCachingActive;
373 
374     /**
375      * The position of the view that received the down motion event
376      */
377     int mMotionPosition;
378 
379     /**
380      * The offset to the top of the mMotionPosition view when the down motion event was received
381      */
382     int mMotionViewOriginalTop;
383 
384     /**
385      * The desired offset to the top of the mMotionPosition view after a scroll
386      */
387     int mMotionViewNewTop;
388 
389     /**
390      * The X value associated with the the down motion event
391      */
392     int mMotionX;
393 
394     /**
395      * The Y value associated with the the down motion event
396      */
397     int mMotionY;
398 
399     /**
400      * One of TOUCH_MODE_REST, TOUCH_MODE_DOWN, TOUCH_MODE_TAP, TOUCH_MODE_SCROLL, or
401      * TOUCH_MODE_DONE_WAITING
402      */
403     int mTouchMode = TOUCH_MODE_REST;
404 
405     /**
406      * Y value from on the previous motion event (if any)
407      */
408     int mLastY;
409 
410     /**
411      * How far the finger moved before we started scrolling
412      */
413     int mMotionCorrection;
414 
415     /**
416      * Determines speed during touch scrolling
417      */
418     private VelocityTracker mVelocityTracker;
419 
420     /**
421      * Handles one frame of a fling
422      */
423     private FlingRunnable mFlingRunnable;
424 
425     /**
426      * Handles scrolling between positions within the list.
427      */
428     AbsPositionScroller mPositionScroller;
429 
430     /**
431      * The offset in pixels form the top of the AdapterView to the top
432      * of the currently selected view. Used to save and restore state.
433      */
434     int mSelectedTop = 0;
435 
436     /**
437      * Indicates whether the list is stacked from the bottom edge or
438      * the top edge.
439      */
440     boolean mStackFromBottom;
441 
442     /**
443      * When set to true, the list automatically discards the children's
444      * bitmap cache after scrolling.
445      */
446     boolean mScrollingCacheEnabled;
447 
448     /**
449      * Whether or not to enable the fast scroll feature on this list
450      */
451     boolean mFastScrollEnabled;
452 
453     /**
454      * Whether or not to always show the fast scroll feature on this list
455      */
456     boolean mFastScrollAlwaysVisible;
457 
458     /**
459      * Optional callback to notify client when scroll position has changed
460      */
461     private OnScrollListener mOnScrollListener;
462 
463     /**
464      * Keeps track of our accessory window
465      */
466     PopupWindow mPopup;
467 
468     /**
469      * Used with type filter window
470      */
471     EditText mTextFilter;
472 
473     /**
474      * Indicates whether to use pixels-based or position-based scrollbar
475      * properties.
476      */
477     private boolean mSmoothScrollbarEnabled = true;
478 
479     /**
480      * Indicates that this view supports filtering
481      */
482     private boolean mTextFilterEnabled;
483 
484     /**
485      * Indicates that this view is currently displaying a filtered view of the data
486      */
487     private boolean mFiltered;
488 
489     /**
490      * Rectangle used for hit testing children
491      */
492     private Rect mTouchFrame;
493 
494     /**
495      * The position to resurrect the selected position to.
496      */
497     int mResurrectToPosition = INVALID_POSITION;
498 
499     private ContextMenuInfo mContextMenuInfo = null;
500 
501     /**
502      * Maximum distance to record overscroll
503      */
504     int mOverscrollMax;
505 
506     /**
507      * Content height divided by this is the overscroll limit.
508      */
509     static final int OVERSCROLL_LIMIT_DIVISOR = 3;
510 
511     /**
512      * How many positions in either direction we will search to try to
513      * find a checked item with a stable ID that moved position across
514      * a data set change. If the item isn't found it will be unselected.
515      */
516     private static final int CHECK_POSITION_SEARCH_DISTANCE = 20;
517 
518     /**
519      * Used to request a layout when we changed touch mode
520      */
521     private static final int TOUCH_MODE_UNKNOWN = -1;
522     private static final int TOUCH_MODE_ON = 0;
523     private static final int TOUCH_MODE_OFF = 1;
524 
525     private int mLastTouchMode = TOUCH_MODE_UNKNOWN;
526 
527     private static final boolean PROFILE_SCROLLING = false;
528     private boolean mScrollProfilingStarted = false;
529 
530     private static final boolean PROFILE_FLINGING = false;
531     private boolean mFlingProfilingStarted = false;
532 
533     /**
534      * The StrictMode "critical time span" objects to catch animation
535      * stutters.  Non-null when a time-sensitive animation is
536      * in-flight.  Must call finish() on them when done animating.
537      * These are no-ops on user builds.
538      */
539     private StrictMode.Span mScrollStrictSpan = null;
540     private StrictMode.Span mFlingStrictSpan = null;
541 
542     /**
543      * The last CheckForLongPress runnable we posted, if any
544      */
545     private CheckForLongPress mPendingCheckForLongPress;
546 
547     /**
548      * The last CheckForTap runnable we posted, if any
549      */
550     private CheckForTap mPendingCheckForTap;
551 
552     /**
553      * The last CheckForKeyLongPress runnable we posted, if any
554      */
555     private CheckForKeyLongPress mPendingCheckForKeyLongPress;
556 
557     /**
558      * Acts upon click
559      */
560     private AbsListView.PerformClick mPerformClick;
561 
562     /**
563      * Delayed action for touch mode.
564      */
565     private Runnable mTouchModeReset;
566 
567     /**
568      * This view is in transcript mode -- it shows the bottom of the list when the data
569      * changes
570      */
571     private int mTranscriptMode;
572 
573     /**
574      * Indicates that this list is always drawn on top of a solid, single-color, opaque
575      * background
576      */
577     private int mCacheColorHint;
578 
579     /**
580      * The select child's view (from the adapter's getView) is enabled.
581      */
582     private boolean mIsChildViewEnabled;
583 
584     /**
585      * The cached drawable state for the selector. Accounts for child enabled
586      * state, but otherwise identical to the view's own drawable state.
587      */
588     private int[] mSelectorState;
589 
590     /**
591      * The last scroll state reported to clients through {@link OnScrollListener}.
592      */
593     private int mLastScrollState = OnScrollListener.SCROLL_STATE_IDLE;
594 
595     /**
596      * Helper object that renders and controls the fast scroll thumb.
597      */
598     private FastScroller mFastScroll;
599 
600     /**
601      * Temporary holder for fast scroller style until a FastScroller object
602      * is created.
603      */
604     private int mFastScrollStyle;
605 
606     private boolean mGlobalLayoutListenerAddedFilter;
607 
608     private int mTouchSlop;
609     private float mDensityScale;
610 
611     private InputConnection mDefInputConnection;
612     private InputConnectionWrapper mPublicInputConnection;
613 
614     private Runnable mClearScrollingCache;
615     Runnable mPositionScrollAfterLayout;
616     private int mMinimumVelocity;
617     private int mMaximumVelocity;
618     private float mVelocityScale = 1.0f;
619 
620     final boolean[] mIsScrap = new boolean[1];
621 
622     private final int[] mScrollOffset = new int[2];
623     private final int[] mScrollConsumed = new int[2];
624 
625     private final float[] mTmpPoint = new float[2];
626 
627     // Used for offsetting MotionEvents that we feed to the VelocityTracker.
628     // In the future it would be nice to be able to give this to the VelocityTracker
629     // directly, or alternatively put a VT into absolute-positioning mode that only
630     // reads the raw screen-coordinate x/y values.
631     private int mNestedYOffset = 0;
632 
633     // True when the popup should be hidden because of a call to
634     // dispatchDisplayHint()
635     private boolean mPopupHidden;
636 
637     /**
638      * ID of the active pointer. This is used to retain consistency during
639      * drags/flings if multiple pointers are used.
640      */
641     private int mActivePointerId = INVALID_POINTER;
642 
643     /**
644      * Sentinel value for no current active pointer.
645      * Used by {@link #mActivePointerId}.
646      */
647     private static final int INVALID_POINTER = -1;
648 
649     /**
650      * Maximum distance to overscroll by during edge effects
651      */
652     int mOverscrollDistance;
653 
654     /**
655      * Maximum distance to overfling during edge effects
656      */
657     int mOverflingDistance;
658 
659     // These two EdgeGlows are always set and used together.
660     // Checking one for null is as good as checking both.
661 
662     /**
663      * Tracks the state of the top edge glow.
664      */
665     private EdgeEffect mEdgeGlowTop;
666 
667     /**
668      * Tracks the state of the bottom edge glow.
669      */
670     private EdgeEffect mEdgeGlowBottom;
671 
672     /**
673      * An estimate of how many pixels are between the top of the list and
674      * the top of the first position in the adapter, based on the last time
675      * we saw it. Used to hint where to draw edge glows.
676      */
677     private int mFirstPositionDistanceGuess;
678 
679     /**
680      * An estimate of how many pixels are between the bottom of the list and
681      * the bottom of the last position in the adapter, based on the last time
682      * we saw it. Used to hint where to draw edge glows.
683      */
684     private int mLastPositionDistanceGuess;
685 
686     /**
687      * Used for determining when to cancel out of overscroll.
688      */
689     private int mDirection = 0;
690 
691     /**
692      * Tracked on measurement in transcript mode. Makes sure that we can still pin to
693      * the bottom correctly on resizes.
694      */
695     private boolean mForceTranscriptScroll;
696 
697     /**
698      * Used for interacting with list items from an accessibility service.
699      */
700     private ListItemAccessibilityDelegate mAccessibilityDelegate;
701 
702     private int mLastAccessibilityScrollEventFromIndex;
703     private int mLastAccessibilityScrollEventToIndex;
704 
705     /**
706      * Track the item count from the last time we handled a data change.
707      */
708     private int mLastHandledItemCount;
709 
710     /**
711      * Used for smooth scrolling at a consistent rate
712      */
713     static final Interpolator sLinearInterpolator = new LinearInterpolator();
714 
715     /**
716      * The saved state that we will be restoring from when we next sync.
717      * Kept here so that if we happen to be asked to save our state before
718      * the sync happens, we can return this existing data rather than losing
719      * it.
720      */
721     private SavedState mPendingSync;
722 
723     /**
724      * Whether the view is in the process of detaching from its window.
725      */
726     private boolean mIsDetaching;
727 
728     /**
729      * Interface definition for a callback to be invoked when the list or grid
730      * has been scrolled.
731      */
732     public interface OnScrollListener {
733 
734         /**
735          * The view is not scrolling. Note navigating the list using the trackball counts as
736          * being in the idle state since these transitions are not animated.
737          */
738         public static int SCROLL_STATE_IDLE = 0;
739 
740         /**
741          * The user is scrolling using touch, and their finger is still on the screen
742          */
743         public static int SCROLL_STATE_TOUCH_SCROLL = 1;
744 
745         /**
746          * The user had previously been scrolling using touch and had performed a fling. The
747          * animation is now coasting to a stop
748          */
749         public static int SCROLL_STATE_FLING = 2;
750 
751         /**
752          * Callback method to be invoked while the list view or grid view is being scrolled. If the
753          * view is being scrolled, this method will be called before the next frame of the scroll is
754          * rendered. In particular, it will be called before any calls to
755          * {@link Adapter#getView(int, View, ViewGroup)}.
756          *
757          * @param view The view whose scroll state is being reported
758          *
759          * @param scrollState The current scroll state. One of
760          * {@link #SCROLL_STATE_TOUCH_SCROLL} or {@link #SCROLL_STATE_IDLE}.
761          */
onScrollStateChanged(AbsListView view, int scrollState)762         public void onScrollStateChanged(AbsListView view, int scrollState);
763 
764         /**
765          * Callback method to be invoked when the list or grid has been scrolled. This will be
766          * called after the scroll has completed
767          * @param view The view whose scroll state is being reported
768          * @param firstVisibleItem the index of the first visible cell (ignore if
769          *        visibleItemCount == 0)
770          * @param visibleItemCount the number of visible cells
771          * @param totalItemCount the number of items in the list adaptor
772          */
onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)773         public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
774                 int totalItemCount);
775     }
776 
777     /**
778      * The top-level view of a list item can implement this interface to allow
779      * itself to modify the bounds of the selection shown for that item.
780      */
781     public interface SelectionBoundsAdjuster {
782         /**
783          * Called to allow the list item to adjust the bounds shown for
784          * its selection.
785          *
786          * @param bounds On call, this contains the bounds the list has
787          * selected for the item (that is the bounds of the entire view).  The
788          * values can be modified as desired.
789          */
adjustListItemSelectionBounds(Rect bounds)790         public void adjustListItemSelectionBounds(Rect bounds);
791     }
792 
AbsListView(Context context)793     public AbsListView(Context context) {
794         super(context);
795         initAbsListView();
796 
797         mOwnerThread = Thread.currentThread();
798 
799         setVerticalScrollBarEnabled(true);
800         TypedArray a = context.obtainStyledAttributes(R.styleable.View);
801         initializeScrollbarsInternal(a);
802         a.recycle();
803     }
804 
AbsListView(Context context, AttributeSet attrs)805     public AbsListView(Context context, AttributeSet attrs) {
806         this(context, attrs, com.android.internal.R.attr.absListViewStyle);
807     }
808 
AbsListView(Context context, AttributeSet attrs, int defStyleAttr)809     public AbsListView(Context context, AttributeSet attrs, int defStyleAttr) {
810         this(context, attrs, defStyleAttr, 0);
811     }
812 
AbsListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)813     public AbsListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
814         super(context, attrs, defStyleAttr, defStyleRes);
815         initAbsListView();
816 
817         mOwnerThread = Thread.currentThread();
818 
819         final TypedArray a = context.obtainStyledAttributes(
820                 attrs, R.styleable.AbsListView, defStyleAttr, defStyleRes);
821 
822         final Drawable selector = a.getDrawable(R.styleable.AbsListView_listSelector);
823         if (selector != null) {
824             setSelector(selector);
825         }
826 
827         mDrawSelectorOnTop = a.getBoolean(R.styleable.AbsListView_drawSelectorOnTop, false);
828 
829         setStackFromBottom(a.getBoolean(
830                 R.styleable.AbsListView_stackFromBottom, false));
831         setScrollingCacheEnabled(a.getBoolean(
832                 R.styleable.AbsListView_scrollingCache, true));
833         setTextFilterEnabled(a.getBoolean(
834                 R.styleable.AbsListView_textFilterEnabled, false));
835         setTranscriptMode(a.getInt(
836                 R.styleable.AbsListView_transcriptMode, TRANSCRIPT_MODE_DISABLED));
837         setCacheColorHint(a.getColor(
838                 R.styleable.AbsListView_cacheColorHint, 0));
839         setSmoothScrollbarEnabled(a.getBoolean(
840                 R.styleable.AbsListView_smoothScrollbar, true));
841         setChoiceMode(a.getInt(
842                 R.styleable.AbsListView_choiceMode, CHOICE_MODE_NONE));
843 
844         setFastScrollEnabled(a.getBoolean(
845                 R.styleable.AbsListView_fastScrollEnabled, false));
846         setFastScrollStyle(a.getResourceId(
847                 R.styleable.AbsListView_fastScrollStyle, 0));
848         setFastScrollAlwaysVisible(a.getBoolean(
849                 R.styleable.AbsListView_fastScrollAlwaysVisible, false));
850 
851         a.recycle();
852     }
853 
initAbsListView()854     private void initAbsListView() {
855         // Setting focusable in touch mode will set the focusable property to true
856         setClickable(true);
857         setFocusableInTouchMode(true);
858         setWillNotDraw(false);
859         setAlwaysDrawnWithCacheEnabled(false);
860         setScrollingCacheEnabled(true);
861 
862         final ViewConfiguration configuration = ViewConfiguration.get(mContext);
863         mTouchSlop = configuration.getScaledTouchSlop();
864         mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
865         mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
866         mOverscrollDistance = configuration.getScaledOverscrollDistance();
867         mOverflingDistance = configuration.getScaledOverflingDistance();
868 
869         mDensityScale = getContext().getResources().getDisplayMetrics().density;
870     }
871 
872     @Override
setOverScrollMode(int mode)873     public void setOverScrollMode(int mode) {
874         if (mode != OVER_SCROLL_NEVER) {
875             if (mEdgeGlowTop == null) {
876                 Context context = getContext();
877                 mEdgeGlowTop = new EdgeEffect(context);
878                 mEdgeGlowBottom = new EdgeEffect(context);
879             }
880         } else {
881             mEdgeGlowTop = null;
882             mEdgeGlowBottom = null;
883         }
884         super.setOverScrollMode(mode);
885     }
886 
887     /**
888      * {@inheritDoc}
889      */
890     @Override
setAdapter(ListAdapter adapter)891     public void setAdapter(ListAdapter adapter) {
892         if (adapter != null) {
893             mAdapterHasStableIds = mAdapter.hasStableIds();
894             if (mChoiceMode != CHOICE_MODE_NONE && mAdapterHasStableIds &&
895                     mCheckedIdStates == null) {
896                 mCheckedIdStates = new LongSparseArray<Integer>();
897             }
898         }
899 
900         if (mCheckStates != null) {
901             mCheckStates.clear();
902         }
903 
904         if (mCheckedIdStates != null) {
905             mCheckedIdStates.clear();
906         }
907     }
908 
909     /**
910      * Returns the number of items currently selected. This will only be valid
911      * if the choice mode is not {@link #CHOICE_MODE_NONE} (default).
912      *
913      * <p>To determine the specific items that are currently selected, use one of
914      * the <code>getChecked*</code> methods.
915      *
916      * @return The number of items currently selected
917      *
918      * @see #getCheckedItemPosition()
919      * @see #getCheckedItemPositions()
920      * @see #getCheckedItemIds()
921      */
getCheckedItemCount()922     public int getCheckedItemCount() {
923         return mCheckedItemCount;
924     }
925 
926     /**
927      * Returns the checked state of the specified position. The result is only
928      * valid if the choice mode has been set to {@link #CHOICE_MODE_SINGLE}
929      * or {@link #CHOICE_MODE_MULTIPLE}.
930      *
931      * @param position The item whose checked state to return
932      * @return The item's checked state or <code>false</code> if choice mode
933      *         is invalid
934      *
935      * @see #setChoiceMode(int)
936      */
isItemChecked(int position)937     public boolean isItemChecked(int position) {
938         if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
939             return mCheckStates.get(position);
940         }
941 
942         return false;
943     }
944 
945     /**
946      * Returns the currently checked item. The result is only valid if the choice
947      * mode has been set to {@link #CHOICE_MODE_SINGLE}.
948      *
949      * @return The position of the currently checked item or
950      *         {@link #INVALID_POSITION} if nothing is selected
951      *
952      * @see #setChoiceMode(int)
953      */
getCheckedItemPosition()954     public int getCheckedItemPosition() {
955         if (mChoiceMode == CHOICE_MODE_SINGLE && mCheckStates != null && mCheckStates.size() == 1) {
956             return mCheckStates.keyAt(0);
957         }
958 
959         return INVALID_POSITION;
960     }
961 
962     /**
963      * Returns the set of checked items in the list. The result is only valid if
964      * the choice mode has not been set to {@link #CHOICE_MODE_NONE}.
965      *
966      * @return  A SparseBooleanArray which will return true for each call to
967      *          get(int position) where position is a checked position in the
968      *          list and false otherwise, or <code>null</code> if the choice
969      *          mode is set to {@link #CHOICE_MODE_NONE}.
970      */
getCheckedItemPositions()971     public SparseBooleanArray getCheckedItemPositions() {
972         if (mChoiceMode != CHOICE_MODE_NONE) {
973             return mCheckStates;
974         }
975         return null;
976     }
977 
978     /**
979      * Returns the set of checked items ids. The result is only valid if the
980      * choice mode has not been set to {@link #CHOICE_MODE_NONE} and the adapter
981      * has stable IDs. ({@link ListAdapter#hasStableIds()} == {@code true})
982      *
983      * @return A new array which contains the id of each checked item in the
984      *         list.
985      */
getCheckedItemIds()986     public long[] getCheckedItemIds() {
987         if (mChoiceMode == CHOICE_MODE_NONE || mCheckedIdStates == null || mAdapter == null) {
988             return new long[0];
989         }
990 
991         final LongSparseArray<Integer> idStates = mCheckedIdStates;
992         final int count = idStates.size();
993         final long[] ids = new long[count];
994 
995         for (int i = 0; i < count; i++) {
996             ids[i] = idStates.keyAt(i);
997         }
998 
999         return ids;
1000     }
1001 
1002     /**
1003      * Clear any choices previously set
1004      */
clearChoices()1005     public void clearChoices() {
1006         if (mCheckStates != null) {
1007             mCheckStates.clear();
1008         }
1009         if (mCheckedIdStates != null) {
1010             mCheckedIdStates.clear();
1011         }
1012         mCheckedItemCount = 0;
1013     }
1014 
1015     /**
1016      * Sets the checked state of the specified position. The is only valid if
1017      * the choice mode has been set to {@link #CHOICE_MODE_SINGLE} or
1018      * {@link #CHOICE_MODE_MULTIPLE}.
1019      *
1020      * @param position The item whose checked state is to be checked
1021      * @param value The new checked state for the item
1022      */
setItemChecked(int position, boolean value)1023     public void setItemChecked(int position, boolean value) {
1024         if (mChoiceMode == CHOICE_MODE_NONE) {
1025             return;
1026         }
1027 
1028         // Start selection mode if needed. We don't need to if we're unchecking something.
1029         if (value && mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode == null) {
1030             if (mMultiChoiceModeCallback == null ||
1031                     !mMultiChoiceModeCallback.hasWrappedCallback()) {
1032                 throw new IllegalStateException("AbsListView: attempted to start selection mode " +
1033                         "for CHOICE_MODE_MULTIPLE_MODAL but no choice mode callback was " +
1034                         "supplied. Call setMultiChoiceModeListener to set a callback.");
1035             }
1036             mChoiceActionMode = startActionMode(mMultiChoiceModeCallback);
1037         }
1038 
1039         if (mChoiceMode == CHOICE_MODE_MULTIPLE || mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
1040             boolean oldValue = mCheckStates.get(position);
1041             mCheckStates.put(position, value);
1042             if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
1043                 if (value) {
1044                     mCheckedIdStates.put(mAdapter.getItemId(position), position);
1045                 } else {
1046                     mCheckedIdStates.delete(mAdapter.getItemId(position));
1047                 }
1048             }
1049             if (oldValue != value) {
1050                 if (value) {
1051                     mCheckedItemCount++;
1052                 } else {
1053                     mCheckedItemCount--;
1054                 }
1055             }
1056             if (mChoiceActionMode != null) {
1057                 final long id = mAdapter.getItemId(position);
1058                 mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
1059                         position, id, value);
1060             }
1061         } else {
1062             boolean updateIds = mCheckedIdStates != null && mAdapter.hasStableIds();
1063             // Clear all values if we're checking something, or unchecking the currently
1064             // selected item
1065             if (value || isItemChecked(position)) {
1066                 mCheckStates.clear();
1067                 if (updateIds) {
1068                     mCheckedIdStates.clear();
1069                 }
1070             }
1071             // this may end up selecting the value we just cleared but this way
1072             // we ensure length of mCheckStates is 1, a fact getCheckedItemPosition relies on
1073             if (value) {
1074                 mCheckStates.put(position, true);
1075                 if (updateIds) {
1076                     mCheckedIdStates.put(mAdapter.getItemId(position), position);
1077                 }
1078                 mCheckedItemCount = 1;
1079             } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
1080                 mCheckedItemCount = 0;
1081             }
1082         }
1083 
1084         // Do not generate a data change while we are in the layout phase
1085         if (!mInLayout && !mBlockLayoutRequests) {
1086             mDataChanged = true;
1087             rememberSyncState();
1088             requestLayout();
1089         }
1090     }
1091 
1092     @Override
performItemClick(View view, int position, long id)1093     public boolean performItemClick(View view, int position, long id) {
1094         boolean handled = false;
1095         boolean dispatchItemClick = true;
1096 
1097         if (mChoiceMode != CHOICE_MODE_NONE) {
1098             handled = true;
1099             boolean checkedStateChanged = false;
1100 
1101             if (mChoiceMode == CHOICE_MODE_MULTIPLE ||
1102                     (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode != null)) {
1103                 boolean checked = !mCheckStates.get(position, false);
1104                 mCheckStates.put(position, checked);
1105                 if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
1106                     if (checked) {
1107                         mCheckedIdStates.put(mAdapter.getItemId(position), position);
1108                     } else {
1109                         mCheckedIdStates.delete(mAdapter.getItemId(position));
1110                     }
1111                 }
1112                 if (checked) {
1113                     mCheckedItemCount++;
1114                 } else {
1115                     mCheckedItemCount--;
1116                 }
1117                 if (mChoiceActionMode != null) {
1118                     mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
1119                             position, id, checked);
1120                     dispatchItemClick = false;
1121                 }
1122                 checkedStateChanged = true;
1123             } else if (mChoiceMode == CHOICE_MODE_SINGLE) {
1124                 boolean checked = !mCheckStates.get(position, false);
1125                 if (checked) {
1126                     mCheckStates.clear();
1127                     mCheckStates.put(position, true);
1128                     if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
1129                         mCheckedIdStates.clear();
1130                         mCheckedIdStates.put(mAdapter.getItemId(position), position);
1131                     }
1132                     mCheckedItemCount = 1;
1133                 } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
1134                     mCheckedItemCount = 0;
1135                 }
1136                 checkedStateChanged = true;
1137             }
1138 
1139             if (checkedStateChanged) {
1140                 updateOnScreenCheckedViews();
1141             }
1142         }
1143 
1144         if (dispatchItemClick) {
1145             handled |= super.performItemClick(view, position, id);
1146         }
1147 
1148         return handled;
1149     }
1150 
1151     /**
1152      * Perform a quick, in-place update of the checked or activated state
1153      * on all visible item views. This should only be called when a valid
1154      * choice mode is active.
1155      */
updateOnScreenCheckedViews()1156     private void updateOnScreenCheckedViews() {
1157         final int firstPos = mFirstPosition;
1158         final int count = getChildCount();
1159         final boolean useActivated = getContext().getApplicationInfo().targetSdkVersion
1160                 >= android.os.Build.VERSION_CODES.HONEYCOMB;
1161         for (int i = 0; i < count; i++) {
1162             final View child = getChildAt(i);
1163             final int position = firstPos + i;
1164 
1165             if (child instanceof Checkable) {
1166                 ((Checkable) child).setChecked(mCheckStates.get(position));
1167             } else if (useActivated) {
1168                 child.setActivated(mCheckStates.get(position));
1169             }
1170         }
1171     }
1172 
1173     /**
1174      * @see #setChoiceMode(int)
1175      *
1176      * @return The current choice mode
1177      */
getChoiceMode()1178     public int getChoiceMode() {
1179         return mChoiceMode;
1180     }
1181 
1182     /**
1183      * Defines the choice behavior for the List. By default, Lists do not have any choice behavior
1184      * ({@link #CHOICE_MODE_NONE}). By setting the choiceMode to {@link #CHOICE_MODE_SINGLE}, the
1185      * List allows up to one item to  be in a chosen state. By setting the choiceMode to
1186      * {@link #CHOICE_MODE_MULTIPLE}, the list allows any number of items to be chosen.
1187      *
1188      * @param choiceMode One of {@link #CHOICE_MODE_NONE}, {@link #CHOICE_MODE_SINGLE}, or
1189      * {@link #CHOICE_MODE_MULTIPLE}
1190      */
setChoiceMode(int choiceMode)1191     public void setChoiceMode(int choiceMode) {
1192         mChoiceMode = choiceMode;
1193         if (mChoiceActionMode != null) {
1194             mChoiceActionMode.finish();
1195             mChoiceActionMode = null;
1196         }
1197         if (mChoiceMode != CHOICE_MODE_NONE) {
1198             if (mCheckStates == null) {
1199                 mCheckStates = new SparseBooleanArray(0);
1200             }
1201             if (mCheckedIdStates == null && mAdapter != null && mAdapter.hasStableIds()) {
1202                 mCheckedIdStates = new LongSparseArray<Integer>(0);
1203             }
1204             // Modal multi-choice mode only has choices when the mode is active. Clear them.
1205             if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
1206                 clearChoices();
1207                 setLongClickable(true);
1208             }
1209         }
1210     }
1211 
1212     /**
1213      * Set a {@link MultiChoiceModeListener} that will manage the lifecycle of the
1214      * selection {@link ActionMode}. Only used when the choice mode is set to
1215      * {@link #CHOICE_MODE_MULTIPLE_MODAL}.
1216      *
1217      * @param listener Listener that will manage the selection mode
1218      *
1219      * @see #setChoiceMode(int)
1220      */
setMultiChoiceModeListener(MultiChoiceModeListener listener)1221     public void setMultiChoiceModeListener(MultiChoiceModeListener listener) {
1222         if (mMultiChoiceModeCallback == null) {
1223             mMultiChoiceModeCallback = new MultiChoiceModeWrapper();
1224         }
1225         mMultiChoiceModeCallback.setWrapped(listener);
1226     }
1227 
1228     /**
1229      * @return true if all list content currently fits within the view boundaries
1230      */
contentFits()1231     private boolean contentFits() {
1232         final int childCount = getChildCount();
1233         if (childCount == 0) return true;
1234         if (childCount != mItemCount) return false;
1235 
1236         return getChildAt(0).getTop() >= mListPadding.top &&
1237                 getChildAt(childCount - 1).getBottom() <= getHeight() - mListPadding.bottom;
1238     }
1239 
1240     /**
1241      * Specifies whether fast scrolling is enabled or disabled.
1242      * <p>
1243      * When fast scrolling is enabled, the user can quickly scroll through lists
1244      * by dragging the fast scroll thumb.
1245      * <p>
1246      * If the adapter backing this list implements {@link SectionIndexer}, the
1247      * fast scroller will display section header previews as the user scrolls.
1248      * Additionally, the user will be able to quickly jump between sections by
1249      * tapping along the length of the scroll bar.
1250      *
1251      * @see SectionIndexer
1252      * @see #isFastScrollEnabled()
1253      * @param enabled true to enable fast scrolling, false otherwise
1254      */
setFastScrollEnabled(final boolean enabled)1255     public void setFastScrollEnabled(final boolean enabled) {
1256         if (mFastScrollEnabled != enabled) {
1257             mFastScrollEnabled = enabled;
1258 
1259             if (isOwnerThread()) {
1260                 setFastScrollerEnabledUiThread(enabled);
1261             } else {
1262                 post(new Runnable() {
1263                     @Override
1264                     public void run() {
1265                         setFastScrollerEnabledUiThread(enabled);
1266                     }
1267                 });
1268             }
1269         }
1270     }
1271 
setFastScrollerEnabledUiThread(boolean enabled)1272     private void setFastScrollerEnabledUiThread(boolean enabled) {
1273         if (mFastScroll != null) {
1274             mFastScroll.setEnabled(enabled);
1275         } else if (enabled) {
1276             mFastScroll = new FastScroller(this, mFastScrollStyle);
1277             mFastScroll.setEnabled(true);
1278         }
1279 
1280         resolvePadding();
1281 
1282         if (mFastScroll != null) {
1283             mFastScroll.updateLayout();
1284         }
1285     }
1286 
1287     /**
1288      * Specifies the style of the fast scroller decorations.
1289      *
1290      * @param styleResId style resource containing fast scroller properties
1291      * @see android.R.styleable#FastScroll
1292      */
setFastScrollStyle(int styleResId)1293     public void setFastScrollStyle(int styleResId) {
1294         if (mFastScroll == null) {
1295             mFastScrollStyle = styleResId;
1296         } else {
1297             mFastScroll.setStyle(styleResId);
1298         }
1299     }
1300 
1301     /**
1302      * Set whether or not the fast scroller should always be shown in place of
1303      * the standard scroll bars. This will enable fast scrolling if it is not
1304      * already enabled.
1305      * <p>
1306      * Fast scrollers shown in this way will not fade out and will be a
1307      * permanent fixture within the list. This is best combined with an inset
1308      * scroll bar style to ensure the scroll bar does not overlap content.
1309      *
1310      * @param alwaysShow true if the fast scroller should always be displayed,
1311      *            false otherwise
1312      * @see #setScrollBarStyle(int)
1313      * @see #setFastScrollEnabled(boolean)
1314      */
setFastScrollAlwaysVisible(final boolean alwaysShow)1315     public void setFastScrollAlwaysVisible(final boolean alwaysShow) {
1316         if (mFastScrollAlwaysVisible != alwaysShow) {
1317             if (alwaysShow && !mFastScrollEnabled) {
1318                 setFastScrollEnabled(true);
1319             }
1320 
1321             mFastScrollAlwaysVisible = alwaysShow;
1322 
1323             if (isOwnerThread()) {
1324                 setFastScrollerAlwaysVisibleUiThread(alwaysShow);
1325             } else {
1326                 post(new Runnable() {
1327                     @Override
1328                     public void run() {
1329                         setFastScrollerAlwaysVisibleUiThread(alwaysShow);
1330                     }
1331                 });
1332             }
1333         }
1334     }
1335 
setFastScrollerAlwaysVisibleUiThread(boolean alwaysShow)1336     private void setFastScrollerAlwaysVisibleUiThread(boolean alwaysShow) {
1337         if (mFastScroll != null) {
1338             mFastScroll.setAlwaysShow(alwaysShow);
1339         }
1340     }
1341 
1342     /**
1343      * @return whether the current thread is the one that created the view
1344      */
isOwnerThread()1345     private boolean isOwnerThread() {
1346         return mOwnerThread == Thread.currentThread();
1347     }
1348 
1349     /**
1350      * Returns true if the fast scroller is set to always show on this view.
1351      *
1352      * @return true if the fast scroller will always show
1353      * @see #setFastScrollAlwaysVisible(boolean)
1354      */
isFastScrollAlwaysVisible()1355     public boolean isFastScrollAlwaysVisible() {
1356         if (mFastScroll == null) {
1357             return mFastScrollEnabled && mFastScrollAlwaysVisible;
1358         } else {
1359             return mFastScroll.isEnabled() && mFastScroll.isAlwaysShowEnabled();
1360         }
1361     }
1362 
1363     @Override
getVerticalScrollbarWidth()1364     public int getVerticalScrollbarWidth() {
1365         if (mFastScroll != null && mFastScroll.isEnabled()) {
1366             return Math.max(super.getVerticalScrollbarWidth(), mFastScroll.getWidth());
1367         }
1368         return super.getVerticalScrollbarWidth();
1369     }
1370 
1371     /**
1372      * Returns true if the fast scroller is enabled.
1373      *
1374      * @see #setFastScrollEnabled(boolean)
1375      * @return true if fast scroll is enabled, false otherwise
1376      */
1377     @ViewDebug.ExportedProperty
isFastScrollEnabled()1378     public boolean isFastScrollEnabled() {
1379         if (mFastScroll == null) {
1380             return mFastScrollEnabled;
1381         } else {
1382             return mFastScroll.isEnabled();
1383         }
1384     }
1385 
1386     @Override
setVerticalScrollbarPosition(int position)1387     public void setVerticalScrollbarPosition(int position) {
1388         super.setVerticalScrollbarPosition(position);
1389         if (mFastScroll != null) {
1390             mFastScroll.setScrollbarPosition(position);
1391         }
1392     }
1393 
1394     @Override
setScrollBarStyle(int style)1395     public void setScrollBarStyle(int style) {
1396         super.setScrollBarStyle(style);
1397         if (mFastScroll != null) {
1398             mFastScroll.setScrollBarStyle(style);
1399         }
1400     }
1401 
1402     /**
1403      * If fast scroll is enabled, then don't draw the vertical scrollbar.
1404      * @hide
1405      */
1406     @Override
isVerticalScrollBarHidden()1407     protected boolean isVerticalScrollBarHidden() {
1408         return isFastScrollEnabled();
1409     }
1410 
1411     /**
1412      * When smooth scrollbar is enabled, the position and size of the scrollbar thumb
1413      * is computed based on the number of visible pixels in the visible items. This
1414      * however assumes that all list items have the same height. If you use a list in
1415      * which items have different heights, the scrollbar will change appearance as the
1416      * user scrolls through the list. To avoid this issue, you need to disable this
1417      * property.
1418      *
1419      * When smooth scrollbar is disabled, the position and size of the scrollbar thumb
1420      * is based solely on the number of items in the adapter and the position of the
1421      * visible items inside the adapter. This provides a stable scrollbar as the user
1422      * navigates through a list of items with varying heights.
1423      *
1424      * @param enabled Whether or not to enable smooth scrollbar.
1425      *
1426      * @see #setSmoothScrollbarEnabled(boolean)
1427      * @attr ref android.R.styleable#AbsListView_smoothScrollbar
1428      */
setSmoothScrollbarEnabled(boolean enabled)1429     public void setSmoothScrollbarEnabled(boolean enabled) {
1430         mSmoothScrollbarEnabled = enabled;
1431     }
1432 
1433     /**
1434      * Returns the current state of the fast scroll feature.
1435      *
1436      * @return True if smooth scrollbar is enabled is enabled, false otherwise.
1437      *
1438      * @see #setSmoothScrollbarEnabled(boolean)
1439      */
1440     @ViewDebug.ExportedProperty
isSmoothScrollbarEnabled()1441     public boolean isSmoothScrollbarEnabled() {
1442         return mSmoothScrollbarEnabled;
1443     }
1444 
1445     /**
1446      * Set the listener that will receive notifications every time the list scrolls.
1447      *
1448      * @param l the scroll listener
1449      */
setOnScrollListener(OnScrollListener l)1450     public void setOnScrollListener(OnScrollListener l) {
1451         mOnScrollListener = l;
1452         invokeOnItemScrollListener();
1453     }
1454 
1455     /**
1456      * Notify our scroll listener (if there is one) of a change in scroll state
1457      */
invokeOnItemScrollListener()1458     void invokeOnItemScrollListener() {
1459         if (mFastScroll != null) {
1460             mFastScroll.onScroll(mFirstPosition, getChildCount(), mItemCount);
1461         }
1462         if (mOnScrollListener != null) {
1463             mOnScrollListener.onScroll(this, mFirstPosition, getChildCount(), mItemCount);
1464         }
1465         onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these.
1466     }
1467 
1468     /** @hide */
1469     @Override
sendAccessibilityEventInternal(int eventType)1470     public void sendAccessibilityEventInternal(int eventType) {
1471         // Since this class calls onScrollChanged even if the mFirstPosition and the
1472         // child count have not changed we will avoid sending duplicate accessibility
1473         // events.
1474         if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
1475             final int firstVisiblePosition = getFirstVisiblePosition();
1476             final int lastVisiblePosition = getLastVisiblePosition();
1477             if (mLastAccessibilityScrollEventFromIndex == firstVisiblePosition
1478                     && mLastAccessibilityScrollEventToIndex == lastVisiblePosition) {
1479                 return;
1480             } else {
1481                 mLastAccessibilityScrollEventFromIndex = firstVisiblePosition;
1482                 mLastAccessibilityScrollEventToIndex = lastVisiblePosition;
1483             }
1484         }
1485         super.sendAccessibilityEventInternal(eventType);
1486     }
1487 
1488     @Override
getAccessibilityClassName()1489     public CharSequence getAccessibilityClassName() {
1490         return AbsListView.class.getName();
1491     }
1492 
1493     /** @hide */
1494     @Override
onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)1495     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
1496         super.onInitializeAccessibilityNodeInfoInternal(info);
1497         if (isEnabled()) {
1498             if (canScrollUp()) {
1499                 info.addAction(AccessibilityAction.ACTION_SCROLL_BACKWARD);
1500                 info.addAction(AccessibilityAction.ACTION_SCROLL_UP);
1501                 info.setScrollable(true);
1502             }
1503             if (canScrollDown()) {
1504                 info.addAction(AccessibilityAction.ACTION_SCROLL_FORWARD);
1505                 info.addAction(AccessibilityAction.ACTION_SCROLL_DOWN);
1506                 info.setScrollable(true);
1507             }
1508         }
1509     }
1510 
getSelectionModeForAccessibility()1511     int getSelectionModeForAccessibility() {
1512         final int choiceMode = getChoiceMode();
1513         switch (choiceMode) {
1514             case CHOICE_MODE_NONE:
1515                 return CollectionInfo.SELECTION_MODE_NONE;
1516             case CHOICE_MODE_SINGLE:
1517                 return CollectionInfo.SELECTION_MODE_SINGLE;
1518             case CHOICE_MODE_MULTIPLE:
1519             case CHOICE_MODE_MULTIPLE_MODAL:
1520                 return CollectionInfo.SELECTION_MODE_MULTIPLE;
1521             default:
1522                 return CollectionInfo.SELECTION_MODE_NONE;
1523         }
1524     }
1525 
1526     /** @hide */
1527     @Override
performAccessibilityActionInternal(int action, Bundle arguments)1528     public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
1529         if (super.performAccessibilityActionInternal(action, arguments)) {
1530             return true;
1531         }
1532         switch (action) {
1533             case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
1534             case R.id.accessibilityActionScrollDown: {
1535                 if (isEnabled() && getLastVisiblePosition() < getCount() - 1) {
1536                     final int viewportHeight = getHeight() - mListPadding.top - mListPadding.bottom;
1537                     smoothScrollBy(viewportHeight, PositionScroller.SCROLL_DURATION);
1538                     return true;
1539                 }
1540             } return false;
1541             case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
1542             case R.id.accessibilityActionScrollUp: {
1543                 if (isEnabled() && mFirstPosition > 0) {
1544                     final int viewportHeight = getHeight() - mListPadding.top - mListPadding.bottom;
1545                     smoothScrollBy(-viewportHeight, PositionScroller.SCROLL_DURATION);
1546                     return true;
1547                 }
1548             } return false;
1549         }
1550         return false;
1551     }
1552 
1553     /** @hide */
1554     @Override
findViewByAccessibilityIdTraversal(int accessibilityId)1555     public View findViewByAccessibilityIdTraversal(int accessibilityId) {
1556         if (accessibilityId == getAccessibilityViewId()) {
1557             return this;
1558         }
1559         return super.findViewByAccessibilityIdTraversal(accessibilityId);
1560     }
1561 
1562     /**
1563      * Indicates whether the children's drawing cache is used during a scroll.
1564      * By default, the drawing cache is enabled but this will consume more memory.
1565      *
1566      * @return true if the scrolling cache is enabled, false otherwise
1567      *
1568      * @see #setScrollingCacheEnabled(boolean)
1569      * @see View#setDrawingCacheEnabled(boolean)
1570      */
1571     @ViewDebug.ExportedProperty
isScrollingCacheEnabled()1572     public boolean isScrollingCacheEnabled() {
1573         return mScrollingCacheEnabled;
1574     }
1575 
1576     /**
1577      * Enables or disables the children's drawing cache during a scroll.
1578      * By default, the drawing cache is enabled but this will use more memory.
1579      *
1580      * When the scrolling cache is enabled, the caches are kept after the
1581      * first scrolling. You can manually clear the cache by calling
1582      * {@link android.view.ViewGroup#setChildrenDrawingCacheEnabled(boolean)}.
1583      *
1584      * @param enabled true to enable the scroll cache, false otherwise
1585      *
1586      * @see #isScrollingCacheEnabled()
1587      * @see View#setDrawingCacheEnabled(boolean)
1588      */
setScrollingCacheEnabled(boolean enabled)1589     public void setScrollingCacheEnabled(boolean enabled) {
1590         if (mScrollingCacheEnabled && !enabled) {
1591             clearScrollingCache();
1592         }
1593         mScrollingCacheEnabled = enabled;
1594     }
1595 
1596     /**
1597      * Enables or disables the type filter window. If enabled, typing when
1598      * this view has focus will filter the children to match the users input.
1599      * Note that the {@link Adapter} used by this view must implement the
1600      * {@link Filterable} interface.
1601      *
1602      * @param textFilterEnabled true to enable type filtering, false otherwise
1603      *
1604      * @see Filterable
1605      */
setTextFilterEnabled(boolean textFilterEnabled)1606     public void setTextFilterEnabled(boolean textFilterEnabled) {
1607         mTextFilterEnabled = textFilterEnabled;
1608     }
1609 
1610     /**
1611      * Indicates whether type filtering is enabled for this view
1612      *
1613      * @return true if type filtering is enabled, false otherwise
1614      *
1615      * @see #setTextFilterEnabled(boolean)
1616      * @see Filterable
1617      */
1618     @ViewDebug.ExportedProperty
isTextFilterEnabled()1619     public boolean isTextFilterEnabled() {
1620         return mTextFilterEnabled;
1621     }
1622 
1623     @Override
getFocusedRect(Rect r)1624     public void getFocusedRect(Rect r) {
1625         View view = getSelectedView();
1626         if (view != null && view.getParent() == this) {
1627             // the focused rectangle of the selected view offset into the
1628             // coordinate space of this view.
1629             view.getFocusedRect(r);
1630             offsetDescendantRectToMyCoords(view, r);
1631         } else {
1632             // otherwise, just the norm
1633             super.getFocusedRect(r);
1634         }
1635     }
1636 
useDefaultSelector()1637     private void useDefaultSelector() {
1638         setSelector(getContext().getDrawable(
1639                 com.android.internal.R.drawable.list_selector_background));
1640     }
1641 
1642     /**
1643      * Indicates whether the content of this view is pinned to, or stacked from,
1644      * the bottom edge.
1645      *
1646      * @return true if the content is stacked from the bottom edge, false otherwise
1647      */
1648     @ViewDebug.ExportedProperty
isStackFromBottom()1649     public boolean isStackFromBottom() {
1650         return mStackFromBottom;
1651     }
1652 
1653     /**
1654      * When stack from bottom is set to true, the list fills its content starting from
1655      * the bottom of the view.
1656      *
1657      * @param stackFromBottom true to pin the view's content to the bottom edge,
1658      *        false to pin the view's content to the top edge
1659      */
setStackFromBottom(boolean stackFromBottom)1660     public void setStackFromBottom(boolean stackFromBottom) {
1661         if (mStackFromBottom != stackFromBottom) {
1662             mStackFromBottom = stackFromBottom;
1663             requestLayoutIfNecessary();
1664         }
1665     }
1666 
requestLayoutIfNecessary()1667     void requestLayoutIfNecessary() {
1668         if (getChildCount() > 0) {
1669             resetList();
1670             requestLayout();
1671             invalidate();
1672         }
1673     }
1674 
1675     static class SavedState extends BaseSavedState {
1676         long selectedId;
1677         long firstId;
1678         int viewTop;
1679         int position;
1680         int height;
1681         String filter;
1682         boolean inActionMode;
1683         int checkedItemCount;
1684         SparseBooleanArray checkState;
1685         LongSparseArray<Integer> checkIdState;
1686 
1687         /**
1688          * Constructor called from {@link AbsListView#onSaveInstanceState()}
1689          */
SavedState(Parcelable superState)1690         SavedState(Parcelable superState) {
1691             super(superState);
1692         }
1693 
1694         /**
1695          * Constructor called from {@link #CREATOR}
1696          */
SavedState(Parcel in)1697         private SavedState(Parcel in) {
1698             super(in);
1699             selectedId = in.readLong();
1700             firstId = in.readLong();
1701             viewTop = in.readInt();
1702             position = in.readInt();
1703             height = in.readInt();
1704             filter = in.readString();
1705             inActionMode = in.readByte() != 0;
1706             checkedItemCount = in.readInt();
1707             checkState = in.readSparseBooleanArray();
1708             final int N = in.readInt();
1709             if (N > 0) {
1710                 checkIdState = new LongSparseArray<Integer>();
1711                 for (int i=0; i<N; i++) {
1712                     final long key = in.readLong();
1713                     final int value = in.readInt();
1714                     checkIdState.put(key, value);
1715                 }
1716             }
1717         }
1718 
1719         @Override
writeToParcel(Parcel out, int flags)1720         public void writeToParcel(Parcel out, int flags) {
1721             super.writeToParcel(out, flags);
1722             out.writeLong(selectedId);
1723             out.writeLong(firstId);
1724             out.writeInt(viewTop);
1725             out.writeInt(position);
1726             out.writeInt(height);
1727             out.writeString(filter);
1728             out.writeByte((byte) (inActionMode ? 1 : 0));
1729             out.writeInt(checkedItemCount);
1730             out.writeSparseBooleanArray(checkState);
1731             final int N = checkIdState != null ? checkIdState.size() : 0;
1732             out.writeInt(N);
1733             for (int i=0; i<N; i++) {
1734                 out.writeLong(checkIdState.keyAt(i));
1735                 out.writeInt(checkIdState.valueAt(i));
1736             }
1737         }
1738 
1739         @Override
toString()1740         public String toString() {
1741             return "AbsListView.SavedState{"
1742                     + Integer.toHexString(System.identityHashCode(this))
1743                     + " selectedId=" + selectedId
1744                     + " firstId=" + firstId
1745                     + " viewTop=" + viewTop
1746                     + " position=" + position
1747                     + " height=" + height
1748                     + " filter=" + filter
1749                     + " checkState=" + checkState + "}";
1750         }
1751 
1752         public static final Parcelable.Creator<SavedState> CREATOR
1753                 = new Parcelable.Creator<SavedState>() {
1754             @Override
1755             public SavedState createFromParcel(Parcel in) {
1756                 return new SavedState(in);
1757             }
1758 
1759             @Override
1760             public SavedState[] newArray(int size) {
1761                 return new SavedState[size];
1762             }
1763         };
1764     }
1765 
1766     @Override
onSaveInstanceState()1767     public Parcelable onSaveInstanceState() {
1768         /*
1769          * This doesn't really make sense as the place to dismiss the
1770          * popups, but there don't seem to be any other useful hooks
1771          * that happen early enough to keep from getting complaints
1772          * about having leaked the window.
1773          */
1774         dismissPopup();
1775 
1776         Parcelable superState = super.onSaveInstanceState();
1777 
1778         SavedState ss = new SavedState(superState);
1779 
1780         if (mPendingSync != null) {
1781             // Just keep what we last restored.
1782             ss.selectedId = mPendingSync.selectedId;
1783             ss.firstId = mPendingSync.firstId;
1784             ss.viewTop = mPendingSync.viewTop;
1785             ss.position = mPendingSync.position;
1786             ss.height = mPendingSync.height;
1787             ss.filter = mPendingSync.filter;
1788             ss.inActionMode = mPendingSync.inActionMode;
1789             ss.checkedItemCount = mPendingSync.checkedItemCount;
1790             ss.checkState = mPendingSync.checkState;
1791             ss.checkIdState = mPendingSync.checkIdState;
1792             return ss;
1793         }
1794 
1795         boolean haveChildren = getChildCount() > 0 && mItemCount > 0;
1796         long selectedId = getSelectedItemId();
1797         ss.selectedId = selectedId;
1798         ss.height = getHeight();
1799 
1800         if (selectedId >= 0) {
1801             // Remember the selection
1802             ss.viewTop = mSelectedTop;
1803             ss.position = getSelectedItemPosition();
1804             ss.firstId = INVALID_POSITION;
1805         } else {
1806             if (haveChildren && mFirstPosition > 0) {
1807                 // Remember the position of the first child.
1808                 // We only do this if we are not currently at the top of
1809                 // the list, for two reasons:
1810                 // (1) The list may be in the process of becoming empty, in
1811                 // which case mItemCount may not be 0, but if we try to
1812                 // ask for any information about position 0 we will crash.
1813                 // (2) Being "at the top" seems like a special case, anyway,
1814                 // and the user wouldn't expect to end up somewhere else when
1815                 // they revisit the list even if its content has changed.
1816                 View v = getChildAt(0);
1817                 ss.viewTop = v.getTop();
1818                 int firstPos = mFirstPosition;
1819                 if (firstPos >= mItemCount) {
1820                     firstPos = mItemCount - 1;
1821                 }
1822                 ss.position = firstPos;
1823                 ss.firstId = mAdapter.getItemId(firstPos);
1824             } else {
1825                 ss.viewTop = 0;
1826                 ss.firstId = INVALID_POSITION;
1827                 ss.position = 0;
1828             }
1829         }
1830 
1831         ss.filter = null;
1832         if (mFiltered) {
1833             final EditText textFilter = mTextFilter;
1834             if (textFilter != null) {
1835                 Editable filterText = textFilter.getText();
1836                 if (filterText != null) {
1837                     ss.filter = filterText.toString();
1838                 }
1839             }
1840         }
1841 
1842         ss.inActionMode = mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode != null;
1843 
1844         if (mCheckStates != null) {
1845             ss.checkState = mCheckStates.clone();
1846         }
1847         if (mCheckedIdStates != null) {
1848             final LongSparseArray<Integer> idState = new LongSparseArray<Integer>();
1849             final int count = mCheckedIdStates.size();
1850             for (int i = 0; i < count; i++) {
1851                 idState.put(mCheckedIdStates.keyAt(i), mCheckedIdStates.valueAt(i));
1852             }
1853             ss.checkIdState = idState;
1854         }
1855         ss.checkedItemCount = mCheckedItemCount;
1856 
1857         if (mRemoteAdapter != null) {
1858             mRemoteAdapter.saveRemoteViewsCache();
1859         }
1860 
1861         return ss;
1862     }
1863 
1864     @Override
onRestoreInstanceState(Parcelable state)1865     public void onRestoreInstanceState(Parcelable state) {
1866         SavedState ss = (SavedState) state;
1867 
1868         super.onRestoreInstanceState(ss.getSuperState());
1869         mDataChanged = true;
1870 
1871         mSyncHeight = ss.height;
1872 
1873         if (ss.selectedId >= 0) {
1874             mNeedSync = true;
1875             mPendingSync = ss;
1876             mSyncRowId = ss.selectedId;
1877             mSyncPosition = ss.position;
1878             mSpecificTop = ss.viewTop;
1879             mSyncMode = SYNC_SELECTED_POSITION;
1880         } else if (ss.firstId >= 0) {
1881             setSelectedPositionInt(INVALID_POSITION);
1882             // Do this before setting mNeedSync since setNextSelectedPosition looks at mNeedSync
1883             setNextSelectedPositionInt(INVALID_POSITION);
1884             mSelectorPosition = INVALID_POSITION;
1885             mNeedSync = true;
1886             mPendingSync = ss;
1887             mSyncRowId = ss.firstId;
1888             mSyncPosition = ss.position;
1889             mSpecificTop = ss.viewTop;
1890             mSyncMode = SYNC_FIRST_POSITION;
1891         }
1892 
1893         setFilterText(ss.filter);
1894 
1895         if (ss.checkState != null) {
1896             mCheckStates = ss.checkState;
1897         }
1898 
1899         if (ss.checkIdState != null) {
1900             mCheckedIdStates = ss.checkIdState;
1901         }
1902 
1903         mCheckedItemCount = ss.checkedItemCount;
1904 
1905         if (ss.inActionMode && mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL &&
1906                 mMultiChoiceModeCallback != null) {
1907             mChoiceActionMode = startActionMode(mMultiChoiceModeCallback);
1908         }
1909 
1910         requestLayout();
1911     }
1912 
acceptFilter()1913     private boolean acceptFilter() {
1914         return mTextFilterEnabled && getAdapter() instanceof Filterable &&
1915                 ((Filterable) getAdapter()).getFilter() != null;
1916     }
1917 
1918     /**
1919      * Sets the initial value for the text filter.
1920      * @param filterText The text to use for the filter.
1921      *
1922      * @see #setTextFilterEnabled
1923      */
setFilterText(String filterText)1924     public void setFilterText(String filterText) {
1925         // TODO: Should we check for acceptFilter()?
1926         if (mTextFilterEnabled && !TextUtils.isEmpty(filterText)) {
1927             createTextFilter(false);
1928             // This is going to call our listener onTextChanged, but we might not
1929             // be ready to bring up a window yet
1930             mTextFilter.setText(filterText);
1931             mTextFilter.setSelection(filterText.length());
1932             if (mAdapter instanceof Filterable) {
1933                 // if mPopup is non-null, then onTextChanged will do the filtering
1934                 if (mPopup == null) {
1935                     Filter f = ((Filterable) mAdapter).getFilter();
1936                     f.filter(filterText);
1937                 }
1938                 // Set filtered to true so we will display the filter window when our main
1939                 // window is ready
1940                 mFiltered = true;
1941                 mDataSetObserver.clearSavedState();
1942             }
1943         }
1944     }
1945 
1946     /**
1947      * Returns the list's text filter, if available.
1948      * @return the list's text filter or null if filtering isn't enabled
1949      */
getTextFilter()1950     public CharSequence getTextFilter() {
1951         if (mTextFilterEnabled && mTextFilter != null) {
1952             return mTextFilter.getText();
1953         }
1954         return null;
1955     }
1956 
1957     @Override
onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect)1958     protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
1959         super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
1960         if (gainFocus && mSelectedPosition < 0 && !isInTouchMode()) {
1961             if (!isAttachedToWindow() && mAdapter != null) {
1962                 // Data may have changed while we were detached and it's valid
1963                 // to change focus while detached. Refresh so we don't die.
1964                 mDataChanged = true;
1965                 mOldItemCount = mItemCount;
1966                 mItemCount = mAdapter.getCount();
1967             }
1968             resurrectSelection();
1969         }
1970     }
1971 
1972     @Override
requestLayout()1973     public void requestLayout() {
1974         if (!mBlockLayoutRequests && !mInLayout) {
1975             super.requestLayout();
1976         }
1977     }
1978 
1979     /**
1980      * The list is empty. Clear everything out.
1981      */
resetList()1982     void resetList() {
1983         removeAllViewsInLayout();
1984         mFirstPosition = 0;
1985         mDataChanged = false;
1986         mPositionScrollAfterLayout = null;
1987         mNeedSync = false;
1988         mPendingSync = null;
1989         mOldSelectedPosition = INVALID_POSITION;
1990         mOldSelectedRowId = INVALID_ROW_ID;
1991         setSelectedPositionInt(INVALID_POSITION);
1992         setNextSelectedPositionInt(INVALID_POSITION);
1993         mSelectedTop = 0;
1994         mSelectorPosition = INVALID_POSITION;
1995         mSelectorRect.setEmpty();
1996         invalidate();
1997     }
1998 
1999     @Override
computeVerticalScrollExtent()2000     protected int computeVerticalScrollExtent() {
2001         final int count = getChildCount();
2002         if (count > 0) {
2003             if (mSmoothScrollbarEnabled) {
2004                 int extent = count * 100;
2005 
2006                 View view = getChildAt(0);
2007                 final int top = view.getTop();
2008                 int height = view.getHeight();
2009                 if (height > 0) {
2010                     extent += (top * 100) / height;
2011                 }
2012 
2013                 view = getChildAt(count - 1);
2014                 final int bottom = view.getBottom();
2015                 height = view.getHeight();
2016                 if (height > 0) {
2017                     extent -= ((bottom - getHeight()) * 100) / height;
2018                 }
2019 
2020                 return extent;
2021             } else {
2022                 return 1;
2023             }
2024         }
2025         return 0;
2026     }
2027 
2028     @Override
computeVerticalScrollOffset()2029     protected int computeVerticalScrollOffset() {
2030         final int firstPosition = mFirstPosition;
2031         final int childCount = getChildCount();
2032         if (firstPosition >= 0 && childCount > 0) {
2033             if (mSmoothScrollbarEnabled) {
2034                 final View view = getChildAt(0);
2035                 final int top = view.getTop();
2036                 int height = view.getHeight();
2037                 if (height > 0) {
2038                     return Math.max(firstPosition * 100 - (top * 100) / height +
2039                             (int)((float)mScrollY / getHeight() * mItemCount * 100), 0);
2040                 }
2041             } else {
2042                 int index;
2043                 final int count = mItemCount;
2044                 if (firstPosition == 0) {
2045                     index = 0;
2046                 } else if (firstPosition + childCount == count) {
2047                     index = count;
2048                 } else {
2049                     index = firstPosition + childCount / 2;
2050                 }
2051                 return (int) (firstPosition + childCount * (index / (float) count));
2052             }
2053         }
2054         return 0;
2055     }
2056 
2057     @Override
computeVerticalScrollRange()2058     protected int computeVerticalScrollRange() {
2059         int result;
2060         if (mSmoothScrollbarEnabled) {
2061             result = Math.max(mItemCount * 100, 0);
2062             if (mScrollY != 0) {
2063                 // Compensate for overscroll
2064                 result += Math.abs((int) ((float) mScrollY / getHeight() * mItemCount * 100));
2065             }
2066         } else {
2067             result = mItemCount;
2068         }
2069         return result;
2070     }
2071 
2072     @Override
getTopFadingEdgeStrength()2073     protected float getTopFadingEdgeStrength() {
2074         final int count = getChildCount();
2075         final float fadeEdge = super.getTopFadingEdgeStrength();
2076         if (count == 0) {
2077             return fadeEdge;
2078         } else {
2079             if (mFirstPosition > 0) {
2080                 return 1.0f;
2081             }
2082 
2083             final int top = getChildAt(0).getTop();
2084             final float fadeLength = getVerticalFadingEdgeLength();
2085             return top < mPaddingTop ? -(top - mPaddingTop) / fadeLength : fadeEdge;
2086         }
2087     }
2088 
2089     @Override
getBottomFadingEdgeStrength()2090     protected float getBottomFadingEdgeStrength() {
2091         final int count = getChildCount();
2092         final float fadeEdge = super.getBottomFadingEdgeStrength();
2093         if (count == 0) {
2094             return fadeEdge;
2095         } else {
2096             if (mFirstPosition + count - 1 < mItemCount - 1) {
2097                 return 1.0f;
2098             }
2099 
2100             final int bottom = getChildAt(count - 1).getBottom();
2101             final int height = getHeight();
2102             final float fadeLength = getVerticalFadingEdgeLength();
2103             return bottom > height - mPaddingBottom ?
2104                     (bottom - height + mPaddingBottom) / fadeLength : fadeEdge;
2105         }
2106     }
2107 
2108     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)2109     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
2110         if (mSelector == null) {
2111             useDefaultSelector();
2112         }
2113         final Rect listPadding = mListPadding;
2114         listPadding.left = mSelectionLeftPadding + mPaddingLeft;
2115         listPadding.top = mSelectionTopPadding + mPaddingTop;
2116         listPadding.right = mSelectionRightPadding + mPaddingRight;
2117         listPadding.bottom = mSelectionBottomPadding + mPaddingBottom;
2118 
2119         // Check if our previous measured size was at a point where we should scroll later.
2120         if (mTranscriptMode == TRANSCRIPT_MODE_NORMAL) {
2121             final int childCount = getChildCount();
2122             final int listBottom = getHeight() - getPaddingBottom();
2123             final View lastChild = getChildAt(childCount - 1);
2124             final int lastBottom = lastChild != null ? lastChild.getBottom() : listBottom;
2125             mForceTranscriptScroll = mFirstPosition + childCount >= mLastHandledItemCount &&
2126                     lastBottom <= listBottom;
2127         }
2128     }
2129 
2130     /**
2131      * Subclasses should NOT override this method but
2132      *  {@link #layoutChildren()} instead.
2133      */
2134     @Override
onLayout(boolean changed, int l, int t, int r, int b)2135     protected void onLayout(boolean changed, int l, int t, int r, int b) {
2136         super.onLayout(changed, l, t, r, b);
2137 
2138         mInLayout = true;
2139 
2140         final int childCount = getChildCount();
2141         if (changed) {
2142             for (int i = 0; i < childCount; i++) {
2143                 getChildAt(i).forceLayout();
2144             }
2145             mRecycler.markChildrenDirty();
2146         }
2147 
2148         layoutChildren();
2149         mInLayout = false;
2150 
2151         mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;
2152 
2153         // TODO: Move somewhere sane. This doesn't belong in onLayout().
2154         if (mFastScroll != null) {
2155             mFastScroll.onItemCountChanged(getChildCount(), mItemCount);
2156         }
2157     }
2158 
2159     /**
2160      * @hide
2161      */
2162     @Override
setFrame(int left, int top, int right, int bottom)2163     protected boolean setFrame(int left, int top, int right, int bottom) {
2164         final boolean changed = super.setFrame(left, top, right, bottom);
2165 
2166         if (changed) {
2167             // Reposition the popup when the frame has changed. This includes
2168             // translating the widget, not just changing its dimension. The
2169             // filter popup needs to follow the widget.
2170             final boolean visible = getWindowVisibility() == View.VISIBLE;
2171             if (mFiltered && visible && mPopup != null && mPopup.isShowing()) {
2172                 positionPopup();
2173             }
2174         }
2175 
2176         return changed;
2177     }
2178 
2179     /**
2180      * Subclasses must override this method to layout their children.
2181      */
layoutChildren()2182     protected void layoutChildren() {
2183     }
2184 
2185     /**
2186      * @param focusedView view that holds accessibility focus
2187      * @return direct child that contains accessibility focus, or null if no
2188      *         child contains accessibility focus
2189      */
getAccessibilityFocusedChild(View focusedView)2190     View getAccessibilityFocusedChild(View focusedView) {
2191         ViewParent viewParent = focusedView.getParent();
2192         while ((viewParent instanceof View) && (viewParent != this)) {
2193             focusedView = (View) viewParent;
2194             viewParent = viewParent.getParent();
2195         }
2196 
2197         if (!(viewParent instanceof View)) {
2198             return null;
2199         }
2200 
2201         return focusedView;
2202     }
2203 
updateScrollIndicators()2204     void updateScrollIndicators() {
2205         if (mScrollUp != null) {
2206             mScrollUp.setVisibility(canScrollUp() ? View.VISIBLE : View.INVISIBLE);
2207         }
2208 
2209         if (mScrollDown != null) {
2210             mScrollDown.setVisibility(canScrollDown() ? View.VISIBLE : View.INVISIBLE);
2211         }
2212     }
2213 
canScrollUp()2214     private boolean canScrollUp() {
2215         boolean canScrollUp;
2216         // 0th element is not visible
2217         canScrollUp = mFirstPosition > 0;
2218 
2219         // ... Or top of 0th element is not visible
2220         if (!canScrollUp) {
2221             if (getChildCount() > 0) {
2222                 View child = getChildAt(0);
2223                 canScrollUp = child.getTop() < mListPadding.top;
2224             }
2225         }
2226 
2227         return canScrollUp;
2228     }
2229 
2230     private boolean canScrollDown() {
2231         boolean canScrollDown;
2232         int count = getChildCount();
2233 
2234         // Last item is not visible
2235         canScrollDown = (mFirstPosition + count) < mItemCount;
2236 
2237         // ... Or bottom of the last element is not visible
2238         if (!canScrollDown && count > 0) {
2239             View child = getChildAt(count - 1);
2240             canScrollDown = child.getBottom() > mBottom - mListPadding.bottom;
2241         }
2242 
2243         return canScrollDown;
2244     }
2245 
2246     @Override
2247     @ViewDebug.ExportedProperty
getSelectedView()2248     public View getSelectedView() {
2249         if (mItemCount > 0 && mSelectedPosition >= 0) {
2250             return getChildAt(mSelectedPosition - mFirstPosition);
2251         } else {
2252             return null;
2253         }
2254     }
2255 
2256     /**
2257      * List padding is the maximum of the normal view's padding and the padding of the selector.
2258      *
2259      * @see android.view.View#getPaddingTop()
2260      * @see #getSelector()
2261      *
2262      * @return The top list padding.
2263      */
getListPaddingTop()2264     public int getListPaddingTop() {
2265         return mListPadding.top;
2266     }
2267 
2268     /**
2269      * List padding is the maximum of the normal view's padding and the padding of the selector.
2270      *
2271      * @see android.view.View#getPaddingBottom()
2272      * @see #getSelector()
2273      *
2274      * @return The bottom list padding.
2275      */
getListPaddingBottom()2276     public int getListPaddingBottom() {
2277         return mListPadding.bottom;
2278     }
2279 
2280     /**
2281      * List padding is the maximum of the normal view's padding and the padding of the selector.
2282      *
2283      * @see android.view.View#getPaddingLeft()
2284      * @see #getSelector()
2285      *
2286      * @return The left list padding.
2287      */
getListPaddingLeft()2288     public int getListPaddingLeft() {
2289         return mListPadding.left;
2290     }
2291 
2292     /**
2293      * List padding is the maximum of the normal view's padding and the padding of the selector.
2294      *
2295      * @see android.view.View#getPaddingRight()
2296      * @see #getSelector()
2297      *
2298      * @return The right list padding.
2299      */
getListPaddingRight()2300     public int getListPaddingRight() {
2301         return mListPadding.right;
2302     }
2303 
2304     /**
2305      * Get a view and have it show the data associated with the specified
2306      * position. This is called when we have already discovered that the view is
2307      * not available for reuse in the recycle bin. The only choices left are
2308      * converting an old view or making a new one.
2309      *
2310      * @param position The position to display
2311      * @param isScrap Array of at least 1 boolean, the first entry will become true if
2312      *                the returned view was taken from the scrap heap, false if otherwise.
2313      *
2314      * @return A view displaying the data associated with the specified position
2315      */
obtainView(int position, boolean[] isScrap)2316     View obtainView(int position, boolean[] isScrap) {
2317         Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView");
2318 
2319         isScrap[0] = false;
2320 
2321         // Check whether we have a transient state view. Attempt to re-bind the
2322         // data and discard the view if we fail.
2323         final View transientView = mRecycler.getTransientStateView(position);
2324         if (transientView != null) {
2325             final LayoutParams params = (LayoutParams) transientView.getLayoutParams();
2326 
2327             // If the view type hasn't changed, attempt to re-bind the data.
2328             if (params.viewType == mAdapter.getItemViewType(position)) {
2329                 final View updatedView = mAdapter.getView(position, transientView, this);
2330 
2331                 // If we failed to re-bind the data, scrap the obtained view.
2332                 if (updatedView != transientView) {
2333                     setItemViewLayoutParams(updatedView, position);
2334                     mRecycler.addScrapView(updatedView, position);
2335                 }
2336             }
2337 
2338             isScrap[0] = true;
2339 
2340             // Finish the temporary detach started in addScrapView().
2341             transientView.dispatchFinishTemporaryDetach();
2342             return transientView;
2343         }
2344 
2345         final View scrapView = mRecycler.getScrapView(position);
2346         final View child = mAdapter.getView(position, scrapView, this);
2347         if (scrapView != null) {
2348             if (child != scrapView) {
2349                 // Failed to re-bind the data, return scrap to the heap.
2350                 mRecycler.addScrapView(scrapView, position);
2351             } else {
2352                 isScrap[0] = true;
2353 
2354                 // Finish the temporary detach started in addScrapView().
2355                 child.dispatchFinishTemporaryDetach();
2356             }
2357         }
2358 
2359         if (mCacheColorHint != 0) {
2360             child.setDrawingCacheBackgroundColor(mCacheColorHint);
2361         }
2362 
2363         if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
2364             child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
2365         }
2366 
2367         setItemViewLayoutParams(child, position);
2368 
2369         if (AccessibilityManager.getInstance(mContext).isEnabled()) {
2370             if (mAccessibilityDelegate == null) {
2371                 mAccessibilityDelegate = new ListItemAccessibilityDelegate();
2372             }
2373             if (child.getAccessibilityDelegate() == null) {
2374                 child.setAccessibilityDelegate(mAccessibilityDelegate);
2375             }
2376         }
2377 
2378         Trace.traceEnd(Trace.TRACE_TAG_VIEW);
2379 
2380         return child;
2381     }
2382 
setItemViewLayoutParams(View child, int position)2383     private void setItemViewLayoutParams(View child, int position) {
2384         final ViewGroup.LayoutParams vlp = child.getLayoutParams();
2385         LayoutParams lp;
2386         if (vlp == null) {
2387             lp = (LayoutParams) generateDefaultLayoutParams();
2388         } else if (!checkLayoutParams(vlp)) {
2389             lp = (LayoutParams) generateLayoutParams(vlp);
2390         } else {
2391             lp = (LayoutParams) vlp;
2392         }
2393 
2394         if (mAdapterHasStableIds) {
2395             lp.itemId = mAdapter.getItemId(position);
2396         }
2397         lp.viewType = mAdapter.getItemViewType(position);
2398         lp.isEnabled = mAdapter.isEnabled(position);
2399         if (lp != vlp) {
2400           child.setLayoutParams(lp);
2401         }
2402     }
2403 
2404     class ListItemAccessibilityDelegate extends AccessibilityDelegate {
2405         @Override
onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info)2406         public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
2407             super.onInitializeAccessibilityNodeInfo(host, info);
2408 
2409             final int position = getPositionForView(host);
2410             onInitializeAccessibilityNodeInfoForItem(host, position, info);
2411         }
2412 
2413         @Override
performAccessibilityAction(View host, int action, Bundle arguments)2414         public boolean performAccessibilityAction(View host, int action, Bundle arguments) {
2415             if (super.performAccessibilityAction(host, action, arguments)) {
2416                 return true;
2417             }
2418 
2419             final int position = getPositionForView(host);
2420             if (position == INVALID_POSITION || mAdapter == null) {
2421                 // Cannot perform actions on invalid items.
2422                 return false;
2423             }
2424 
2425             if (position >= mAdapter.getCount()) {
2426                 // The position is no longer valid, likely due to a data set
2427                 // change. We could fail here for all data set changes, since
2428                 // there is a chance that the data bound to the view may no
2429                 // longer exist at the same position within the adapter, but
2430                 // it's more consistent with the standard touch interaction to
2431                 // click at whatever may have moved into that position.
2432                 return false;
2433             }
2434 
2435             final boolean isItemEnabled;
2436             final ViewGroup.LayoutParams lp = host.getLayoutParams();
2437             if (lp instanceof AbsListView.LayoutParams) {
2438                 isItemEnabled = ((AbsListView.LayoutParams) lp).isEnabled;
2439             } else {
2440                 isItemEnabled = false;
2441             }
2442 
2443             if (!isEnabled() || !isItemEnabled) {
2444                 // Cannot perform actions on disabled items.
2445                 return false;
2446             }
2447 
2448             switch (action) {
2449                 case AccessibilityNodeInfo.ACTION_CLEAR_SELECTION: {
2450                     if (getSelectedItemPosition() == position) {
2451                         setSelection(INVALID_POSITION);
2452                         return true;
2453                     }
2454                 } return false;
2455                 case AccessibilityNodeInfo.ACTION_SELECT: {
2456                     if (getSelectedItemPosition() != position) {
2457                         setSelection(position);
2458                         return true;
2459                     }
2460                 } return false;
2461                 case AccessibilityNodeInfo.ACTION_CLICK: {
2462                     if (isClickable()) {
2463                         final long id = getItemIdAtPosition(position);
2464                         return performItemClick(host, position, id);
2465                     }
2466                 } return false;
2467                 case AccessibilityNodeInfo.ACTION_LONG_CLICK: {
2468                     if (isLongClickable()) {
2469                         final long id = getItemIdAtPosition(position);
2470                         return performLongPress(host, position, id);
2471                     }
2472                 } return false;
2473             }
2474 
2475             return false;
2476         }
2477     }
2478 
2479     /**
2480      * Initializes an {@link AccessibilityNodeInfo} with information about a
2481      * particular item in the list.
2482      *
2483      * @param view View representing the list item.
2484      * @param position Position of the list item within the adapter.
2485      * @param info Node info to populate.
2486      */
onInitializeAccessibilityNodeInfoForItem( View view, int position, AccessibilityNodeInfo info)2487     public void onInitializeAccessibilityNodeInfoForItem(
2488             View view, int position, AccessibilityNodeInfo info) {
2489         if (position == INVALID_POSITION) {
2490             // The item doesn't exist, so there's not much we can do here.
2491             return;
2492         }
2493 
2494         final boolean isItemEnabled;
2495         final ViewGroup.LayoutParams lp = view.getLayoutParams();
2496         if (lp instanceof AbsListView.LayoutParams) {
2497             isItemEnabled = ((AbsListView.LayoutParams) lp).isEnabled;
2498         } else {
2499             isItemEnabled = false;
2500         }
2501 
2502         if (!isEnabled() || !isItemEnabled) {
2503             info.setEnabled(false);
2504             return;
2505         }
2506 
2507         if (position == getSelectedItemPosition()) {
2508             info.setSelected(true);
2509             info.addAction(AccessibilityAction.ACTION_CLEAR_SELECTION);
2510         } else {
2511             info.addAction(AccessibilityAction.ACTION_SELECT);
2512         }
2513 
2514         if (isClickable()) {
2515             info.addAction(AccessibilityAction.ACTION_CLICK);
2516             info.setClickable(true);
2517         }
2518 
2519         if (isLongClickable()) {
2520             info.addAction(AccessibilityAction.ACTION_LONG_CLICK);
2521             info.setLongClickable(true);
2522         }
2523     }
2524 
2525     /**
2526      * Positions the selector in a way that mimics touch.
2527      */
positionSelectorLikeTouch(int position, View sel, float x, float y)2528     void positionSelectorLikeTouch(int position, View sel, float x, float y) {
2529         positionSelector(position, sel, true, x, y);
2530     }
2531 
2532     /**
2533      * Positions the selector in a way that mimics keyboard focus.
2534      */
positionSelectorLikeFocus(int position, View sel)2535     void positionSelectorLikeFocus(int position, View sel) {
2536         if (mSelector != null && mSelectorPosition != position && position != INVALID_POSITION) {
2537             final Rect bounds = mSelectorRect;
2538             final float x = bounds.exactCenterX();
2539             final float y = bounds.exactCenterY();
2540             positionSelector(position, sel, true, x, y);
2541         } else {
2542             positionSelector(position, sel);
2543         }
2544     }
2545 
positionSelector(int position, View sel)2546     void positionSelector(int position, View sel) {
2547         positionSelector(position, sel, false, -1, -1);
2548     }
2549 
positionSelector(int position, View sel, boolean manageHotspot, float x, float y)2550     private void positionSelector(int position, View sel, boolean manageHotspot, float x, float y) {
2551         final boolean positionChanged = position != mSelectorPosition;
2552         if (position != INVALID_POSITION) {
2553             mSelectorPosition = position;
2554         }
2555 
2556         final Rect selectorRect = mSelectorRect;
2557         selectorRect.set(sel.getLeft(), sel.getTop(), sel.getRight(), sel.getBottom());
2558         if (sel instanceof SelectionBoundsAdjuster) {
2559             ((SelectionBoundsAdjuster)sel).adjustListItemSelectionBounds(selectorRect);
2560         }
2561 
2562         // Adjust for selection padding.
2563         selectorRect.left -= mSelectionLeftPadding;
2564         selectorRect.top -= mSelectionTopPadding;
2565         selectorRect.right += mSelectionRightPadding;
2566         selectorRect.bottom += mSelectionBottomPadding;
2567 
2568         // Update the child enabled state prior to updating the selector.
2569         final boolean isChildViewEnabled = sel.isEnabled();
2570         if (mIsChildViewEnabled != isChildViewEnabled) {
2571             mIsChildViewEnabled = isChildViewEnabled;
2572         }
2573 
2574         // Update the selector drawable's state and position.
2575         final Drawable selector = mSelector;
2576         if (selector != null) {
2577             if (positionChanged) {
2578                 // Wipe out the current selector state so that we can start
2579                 // over in the new position with a fresh state.
2580                 selector.setVisible(false, false);
2581                 selector.setState(StateSet.NOTHING);
2582             }
2583             selector.setBounds(selectorRect);
2584             if (positionChanged) {
2585                 if (getVisibility() == VISIBLE) {
2586                     selector.setVisible(true, false);
2587                 }
2588                 updateSelectorState();
2589             }
2590             if (manageHotspot) {
2591                 selector.setHotspot(x, y);
2592             }
2593         }
2594     }
2595 
2596     @Override
dispatchDraw(Canvas canvas)2597     protected void dispatchDraw(Canvas canvas) {
2598         int saveCount = 0;
2599         final boolean clipToPadding = (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
2600         if (clipToPadding) {
2601             saveCount = canvas.save();
2602             final int scrollX = mScrollX;
2603             final int scrollY = mScrollY;
2604             canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,
2605                     scrollX + mRight - mLeft - mPaddingRight,
2606                     scrollY + mBottom - mTop - mPaddingBottom);
2607             mGroupFlags &= ~CLIP_TO_PADDING_MASK;
2608         }
2609 
2610         final boolean drawSelectorOnTop = mDrawSelectorOnTop;
2611         if (!drawSelectorOnTop) {
2612             drawSelector(canvas);
2613         }
2614 
2615         super.dispatchDraw(canvas);
2616 
2617         if (drawSelectorOnTop) {
2618             drawSelector(canvas);
2619         }
2620 
2621         if (clipToPadding) {
2622             canvas.restoreToCount(saveCount);
2623             mGroupFlags |= CLIP_TO_PADDING_MASK;
2624         }
2625     }
2626 
2627     @Override
isPaddingOffsetRequired()2628     protected boolean isPaddingOffsetRequired() {
2629         return (mGroupFlags & CLIP_TO_PADDING_MASK) != CLIP_TO_PADDING_MASK;
2630     }
2631 
2632     @Override
getLeftPaddingOffset()2633     protected int getLeftPaddingOffset() {
2634         return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : -mPaddingLeft;
2635     }
2636 
2637     @Override
getTopPaddingOffset()2638     protected int getTopPaddingOffset() {
2639         return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : -mPaddingTop;
2640     }
2641 
2642     @Override
getRightPaddingOffset()2643     protected int getRightPaddingOffset() {
2644         return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : mPaddingRight;
2645     }
2646 
2647     @Override
getBottomPaddingOffset()2648     protected int getBottomPaddingOffset() {
2649         return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : mPaddingBottom;
2650     }
2651 
2652     @Override
onSizeChanged(int w, int h, int oldw, int oldh)2653     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
2654         if (getChildCount() > 0) {
2655             mDataChanged = true;
2656             rememberSyncState();
2657         }
2658 
2659         if (mFastScroll != null) {
2660             mFastScroll.onSizeChanged(w, h, oldw, oldh);
2661         }
2662     }
2663 
2664     /**
2665      * @return True if the current touch mode requires that we draw the selector in the pressed
2666      *         state.
2667      */
touchModeDrawsInPressedState()2668     boolean touchModeDrawsInPressedState() {
2669         // FIXME use isPressed for this
2670         switch (mTouchMode) {
2671         case TOUCH_MODE_TAP:
2672         case TOUCH_MODE_DONE_WAITING:
2673             return true;
2674         default:
2675             return false;
2676         }
2677     }
2678 
2679     /**
2680      * Indicates whether this view is in a state where the selector should be drawn. This will
2681      * happen if we have focus but are not in touch mode, or we are in the middle of displaying
2682      * the pressed state for an item.
2683      *
2684      * @return True if the selector should be shown
2685      */
shouldShowSelector()2686     boolean shouldShowSelector() {
2687         return (isFocused() && !isInTouchMode()) || (touchModeDrawsInPressedState() && isPressed());
2688     }
2689 
drawSelector(Canvas canvas)2690     private void drawSelector(Canvas canvas) {
2691         if (!mSelectorRect.isEmpty()) {
2692             final Drawable selector = mSelector;
2693             selector.setBounds(mSelectorRect);
2694             selector.draw(canvas);
2695         }
2696     }
2697 
2698     /**
2699      * Controls whether the selection highlight drawable should be drawn on top of the item or
2700      * behind it.
2701      *
2702      * @param onTop If true, the selector will be drawn on the item it is highlighting. The default
2703      *        is false.
2704      *
2705      * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop
2706      */
setDrawSelectorOnTop(boolean onTop)2707     public void setDrawSelectorOnTop(boolean onTop) {
2708         mDrawSelectorOnTop = onTop;
2709     }
2710 
2711     /**
2712      * Set a Drawable that should be used to highlight the currently selected item.
2713      *
2714      * @param resID A Drawable resource to use as the selection highlight.
2715      *
2716      * @attr ref android.R.styleable#AbsListView_listSelector
2717      */
setSelector(@rawableRes int resID)2718     public void setSelector(@DrawableRes int resID) {
2719         setSelector(getContext().getDrawable(resID));
2720     }
2721 
setSelector(Drawable sel)2722     public void setSelector(Drawable sel) {
2723         if (mSelector != null) {
2724             mSelector.setCallback(null);
2725             unscheduleDrawable(mSelector);
2726         }
2727         mSelector = sel;
2728         Rect padding = new Rect();
2729         sel.getPadding(padding);
2730         mSelectionLeftPadding = padding.left;
2731         mSelectionTopPadding = padding.top;
2732         mSelectionRightPadding = padding.right;
2733         mSelectionBottomPadding = padding.bottom;
2734         sel.setCallback(this);
2735         updateSelectorState();
2736     }
2737 
2738     /**
2739      * Returns the selector {@link android.graphics.drawable.Drawable} that is used to draw the
2740      * selection in the list.
2741      *
2742      * @return the drawable used to display the selector
2743      */
getSelector()2744     public Drawable getSelector() {
2745         return mSelector;
2746     }
2747 
2748     /**
2749      * Sets the selector state to "pressed" and posts a CheckForKeyLongPress to see if
2750      * this is a long press.
2751      */
keyPressed()2752     void keyPressed() {
2753         if (!isEnabled() || !isClickable()) {
2754             return;
2755         }
2756 
2757         Drawable selector = mSelector;
2758         Rect selectorRect = mSelectorRect;
2759         if (selector != null && (isFocused() || touchModeDrawsInPressedState())
2760                 && !selectorRect.isEmpty()) {
2761 
2762             final View v = getChildAt(mSelectedPosition - mFirstPosition);
2763 
2764             if (v != null) {
2765                 if (v.hasFocusable()) return;
2766                 v.setPressed(true);
2767             }
2768             setPressed(true);
2769 
2770             final boolean longClickable = isLongClickable();
2771             Drawable d = selector.getCurrent();
2772             if (d != null && d instanceof TransitionDrawable) {
2773                 if (longClickable) {
2774                     ((TransitionDrawable) d).startTransition(
2775                             ViewConfiguration.getLongPressTimeout());
2776                 } else {
2777                     ((TransitionDrawable) d).resetTransition();
2778                 }
2779             }
2780             if (longClickable && !mDataChanged) {
2781                 if (mPendingCheckForKeyLongPress == null) {
2782                     mPendingCheckForKeyLongPress = new CheckForKeyLongPress();
2783                 }
2784                 mPendingCheckForKeyLongPress.rememberWindowAttachCount();
2785                 postDelayed(mPendingCheckForKeyLongPress, ViewConfiguration.getLongPressTimeout());
2786             }
2787         }
2788     }
2789 
setScrollIndicators(View up, View down)2790     public void setScrollIndicators(View up, View down) {
2791         mScrollUp = up;
2792         mScrollDown = down;
2793     }
2794 
updateSelectorState()2795     void updateSelectorState() {
2796         if (mSelector != null) {
2797             if (shouldShowSelector()) {
2798                 mSelector.setState(getDrawableStateForSelector());
2799             } else {
2800                 mSelector.setState(StateSet.NOTHING);
2801             }
2802         }
2803     }
2804 
2805     @Override
drawableStateChanged()2806     protected void drawableStateChanged() {
2807         super.drawableStateChanged();
2808         updateSelectorState();
2809     }
2810 
getDrawableStateForSelector()2811     private int[] getDrawableStateForSelector() {
2812         // If the child view is enabled then do the default behavior.
2813         if (mIsChildViewEnabled) {
2814             // Common case
2815             return super.getDrawableState();
2816         }
2817 
2818         // The selector uses this View's drawable state. The selected child view
2819         // is disabled, so we need to remove the enabled state from the drawable
2820         // states.
2821         final int enabledState = ENABLED_STATE_SET[0];
2822 
2823         // If we don't have any extra space, it will return one of the static
2824         // state arrays, and clearing the enabled state on those arrays is a
2825         // bad thing! If we specify we need extra space, it will create+copy
2826         // into a new array that is safely mutable.
2827         final int[] state = onCreateDrawableState(1);
2828 
2829         int enabledPos = -1;
2830         for (int i = state.length - 1; i >= 0; i--) {
2831             if (state[i] == enabledState) {
2832                 enabledPos = i;
2833                 break;
2834             }
2835         }
2836 
2837         // Remove the enabled state
2838         if (enabledPos >= 0) {
2839             System.arraycopy(state, enabledPos + 1, state, enabledPos,
2840                     state.length - enabledPos - 1);
2841         }
2842 
2843         return state;
2844     }
2845 
2846     @Override
verifyDrawable(Drawable dr)2847     public boolean verifyDrawable(Drawable dr) {
2848         return mSelector == dr || super.verifyDrawable(dr);
2849     }
2850 
2851     @Override
jumpDrawablesToCurrentState()2852     public void jumpDrawablesToCurrentState() {
2853         super.jumpDrawablesToCurrentState();
2854         if (mSelector != null) mSelector.jumpToCurrentState();
2855     }
2856 
2857     @Override
onAttachedToWindow()2858     protected void onAttachedToWindow() {
2859         super.onAttachedToWindow();
2860 
2861         final ViewTreeObserver treeObserver = getViewTreeObserver();
2862         treeObserver.addOnTouchModeChangeListener(this);
2863         if (mTextFilterEnabled && mPopup != null && !mGlobalLayoutListenerAddedFilter) {
2864             treeObserver.addOnGlobalLayoutListener(this);
2865         }
2866 
2867         if (mAdapter != null && mDataSetObserver == null) {
2868             mDataSetObserver = new AdapterDataSetObserver();
2869             mAdapter.registerDataSetObserver(mDataSetObserver);
2870 
2871             // Data may have changed while we were detached. Refresh.
2872             mDataChanged = true;
2873             mOldItemCount = mItemCount;
2874             mItemCount = mAdapter.getCount();
2875         }
2876     }
2877 
2878     @Override
onDetachedFromWindow()2879     protected void onDetachedFromWindow() {
2880         super.onDetachedFromWindow();
2881 
2882         mIsDetaching = true;
2883 
2884         // Dismiss the popup in case onSaveInstanceState() was not invoked
2885         dismissPopup();
2886 
2887         // Detach any view left in the scrap heap
2888         mRecycler.clear();
2889 
2890         final ViewTreeObserver treeObserver = getViewTreeObserver();
2891         treeObserver.removeOnTouchModeChangeListener(this);
2892         if (mTextFilterEnabled && mPopup != null) {
2893             treeObserver.removeOnGlobalLayoutListener(this);
2894             mGlobalLayoutListenerAddedFilter = false;
2895         }
2896 
2897         if (mAdapter != null && mDataSetObserver != null) {
2898             mAdapter.unregisterDataSetObserver(mDataSetObserver);
2899             mDataSetObserver = null;
2900         }
2901 
2902         if (mScrollStrictSpan != null) {
2903             mScrollStrictSpan.finish();
2904             mScrollStrictSpan = null;
2905         }
2906 
2907         if (mFlingStrictSpan != null) {
2908             mFlingStrictSpan.finish();
2909             mFlingStrictSpan = null;
2910         }
2911 
2912         if (mFlingRunnable != null) {
2913             removeCallbacks(mFlingRunnable);
2914         }
2915 
2916         if (mPositionScroller != null) {
2917             mPositionScroller.stop();
2918         }
2919 
2920         if (mClearScrollingCache != null) {
2921             removeCallbacks(mClearScrollingCache);
2922         }
2923 
2924         if (mPerformClick != null) {
2925             removeCallbacks(mPerformClick);
2926         }
2927 
2928         if (mTouchModeReset != null) {
2929             removeCallbacks(mTouchModeReset);
2930             mTouchModeReset.run();
2931         }
2932 
2933         mIsDetaching = false;
2934     }
2935 
2936     @Override
onWindowFocusChanged(boolean hasWindowFocus)2937     public void onWindowFocusChanged(boolean hasWindowFocus) {
2938         super.onWindowFocusChanged(hasWindowFocus);
2939 
2940         final int touchMode = isInTouchMode() ? TOUCH_MODE_ON : TOUCH_MODE_OFF;
2941 
2942         if (!hasWindowFocus) {
2943             setChildrenDrawingCacheEnabled(false);
2944             if (mFlingRunnable != null) {
2945                 removeCallbacks(mFlingRunnable);
2946                 // let the fling runnable report it's new state which
2947                 // should be idle
2948                 mFlingRunnable.endFling();
2949                 if (mPositionScroller != null) {
2950                     mPositionScroller.stop();
2951                 }
2952                 if (mScrollY != 0) {
2953                     mScrollY = 0;
2954                     invalidateParentCaches();
2955                     finishGlows();
2956                     invalidate();
2957                 }
2958             }
2959             // Always hide the type filter
2960             dismissPopup();
2961 
2962             if (touchMode == TOUCH_MODE_OFF) {
2963                 // Remember the last selected element
2964                 mResurrectToPosition = mSelectedPosition;
2965             }
2966         } else {
2967             if (mFiltered && !mPopupHidden) {
2968                 // Show the type filter only if a filter is in effect
2969                 showPopup();
2970             }
2971 
2972             // If we changed touch mode since the last time we had focus
2973             if (touchMode != mLastTouchMode && mLastTouchMode != TOUCH_MODE_UNKNOWN) {
2974                 // If we come back in trackball mode, we bring the selection back
2975                 if (touchMode == TOUCH_MODE_OFF) {
2976                     // This will trigger a layout
2977                     resurrectSelection();
2978 
2979                 // If we come back in touch mode, then we want to hide the selector
2980                 } else {
2981                     hideSelector();
2982                     mLayoutMode = LAYOUT_NORMAL;
2983                     layoutChildren();
2984                 }
2985             }
2986         }
2987 
2988         mLastTouchMode = touchMode;
2989     }
2990 
2991     @Override
onRtlPropertiesChanged(int layoutDirection)2992     public void onRtlPropertiesChanged(int layoutDirection) {
2993         super.onRtlPropertiesChanged(layoutDirection);
2994         if (mFastScroll != null) {
2995            mFastScroll.setScrollbarPosition(getVerticalScrollbarPosition());
2996         }
2997     }
2998 
2999     /**
3000      * Creates the ContextMenuInfo returned from {@link #getContextMenuInfo()}. This
3001      * methods knows the view, position and ID of the item that received the
3002      * long press.
3003      *
3004      * @param view The view that received the long press.
3005      * @param position The position of the item that received the long press.
3006      * @param id The ID of the item that received the long press.
3007      * @return The extra information that should be returned by
3008      *         {@link #getContextMenuInfo()}.
3009      */
createContextMenuInfo(View view, int position, long id)3010     ContextMenuInfo createContextMenuInfo(View view, int position, long id) {
3011         return new AdapterContextMenuInfo(view, position, id);
3012     }
3013 
3014     @Override
onCancelPendingInputEvents()3015     public void onCancelPendingInputEvents() {
3016         super.onCancelPendingInputEvents();
3017         if (mPerformClick != null) {
3018             removeCallbacks(mPerformClick);
3019         }
3020         if (mPendingCheckForTap != null) {
3021             removeCallbacks(mPendingCheckForTap);
3022         }
3023         if (mPendingCheckForLongPress != null) {
3024             removeCallbacks(mPendingCheckForLongPress);
3025         }
3026         if (mPendingCheckForKeyLongPress != null) {
3027             removeCallbacks(mPendingCheckForKeyLongPress);
3028         }
3029     }
3030 
3031     /**
3032      * A base class for Runnables that will check that their view is still attached to
3033      * the original window as when the Runnable was created.
3034      *
3035      */
3036     private class WindowRunnnable {
3037         private int mOriginalAttachCount;
3038 
rememberWindowAttachCount()3039         public void rememberWindowAttachCount() {
3040             mOriginalAttachCount = getWindowAttachCount();
3041         }
3042 
sameWindow()3043         public boolean sameWindow() {
3044             return getWindowAttachCount() == mOriginalAttachCount;
3045         }
3046     }
3047 
3048     private class PerformClick extends WindowRunnnable implements Runnable {
3049         int mClickMotionPosition;
3050 
3051         @Override
run()3052         public void run() {
3053             // The data has changed since we posted this action in the event queue,
3054             // bail out before bad things happen
3055             if (mDataChanged) return;
3056 
3057             final ListAdapter adapter = mAdapter;
3058             final int motionPosition = mClickMotionPosition;
3059             if (adapter != null && mItemCount > 0 &&
3060                     motionPosition != INVALID_POSITION &&
3061                     motionPosition < adapter.getCount() && sameWindow()) {
3062                 final View view = getChildAt(motionPosition - mFirstPosition);
3063                 // If there is no view, something bad happened (the view scrolled off the
3064                 // screen, etc.) and we should cancel the click
3065                 if (view != null) {
3066                     performItemClick(view, motionPosition, adapter.getItemId(motionPosition));
3067                 }
3068             }
3069         }
3070     }
3071 
3072     private class CheckForLongPress extends WindowRunnnable implements Runnable {
3073         @Override
run()3074         public void run() {
3075             final int motionPosition = mMotionPosition;
3076             final View child = getChildAt(motionPosition - mFirstPosition);
3077             if (child != null) {
3078                 final int longPressPosition = mMotionPosition;
3079                 final long longPressId = mAdapter.getItemId(mMotionPosition);
3080 
3081                 boolean handled = false;
3082                 if (sameWindow() && !mDataChanged) {
3083                     handled = performLongPress(child, longPressPosition, longPressId);
3084                 }
3085                 if (handled) {
3086                     mTouchMode = TOUCH_MODE_REST;
3087                     setPressed(false);
3088                     child.setPressed(false);
3089                 } else {
3090                     mTouchMode = TOUCH_MODE_DONE_WAITING;
3091                 }
3092             }
3093         }
3094     }
3095 
3096     private class CheckForKeyLongPress extends WindowRunnnable implements Runnable {
3097         @Override
run()3098         public void run() {
3099             if (isPressed() && mSelectedPosition >= 0) {
3100                 int index = mSelectedPosition - mFirstPosition;
3101                 View v = getChildAt(index);
3102 
3103                 if (!mDataChanged) {
3104                     boolean handled = false;
3105                     if (sameWindow()) {
3106                         handled = performLongPress(v, mSelectedPosition, mSelectedRowId);
3107                     }
3108                     if (handled) {
3109                         setPressed(false);
3110                         v.setPressed(false);
3111                     }
3112                 } else {
3113                     setPressed(false);
3114                     if (v != null) v.setPressed(false);
3115                 }
3116             }
3117         }
3118     }
3119 
performStylusButtonPressAction(MotionEvent ev)3120     private boolean performStylusButtonPressAction(MotionEvent ev) {
3121         if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode == null) {
3122             final View child = getChildAt(mMotionPosition - mFirstPosition);
3123             if (child != null) {
3124                 final int longPressPosition = mMotionPosition;
3125                 final long longPressId = mAdapter.getItemId(mMotionPosition);
3126                 if (performLongPress(child, longPressPosition, longPressId)) {
3127                     mTouchMode = TOUCH_MODE_REST;
3128                     setPressed(false);
3129                     child.setPressed(false);
3130                     return true;
3131                 }
3132             }
3133         }
3134         return false;
3135     }
3136 
performLongPress(final View child, final int longPressPosition, final long longPressId)3137     boolean performLongPress(final View child,
3138             final int longPressPosition, final long longPressId) {
3139         // CHOICE_MODE_MULTIPLE_MODAL takes over long press.
3140         if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
3141             if (mChoiceActionMode == null &&
3142                     (mChoiceActionMode = startActionMode(mMultiChoiceModeCallback)) != null) {
3143                 setItemChecked(longPressPosition, true);
3144                 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
3145             }
3146             return true;
3147         }
3148 
3149         boolean handled = false;
3150         if (mOnItemLongClickListener != null) {
3151             handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, child,
3152                     longPressPosition, longPressId);
3153         }
3154         if (!handled) {
3155             mContextMenuInfo = createContextMenuInfo(child, longPressPosition, longPressId);
3156             handled = super.showContextMenuForChild(AbsListView.this);
3157         }
3158         if (handled) {
3159             performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
3160         }
3161         return handled;
3162     }
3163 
3164     @Override
getContextMenuInfo()3165     protected ContextMenuInfo getContextMenuInfo() {
3166         return mContextMenuInfo;
3167     }
3168 
3169     /** @hide */
3170     @Override
showContextMenu(float x, float y, int metaState)3171     public boolean showContextMenu(float x, float y, int metaState) {
3172         final int position = pointToPosition((int)x, (int)y);
3173         if (position != INVALID_POSITION) {
3174             final long id = mAdapter.getItemId(position);
3175             View child = getChildAt(position - mFirstPosition);
3176             if (child != null) {
3177                 mContextMenuInfo = createContextMenuInfo(child, position, id);
3178                 return super.showContextMenuForChild(AbsListView.this);
3179             }
3180         }
3181         return super.showContextMenu(x, y, metaState);
3182     }
3183 
3184     @Override
showContextMenuForChild(View originalView)3185     public boolean showContextMenuForChild(View originalView) {
3186         final int longPressPosition = getPositionForView(originalView);
3187         if (longPressPosition >= 0) {
3188             final long longPressId = mAdapter.getItemId(longPressPosition);
3189             boolean handled = false;
3190 
3191             if (mOnItemLongClickListener != null) {
3192                 handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, originalView,
3193                         longPressPosition, longPressId);
3194             }
3195             if (!handled) {
3196                 mContextMenuInfo = createContextMenuInfo(
3197                         getChildAt(longPressPosition - mFirstPosition),
3198                         longPressPosition, longPressId);
3199                 handled = super.showContextMenuForChild(originalView);
3200             }
3201 
3202             return handled;
3203         }
3204         return false;
3205     }
3206 
3207     @Override
onKeyDown(int keyCode, KeyEvent event)3208     public boolean onKeyDown(int keyCode, KeyEvent event) {
3209         return false;
3210     }
3211 
3212     @Override
onKeyUp(int keyCode, KeyEvent event)3213     public boolean onKeyUp(int keyCode, KeyEvent event) {
3214         if (KeyEvent.isConfirmKey(keyCode)) {
3215             if (!isEnabled()) {
3216                 return true;
3217             }
3218             if (isClickable() && isPressed() &&
3219                     mSelectedPosition >= 0 && mAdapter != null &&
3220                     mSelectedPosition < mAdapter.getCount()) {
3221 
3222                 final View view = getChildAt(mSelectedPosition - mFirstPosition);
3223                 if (view != null) {
3224                     performItemClick(view, mSelectedPosition, mSelectedRowId);
3225                     view.setPressed(false);
3226                 }
3227                 setPressed(false);
3228                 return true;
3229             }
3230         }
3231         return super.onKeyUp(keyCode, event);
3232     }
3233 
3234     @Override
dispatchSetPressed(boolean pressed)3235     protected void dispatchSetPressed(boolean pressed) {
3236         // Don't dispatch setPressed to our children. We call setPressed on ourselves to
3237         // get the selector in the right state, but we don't want to press each child.
3238     }
3239 
3240     @Override
dispatchDrawableHotspotChanged(float x, float y)3241     public void dispatchDrawableHotspotChanged(float x, float y) {
3242         // Don't dispatch hotspot changes to children. We'll manually handle
3243         // calling drawableHotspotChanged on the correct child.
3244     }
3245 
3246     /**
3247      * Maps a point to a position in the list.
3248      *
3249      * @param x X in local coordinate
3250      * @param y Y in local coordinate
3251      * @return The position of the item which contains the specified point, or
3252      *         {@link #INVALID_POSITION} if the point does not intersect an item.
3253      */
pointToPosition(int x, int y)3254     public int pointToPosition(int x, int y) {
3255         Rect frame = mTouchFrame;
3256         if (frame == null) {
3257             mTouchFrame = new Rect();
3258             frame = mTouchFrame;
3259         }
3260 
3261         final int count = getChildCount();
3262         for (int i = count - 1; i >= 0; i--) {
3263             final View child = getChildAt(i);
3264             if (child.getVisibility() == View.VISIBLE) {
3265                 child.getHitRect(frame);
3266                 if (frame.contains(x, y)) {
3267                     return mFirstPosition + i;
3268                 }
3269             }
3270         }
3271         return INVALID_POSITION;
3272     }
3273 
3274 
3275     /**
3276      * Maps a point to a the rowId of the item which intersects that point.
3277      *
3278      * @param x X in local coordinate
3279      * @param y Y in local coordinate
3280      * @return The rowId of the item which contains the specified point, or {@link #INVALID_ROW_ID}
3281      *         if the point does not intersect an item.
3282      */
pointToRowId(int x, int y)3283     public long pointToRowId(int x, int y) {
3284         int position = pointToPosition(x, y);
3285         if (position >= 0) {
3286             return mAdapter.getItemId(position);
3287         }
3288         return INVALID_ROW_ID;
3289     }
3290 
3291     private final class CheckForTap implements Runnable {
3292         float x;
3293         float y;
3294 
3295         @Override
run()3296         public void run() {
3297             if (mTouchMode == TOUCH_MODE_DOWN) {
3298                 mTouchMode = TOUCH_MODE_TAP;
3299                 final View child = getChildAt(mMotionPosition - mFirstPosition);
3300                 if (child != null && !child.hasFocusable()) {
3301                     mLayoutMode = LAYOUT_NORMAL;
3302 
3303                     if (!mDataChanged) {
3304                         final float[] point = mTmpPoint;
3305                         point[0] = x;
3306                         point[1] = y;
3307                         transformPointToViewLocal(point, child);
3308                         child.drawableHotspotChanged(point[0], point[1]);
3309                         child.setPressed(true);
3310                         setPressed(true);
3311                         layoutChildren();
3312                         positionSelector(mMotionPosition, child);
3313                         refreshDrawableState();
3314 
3315                         final int longPressTimeout = ViewConfiguration.getLongPressTimeout();
3316                         final boolean longClickable = isLongClickable();
3317 
3318                         if (mSelector != null) {
3319                             final Drawable d = mSelector.getCurrent();
3320                             if (d != null && d instanceof TransitionDrawable) {
3321                                 if (longClickable) {
3322                                     ((TransitionDrawable) d).startTransition(longPressTimeout);
3323                                 } else {
3324                                     ((TransitionDrawable) d).resetTransition();
3325                                 }
3326                             }
3327                             mSelector.setHotspot(x, y);
3328                         }
3329 
3330                         if (longClickable) {
3331                             if (mPendingCheckForLongPress == null) {
3332                                 mPendingCheckForLongPress = new CheckForLongPress();
3333                             }
3334                             mPendingCheckForLongPress.rememberWindowAttachCount();
3335                             postDelayed(mPendingCheckForLongPress, longPressTimeout);
3336                         } else {
3337                             mTouchMode = TOUCH_MODE_DONE_WAITING;
3338                         }
3339                     } else {
3340                         mTouchMode = TOUCH_MODE_DONE_WAITING;
3341                     }
3342                 }
3343             }
3344         }
3345     }
3346 
startScrollIfNeeded(int x, int y, MotionEvent vtev)3347     private boolean startScrollIfNeeded(int x, int y, MotionEvent vtev) {
3348         // Check if we have moved far enough that it looks more like a
3349         // scroll than a tap
3350         final int deltaY = y - mMotionY;
3351         final int distance = Math.abs(deltaY);
3352         final boolean overscroll = mScrollY != 0;
3353         if ((overscroll || distance > mTouchSlop) &&
3354                 (getNestedScrollAxes() & SCROLL_AXIS_VERTICAL) == 0) {
3355             createScrollingCache();
3356             if (overscroll) {
3357                 mTouchMode = TOUCH_MODE_OVERSCROLL;
3358                 mMotionCorrection = 0;
3359             } else {
3360                 mTouchMode = TOUCH_MODE_SCROLL;
3361                 mMotionCorrection = deltaY > 0 ? mTouchSlop : -mTouchSlop;
3362             }
3363             removeCallbacks(mPendingCheckForLongPress);
3364             setPressed(false);
3365             final View motionView = getChildAt(mMotionPosition - mFirstPosition);
3366             if (motionView != null) {
3367                 motionView.setPressed(false);
3368             }
3369             reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
3370             // Time to start stealing events! Once we've stolen them, don't let anyone
3371             // steal from us
3372             final ViewParent parent = getParent();
3373             if (parent != null) {
3374                 parent.requestDisallowInterceptTouchEvent(true);
3375             }
3376             scrollIfNeeded(x, y, vtev);
3377             return true;
3378         }
3379 
3380         return false;
3381     }
3382 
scrollIfNeeded(int x, int y, MotionEvent vtev)3383     private void scrollIfNeeded(int x, int y, MotionEvent vtev) {
3384         int rawDeltaY = y - mMotionY;
3385         int scrollOffsetCorrection = 0;
3386         int scrollConsumedCorrection = 0;
3387         if (mLastY == Integer.MIN_VALUE) {
3388             rawDeltaY -= mMotionCorrection;
3389         }
3390         if (dispatchNestedPreScroll(0, mLastY != Integer.MIN_VALUE ? mLastY - y : -rawDeltaY,
3391                 mScrollConsumed, mScrollOffset)) {
3392             rawDeltaY += mScrollConsumed[1];
3393             scrollOffsetCorrection = -mScrollOffset[1];
3394             scrollConsumedCorrection = mScrollConsumed[1];
3395             if (vtev != null) {
3396                 vtev.offsetLocation(0, mScrollOffset[1]);
3397                 mNestedYOffset += mScrollOffset[1];
3398             }
3399         }
3400         final int deltaY = rawDeltaY;
3401         int incrementalDeltaY =
3402                 mLastY != Integer.MIN_VALUE ? y - mLastY + scrollConsumedCorrection : deltaY;
3403         int lastYCorrection = 0;
3404 
3405         if (mTouchMode == TOUCH_MODE_SCROLL) {
3406             if (PROFILE_SCROLLING) {
3407                 if (!mScrollProfilingStarted) {
3408                     Debug.startMethodTracing("AbsListViewScroll");
3409                     mScrollProfilingStarted = true;
3410                 }
3411             }
3412 
3413             if (mScrollStrictSpan == null) {
3414                 // If it's non-null, we're already in a scroll.
3415                 mScrollStrictSpan = StrictMode.enterCriticalSpan("AbsListView-scroll");
3416             }
3417 
3418             if (y != mLastY) {
3419                 // We may be here after stopping a fling and continuing to scroll.
3420                 // If so, we haven't disallowed intercepting touch events yet.
3421                 // Make sure that we do so in case we're in a parent that can intercept.
3422                 if ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) == 0 &&
3423                         Math.abs(rawDeltaY) > mTouchSlop) {
3424                     final ViewParent parent = getParent();
3425                     if (parent != null) {
3426                         parent.requestDisallowInterceptTouchEvent(true);
3427                     }
3428                 }
3429 
3430                 final int motionIndex;
3431                 if (mMotionPosition >= 0) {
3432                     motionIndex = mMotionPosition - mFirstPosition;
3433                 } else {
3434                     // If we don't have a motion position that we can reliably track,
3435                     // pick something in the middle to make a best guess at things below.
3436                     motionIndex = getChildCount() / 2;
3437                 }
3438 
3439                 int motionViewPrevTop = 0;
3440                 View motionView = this.getChildAt(motionIndex);
3441                 if (motionView != null) {
3442                     motionViewPrevTop = motionView.getTop();
3443                 }
3444 
3445                 // No need to do all this work if we're not going to move anyway
3446                 boolean atEdge = false;
3447                 if (incrementalDeltaY != 0) {
3448                     atEdge = trackMotionScroll(deltaY, incrementalDeltaY);
3449                 }
3450 
3451                 // Check to see if we have bumped into the scroll limit
3452                 motionView = this.getChildAt(motionIndex);
3453                 if (motionView != null) {
3454                     // Check if the top of the motion view is where it is
3455                     // supposed to be
3456                     final int motionViewRealTop = motionView.getTop();
3457                     if (atEdge) {
3458                         // Apply overscroll
3459 
3460                         int overscroll = -incrementalDeltaY -
3461                                 (motionViewRealTop - motionViewPrevTop);
3462                         if (dispatchNestedScroll(0, overscroll - incrementalDeltaY, 0, overscroll,
3463                                 mScrollOffset)) {
3464                             lastYCorrection -= mScrollOffset[1];
3465                             if (vtev != null) {
3466                                 vtev.offsetLocation(0, mScrollOffset[1]);
3467                                 mNestedYOffset += mScrollOffset[1];
3468                             }
3469                         } else {
3470                             final boolean atOverscrollEdge = overScrollBy(0, overscroll,
3471                                     0, mScrollY, 0, 0, 0, mOverscrollDistance, true);
3472 
3473                             if (atOverscrollEdge && mVelocityTracker != null) {
3474                                 // Don't allow overfling if we're at the edge
3475                                 mVelocityTracker.clear();
3476                             }
3477 
3478                             final int overscrollMode = getOverScrollMode();
3479                             if (overscrollMode == OVER_SCROLL_ALWAYS ||
3480                                     (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
3481                                             !contentFits())) {
3482                                 if (!atOverscrollEdge) {
3483                                     mDirection = 0; // Reset when entering overscroll.
3484                                     mTouchMode = TOUCH_MODE_OVERSCROLL;
3485                                 }
3486                                 if (incrementalDeltaY > 0) {
3487                                     mEdgeGlowTop.onPull((float) -overscroll / getHeight(),
3488                                             (float) x / getWidth());
3489                                     if (!mEdgeGlowBottom.isFinished()) {
3490                                         mEdgeGlowBottom.onRelease();
3491                                     }
3492                                     invalidateTopGlow();
3493                                 } else if (incrementalDeltaY < 0) {
3494                                     mEdgeGlowBottom.onPull((float) overscroll / getHeight(),
3495                                             1.f - (float) x / getWidth());
3496                                     if (!mEdgeGlowTop.isFinished()) {
3497                                         mEdgeGlowTop.onRelease();
3498                                     }
3499                                     invalidateBottomGlow();
3500                                 }
3501                             }
3502                         }
3503                     }
3504                     mMotionY = y + lastYCorrection + scrollOffsetCorrection;
3505                 }
3506                 mLastY = y + lastYCorrection + scrollOffsetCorrection;
3507             }
3508         } else if (mTouchMode == TOUCH_MODE_OVERSCROLL) {
3509             if (y != mLastY) {
3510                 final int oldScroll = mScrollY;
3511                 final int newScroll = oldScroll - incrementalDeltaY;
3512                 int newDirection = y > mLastY ? 1 : -1;
3513 
3514                 if (mDirection == 0) {
3515                     mDirection = newDirection;
3516                 }
3517 
3518                 int overScrollDistance = -incrementalDeltaY;
3519                 if ((newScroll < 0 && oldScroll >= 0) || (newScroll > 0 && oldScroll <= 0)) {
3520                     overScrollDistance = -oldScroll;
3521                     incrementalDeltaY += overScrollDistance;
3522                 } else {
3523                     incrementalDeltaY = 0;
3524                 }
3525 
3526                 if (overScrollDistance != 0) {
3527                     overScrollBy(0, overScrollDistance, 0, mScrollY, 0, 0,
3528                             0, mOverscrollDistance, true);
3529                     final int overscrollMode = getOverScrollMode();
3530                     if (overscrollMode == OVER_SCROLL_ALWAYS ||
3531                             (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
3532                                     !contentFits())) {
3533                         if (rawDeltaY > 0) {
3534                             mEdgeGlowTop.onPull((float) overScrollDistance / getHeight(),
3535                                     (float) x / getWidth());
3536                             if (!mEdgeGlowBottom.isFinished()) {
3537                                 mEdgeGlowBottom.onRelease();
3538                             }
3539                             invalidateTopGlow();
3540                         } else if (rawDeltaY < 0) {
3541                             mEdgeGlowBottom.onPull((float) overScrollDistance / getHeight(),
3542                                     1.f - (float) x / getWidth());
3543                             if (!mEdgeGlowTop.isFinished()) {
3544                                 mEdgeGlowTop.onRelease();
3545                             }
3546                             invalidateBottomGlow();
3547                         }
3548                     }
3549                 }
3550 
3551                 if (incrementalDeltaY != 0) {
3552                     // Coming back to 'real' list scrolling
3553                     if (mScrollY != 0) {
3554                         mScrollY = 0;
3555                         invalidateParentIfNeeded();
3556                     }
3557 
3558                     trackMotionScroll(incrementalDeltaY, incrementalDeltaY);
3559 
3560                     mTouchMode = TOUCH_MODE_SCROLL;
3561 
3562                     // We did not scroll the full amount. Treat this essentially like the
3563                     // start of a new touch scroll
3564                     final int motionPosition = findClosestMotionRow(y);
3565 
3566                     mMotionCorrection = 0;
3567                     View motionView = getChildAt(motionPosition - mFirstPosition);
3568                     mMotionViewOriginalTop = motionView != null ? motionView.getTop() : 0;
3569                     mMotionY =  y + scrollOffsetCorrection;
3570                     mMotionPosition = motionPosition;
3571                 }
3572                 mLastY = y + lastYCorrection + scrollOffsetCorrection;
3573                 mDirection = newDirection;
3574             }
3575         }
3576     }
3577 
invalidateTopGlow()3578     private void invalidateTopGlow() {
3579         if (mEdgeGlowTop == null) {
3580             return;
3581         }
3582         final boolean clipToPadding = getClipToPadding();
3583         final int top = clipToPadding ? mPaddingTop : 0;
3584         final int left = clipToPadding ? mPaddingLeft : 0;
3585         final int right = clipToPadding ? getWidth() - mPaddingRight : getWidth();
3586         invalidate(left, top, right, top + mEdgeGlowTop.getMaxHeight());
3587     }
3588 
invalidateBottomGlow()3589     private void invalidateBottomGlow() {
3590         if (mEdgeGlowBottom == null) {
3591             return;
3592         }
3593         final boolean clipToPadding = getClipToPadding();
3594         final int bottom = clipToPadding ? getHeight() - mPaddingBottom : getHeight();
3595         final int left = clipToPadding ? mPaddingLeft : 0;
3596         final int right = clipToPadding ? getWidth() - mPaddingRight : getWidth();
3597         invalidate(left, bottom - mEdgeGlowBottom.getMaxHeight(), right, bottom);
3598     }
3599 
3600     @Override
onTouchModeChanged(boolean isInTouchMode)3601     public void onTouchModeChanged(boolean isInTouchMode) {
3602         if (isInTouchMode) {
3603             // Get rid of the selection when we enter touch mode
3604             hideSelector();
3605             // Layout, but only if we already have done so previously.
3606             // (Otherwise may clobber a LAYOUT_SYNC layout that was requested to restore
3607             // state.)
3608             if (getHeight() > 0 && getChildCount() > 0) {
3609                 // We do not lose focus initiating a touch (since AbsListView is focusable in
3610                 // touch mode). Force an initial layout to get rid of the selection.
3611                 layoutChildren();
3612             }
3613             updateSelectorState();
3614         } else {
3615             int touchMode = mTouchMode;
3616             if (touchMode == TOUCH_MODE_OVERSCROLL || touchMode == TOUCH_MODE_OVERFLING) {
3617                 if (mFlingRunnable != null) {
3618                     mFlingRunnable.endFling();
3619                 }
3620                 if (mPositionScroller != null) {
3621                     mPositionScroller.stop();
3622                 }
3623 
3624                 if (mScrollY != 0) {
3625                     mScrollY = 0;
3626                     invalidateParentCaches();
3627                     finishGlows();
3628                     invalidate();
3629                 }
3630             }
3631         }
3632     }
3633 
3634     @Override
onTouchEvent(MotionEvent ev)3635     public boolean onTouchEvent(MotionEvent ev) {
3636         if (!isEnabled()) {
3637             // A disabled view that is clickable still consumes the touch
3638             // events, it just doesn't respond to them.
3639             return isClickable() || isLongClickable();
3640         }
3641 
3642         if (mPositionScroller != null) {
3643             mPositionScroller.stop();
3644         }
3645 
3646         if (mIsDetaching || !isAttachedToWindow()) {
3647             // Something isn't right.
3648             // Since we rely on being attached to get data set change notifications,
3649             // don't risk doing anything where we might try to resync and find things
3650             // in a bogus state.
3651             return false;
3652         }
3653 
3654         startNestedScroll(SCROLL_AXIS_VERTICAL);
3655 
3656         if (mFastScroll != null && mFastScroll.onTouchEvent(ev)) {
3657             return true;
3658         }
3659 
3660         initVelocityTrackerIfNotExists();
3661         final MotionEvent vtev = MotionEvent.obtain(ev);
3662 
3663         final int actionMasked = ev.getActionMasked();
3664         if (actionMasked == MotionEvent.ACTION_DOWN) {
3665             mNestedYOffset = 0;
3666         }
3667         vtev.offsetLocation(0, mNestedYOffset);
3668         switch (actionMasked) {
3669             case MotionEvent.ACTION_DOWN: {
3670                 onTouchDown(ev);
3671                 break;
3672             }
3673 
3674             case MotionEvent.ACTION_MOVE: {
3675                 onTouchMove(ev, vtev);
3676                 break;
3677             }
3678 
3679             case MotionEvent.ACTION_UP: {
3680                 onTouchUp(ev);
3681                 break;
3682             }
3683 
3684             case MotionEvent.ACTION_CANCEL: {
3685                 onTouchCancel();
3686                 break;
3687             }
3688 
3689             case MotionEvent.ACTION_POINTER_UP: {
3690                 onSecondaryPointerUp(ev);
3691                 final int x = mMotionX;
3692                 final int y = mMotionY;
3693                 final int motionPosition = pointToPosition(x, y);
3694                 if (motionPosition >= 0) {
3695                     // Remember where the motion event started
3696                     final View child = getChildAt(motionPosition - mFirstPosition);
3697                     mMotionViewOriginalTop = child.getTop();
3698                     mMotionPosition = motionPosition;
3699                 }
3700                 mLastY = y;
3701                 break;
3702             }
3703 
3704             case MotionEvent.ACTION_POINTER_DOWN: {
3705                 // New pointers take over dragging duties
3706                 final int index = ev.getActionIndex();
3707                 final int id = ev.getPointerId(index);
3708                 final int x = (int) ev.getX(index);
3709                 final int y = (int) ev.getY(index);
3710                 mMotionCorrection = 0;
3711                 mActivePointerId = id;
3712                 mMotionX = x;
3713                 mMotionY = y;
3714                 final int motionPosition = pointToPosition(x, y);
3715                 if (motionPosition >= 0) {
3716                     // Remember where the motion event started
3717                     final View child = getChildAt(motionPosition - mFirstPosition);
3718                     mMotionViewOriginalTop = child.getTop();
3719                     mMotionPosition = motionPosition;
3720                 }
3721                 mLastY = y;
3722                 break;
3723             }
3724         }
3725 
3726         if (mVelocityTracker != null) {
3727             mVelocityTracker.addMovement(vtev);
3728         }
3729         vtev.recycle();
3730         return true;
3731     }
3732 
onTouchDown(MotionEvent ev)3733     private void onTouchDown(MotionEvent ev) {
3734         mActivePointerId = ev.getPointerId(0);
3735 
3736         if (mTouchMode == TOUCH_MODE_OVERFLING) {
3737             // Stopped the fling. It is a scroll.
3738             mFlingRunnable.endFling();
3739             if (mPositionScroller != null) {
3740                 mPositionScroller.stop();
3741             }
3742             mTouchMode = TOUCH_MODE_OVERSCROLL;
3743             mMotionX = (int) ev.getX();
3744             mMotionY = (int) ev.getY();
3745             mLastY = mMotionY;
3746             mMotionCorrection = 0;
3747             mDirection = 0;
3748         } else {
3749             final int x = (int) ev.getX();
3750             final int y = (int) ev.getY();
3751             int motionPosition = pointToPosition(x, y);
3752 
3753             if (!mDataChanged) {
3754                 if (mTouchMode == TOUCH_MODE_FLING) {
3755                     // Stopped a fling. It is a scroll.
3756                     createScrollingCache();
3757                     mTouchMode = TOUCH_MODE_SCROLL;
3758                     mMotionCorrection = 0;
3759                     motionPosition = findMotionRow(y);
3760                     mFlingRunnable.flywheelTouch();
3761                 } else if ((motionPosition >= 0) && getAdapter().isEnabled(motionPosition)) {
3762                     // User clicked on an actual view (and was not stopping a
3763                     // fling). It might be a click or a scroll. Assume it is a
3764                     // click until proven otherwise.
3765                     mTouchMode = TOUCH_MODE_DOWN;
3766 
3767                     // FIXME Debounce
3768                     if (mPendingCheckForTap == null) {
3769                         mPendingCheckForTap = new CheckForTap();
3770                     }
3771 
3772                     mPendingCheckForTap.x = ev.getX();
3773                     mPendingCheckForTap.y = ev.getY();
3774                     postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
3775                 }
3776             }
3777 
3778             if (motionPosition >= 0) {
3779                 // Remember where the motion event started
3780                 final View v = getChildAt(motionPosition - mFirstPosition);
3781                 mMotionViewOriginalTop = v.getTop();
3782             }
3783 
3784             mMotionX = x;
3785             mMotionY = y;
3786             mMotionPosition = motionPosition;
3787             mLastY = Integer.MIN_VALUE;
3788         }
3789 
3790         if (mTouchMode == TOUCH_MODE_DOWN && mMotionPosition != INVALID_POSITION
3791                 && performButtonActionOnTouchDown(ev)) {
3792                 removeCallbacks(mPendingCheckForTap);
3793         }
3794     }
3795 
onTouchMove(MotionEvent ev, MotionEvent vtev)3796     private void onTouchMove(MotionEvent ev, MotionEvent vtev) {
3797         int pointerIndex = ev.findPointerIndex(mActivePointerId);
3798         if (pointerIndex == -1) {
3799             pointerIndex = 0;
3800             mActivePointerId = ev.getPointerId(pointerIndex);
3801         }
3802 
3803         if (mDataChanged) {
3804             // Re-sync everything if data has been changed
3805             // since the scroll operation can query the adapter.
3806             layoutChildren();
3807         }
3808 
3809         final int y = (int) ev.getY(pointerIndex);
3810 
3811         switch (mTouchMode) {
3812             case TOUCH_MODE_DOWN:
3813             case TOUCH_MODE_TAP:
3814             case TOUCH_MODE_DONE_WAITING:
3815                 // Check if we have moved far enough that it looks more like a
3816                 // scroll than a tap. If so, we'll enter scrolling mode.
3817                 if (startScrollIfNeeded((int) ev.getX(pointerIndex), y, vtev)) {
3818                     break;
3819                 }
3820                 // Otherwise, check containment within list bounds. If we're
3821                 // outside bounds, cancel any active presses.
3822                 final View motionView = getChildAt(mMotionPosition - mFirstPosition);
3823                 final float x = ev.getX(pointerIndex);
3824                 if (!pointInView(x, y, mTouchSlop)) {
3825                     setPressed(false);
3826                     if (motionView != null) {
3827                         motionView.setPressed(false);
3828                     }
3829                     removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
3830                             mPendingCheckForTap : mPendingCheckForLongPress);
3831                     mTouchMode = TOUCH_MODE_DONE_WAITING;
3832                     updateSelectorState();
3833                 } else if (motionView != null) {
3834                     // Still within bounds, update the hotspot.
3835                     final float[] point = mTmpPoint;
3836                     point[0] = x;
3837                     point[1] = y;
3838                     transformPointToViewLocal(point, motionView);
3839                     motionView.drawableHotspotChanged(point[0], point[1]);
3840                 }
3841                 break;
3842             case TOUCH_MODE_SCROLL:
3843             case TOUCH_MODE_OVERSCROLL:
3844                 scrollIfNeeded((int) ev.getX(pointerIndex), y, vtev);
3845                 break;
3846         }
3847     }
3848 
onTouchUp(MotionEvent ev)3849     private void onTouchUp(MotionEvent ev) {
3850         switch (mTouchMode) {
3851         case TOUCH_MODE_DOWN:
3852         case TOUCH_MODE_TAP:
3853         case TOUCH_MODE_DONE_WAITING:
3854             final int motionPosition = mMotionPosition;
3855             final View child = getChildAt(motionPosition - mFirstPosition);
3856             if (child != null) {
3857                 if (mTouchMode != TOUCH_MODE_DOWN) {
3858                     child.setPressed(false);
3859                 }
3860 
3861                 final float x = ev.getX();
3862                 final boolean inList = x > mListPadding.left && x < getWidth() - mListPadding.right;
3863                 if (inList && !child.hasFocusable()) {
3864                     if (mPerformClick == null) {
3865                         mPerformClick = new PerformClick();
3866                     }
3867 
3868                     final AbsListView.PerformClick performClick = mPerformClick;
3869                     performClick.mClickMotionPosition = motionPosition;
3870                     performClick.rememberWindowAttachCount();
3871 
3872                     mResurrectToPosition = motionPosition;
3873 
3874                     if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) {
3875                         removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
3876                                 mPendingCheckForTap : mPendingCheckForLongPress);
3877                         mLayoutMode = LAYOUT_NORMAL;
3878                         if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
3879                             mTouchMode = TOUCH_MODE_TAP;
3880                             setSelectedPositionInt(mMotionPosition);
3881                             layoutChildren();
3882                             child.setPressed(true);
3883                             positionSelector(mMotionPosition, child);
3884                             setPressed(true);
3885                             if (mSelector != null) {
3886                                 Drawable d = mSelector.getCurrent();
3887                                 if (d != null && d instanceof TransitionDrawable) {
3888                                     ((TransitionDrawable) d).resetTransition();
3889                                 }
3890                                 mSelector.setHotspot(x, ev.getY());
3891                             }
3892                             if (mTouchModeReset != null) {
3893                                 removeCallbacks(mTouchModeReset);
3894                             }
3895                             mTouchModeReset = new Runnable() {
3896                                 @Override
3897                                 public void run() {
3898                                     mTouchModeReset = null;
3899                                     mTouchMode = TOUCH_MODE_REST;
3900                                     child.setPressed(false);
3901                                     setPressed(false);
3902                                     if (!mDataChanged && !mIsDetaching && isAttachedToWindow()) {
3903                                         performClick.run();
3904                                     }
3905                                 }
3906                             };
3907                             postDelayed(mTouchModeReset,
3908                                     ViewConfiguration.getPressedStateDuration());
3909                         } else {
3910                             mTouchMode = TOUCH_MODE_REST;
3911                             updateSelectorState();
3912                         }
3913                         return;
3914                     } else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
3915                         performClick.run();
3916                     }
3917                 }
3918             }
3919             mTouchMode = TOUCH_MODE_REST;
3920             updateSelectorState();
3921             break;
3922         case TOUCH_MODE_SCROLL:
3923             final int childCount = getChildCount();
3924             if (childCount > 0) {
3925                 final int firstChildTop = getChildAt(0).getTop();
3926                 final int lastChildBottom = getChildAt(childCount - 1).getBottom();
3927                 final int contentTop = mListPadding.top;
3928                 final int contentBottom = getHeight() - mListPadding.bottom;
3929                 if (mFirstPosition == 0 && firstChildTop >= contentTop &&
3930                         mFirstPosition + childCount < mItemCount &&
3931                         lastChildBottom <= getHeight() - contentBottom) {
3932                     mTouchMode = TOUCH_MODE_REST;
3933                     reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
3934                 } else {
3935                     final VelocityTracker velocityTracker = mVelocityTracker;
3936                     velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
3937 
3938                     final int initialVelocity = (int)
3939                             (velocityTracker.getYVelocity(mActivePointerId) * mVelocityScale);
3940                     // Fling if we have enough velocity and we aren't at a boundary.
3941                     // Since we can potentially overfling more than we can overscroll, don't
3942                     // allow the weird behavior where you can scroll to a boundary then
3943                     // fling further.
3944                     boolean flingVelocity = Math.abs(initialVelocity) > mMinimumVelocity;
3945                     if (flingVelocity &&
3946                             !((mFirstPosition == 0 &&
3947                                     firstChildTop == contentTop - mOverscrollDistance) ||
3948                               (mFirstPosition + childCount == mItemCount &&
3949                                     lastChildBottom == contentBottom + mOverscrollDistance))) {
3950                         if (!dispatchNestedPreFling(0, -initialVelocity)) {
3951                             if (mFlingRunnable == null) {
3952                                 mFlingRunnable = new FlingRunnable();
3953                             }
3954                             reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
3955                             mFlingRunnable.start(-initialVelocity);
3956                             dispatchNestedFling(0, -initialVelocity, true);
3957                         } else {
3958                             mTouchMode = TOUCH_MODE_REST;
3959                             reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
3960                         }
3961                     } else {
3962                         mTouchMode = TOUCH_MODE_REST;
3963                         reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
3964                         if (mFlingRunnable != null) {
3965                             mFlingRunnable.endFling();
3966                         }
3967                         if (mPositionScroller != null) {
3968                             mPositionScroller.stop();
3969                         }
3970                         if (flingVelocity && !dispatchNestedPreFling(0, -initialVelocity)) {
3971                             dispatchNestedFling(0, -initialVelocity, false);
3972                         }
3973                     }
3974                 }
3975             } else {
3976                 mTouchMode = TOUCH_MODE_REST;
3977                 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
3978             }
3979             break;
3980 
3981         case TOUCH_MODE_OVERSCROLL:
3982             if (mFlingRunnable == null) {
3983                 mFlingRunnable = new FlingRunnable();
3984             }
3985             final VelocityTracker velocityTracker = mVelocityTracker;
3986             velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
3987             final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
3988 
3989             reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
3990             if (Math.abs(initialVelocity) > mMinimumVelocity) {
3991                 mFlingRunnable.startOverfling(-initialVelocity);
3992             } else {
3993                 mFlingRunnable.startSpringback();
3994             }
3995 
3996             break;
3997         }
3998 
3999         setPressed(false);
4000 
4001         if (mEdgeGlowTop != null) {
4002             mEdgeGlowTop.onRelease();
4003             mEdgeGlowBottom.onRelease();
4004         }
4005 
4006         // Need to redraw since we probably aren't drawing the selector anymore
4007         invalidate();
4008         removeCallbacks(mPendingCheckForLongPress);
4009         recycleVelocityTracker();
4010 
4011         mActivePointerId = INVALID_POINTER;
4012 
4013         if (PROFILE_SCROLLING) {
4014             if (mScrollProfilingStarted) {
4015                 Debug.stopMethodTracing();
4016                 mScrollProfilingStarted = false;
4017             }
4018         }
4019 
4020         if (mScrollStrictSpan != null) {
4021             mScrollStrictSpan.finish();
4022             mScrollStrictSpan = null;
4023         }
4024     }
4025 
onTouchCancel()4026     private void onTouchCancel() {
4027         switch (mTouchMode) {
4028         case TOUCH_MODE_OVERSCROLL:
4029             if (mFlingRunnable == null) {
4030                 mFlingRunnable = new FlingRunnable();
4031             }
4032             mFlingRunnable.startSpringback();
4033             break;
4034 
4035         case TOUCH_MODE_OVERFLING:
4036             // Do nothing - let it play out.
4037             break;
4038 
4039         default:
4040             mTouchMode = TOUCH_MODE_REST;
4041             setPressed(false);
4042             final View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
4043             if (motionView != null) {
4044                 motionView.setPressed(false);
4045             }
4046             clearScrollingCache();
4047             removeCallbacks(mPendingCheckForLongPress);
4048             recycleVelocityTracker();
4049         }
4050 
4051         if (mEdgeGlowTop != null) {
4052             mEdgeGlowTop.onRelease();
4053             mEdgeGlowBottom.onRelease();
4054         }
4055         mActivePointerId = INVALID_POINTER;
4056     }
4057 
4058     @Override
onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY)4059     protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
4060         if (mScrollY != scrollY) {
4061             onScrollChanged(mScrollX, scrollY, mScrollX, mScrollY);
4062             mScrollY = scrollY;
4063             invalidateParentIfNeeded();
4064 
4065             awakenScrollBars();
4066         }
4067     }
4068 
4069     @Override
onGenericMotionEvent(MotionEvent event)4070     public boolean onGenericMotionEvent(MotionEvent event) {
4071         if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
4072             switch (event.getAction()) {
4073                 case MotionEvent.ACTION_SCROLL:
4074                     if (mTouchMode == TOUCH_MODE_REST) {
4075                         final float vscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
4076                         if (vscroll != 0) {
4077                             final int delta = (int) (vscroll * getVerticalScrollFactor());
4078                             if (!trackMotionScroll(delta, delta)) {
4079                                 return true;
4080                             }
4081                         }
4082                     }
4083                     break;
4084 
4085                 case MotionEvent.ACTION_BUTTON_PRESS:
4086                     int actionButton = event.getActionButton();
4087                     if ((actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY
4088                             || actionButton == MotionEvent.BUTTON_SECONDARY)
4089                             && (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP)) {
4090                         if (performStylusButtonPressAction(event)) {
4091                             removeCallbacks(mPendingCheckForLongPress);
4092                             removeCallbacks(mPendingCheckForTap);
4093                         }
4094                     }
4095                     break;
4096             }
4097         }
4098 
4099         return super.onGenericMotionEvent(event);
4100     }
4101 
4102     /**
4103      * Initiate a fling with the given velocity.
4104      *
4105      * <p>Applications can use this method to manually initiate a fling as if the user
4106      * initiated it via touch interaction.</p>
4107      *
4108      * @param velocityY Vertical velocity in pixels per second. Note that this is velocity of
4109      *                  content, not velocity of a touch that initiated the fling.
4110      */
fling(int velocityY)4111     public void fling(int velocityY) {
4112         if (mFlingRunnable == null) {
4113             mFlingRunnable = new FlingRunnable();
4114         }
4115         reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
4116         mFlingRunnable.start(velocityY);
4117     }
4118 
4119     @Override
onStartNestedScroll(View child, View target, int nestedScrollAxes)4120     public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
4121         return ((nestedScrollAxes & SCROLL_AXIS_VERTICAL) != 0);
4122     }
4123 
4124     @Override
onNestedScrollAccepted(View child, View target, int axes)4125     public void onNestedScrollAccepted(View child, View target, int axes) {
4126         super.onNestedScrollAccepted(child, target, axes);
4127         startNestedScroll(SCROLL_AXIS_VERTICAL);
4128     }
4129 
4130     @Override
onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed)4131     public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
4132             int dxUnconsumed, int dyUnconsumed) {
4133         final int motionIndex = getChildCount() / 2;
4134         final View motionView = getChildAt(motionIndex);
4135         final int oldTop = motionView != null ? motionView.getTop() : 0;
4136         if (motionView == null || trackMotionScroll(-dyUnconsumed, -dyUnconsumed)) {
4137             int myUnconsumed = dyUnconsumed;
4138             int myConsumed = 0;
4139             if (motionView != null) {
4140                 myConsumed = motionView.getTop() - oldTop;
4141                 myUnconsumed -= myConsumed;
4142             }
4143             dispatchNestedScroll(0, myConsumed, 0, myUnconsumed, null);
4144         }
4145     }
4146 
4147     @Override
onNestedFling(View target, float velocityX, float velocityY, boolean consumed)4148     public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
4149         final int childCount = getChildCount();
4150         if (!consumed && childCount > 0 && canScrollList((int) velocityY) &&
4151                 Math.abs(velocityY) > mMinimumVelocity) {
4152             reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
4153             if (mFlingRunnable == null) {
4154                 mFlingRunnable = new FlingRunnable();
4155             }
4156             if (!dispatchNestedPreFling(0, velocityY)) {
4157                 mFlingRunnable.start((int) velocityY);
4158             }
4159             return true;
4160         }
4161         return dispatchNestedFling(velocityX, velocityY, consumed);
4162     }
4163 
4164     @Override
draw(Canvas canvas)4165     public void draw(Canvas canvas) {
4166         super.draw(canvas);
4167         if (mEdgeGlowTop != null) {
4168             final int scrollY = mScrollY;
4169             final boolean clipToPadding = getClipToPadding();
4170             final int width;
4171             final int height;
4172             final int translateX;
4173             final int translateY;
4174 
4175             if (clipToPadding) {
4176                 width = getWidth() - mPaddingLeft - mPaddingRight;
4177                 height = getHeight() - mPaddingTop - mPaddingBottom;
4178                 translateX = mPaddingLeft;
4179                 translateY = mPaddingTop;
4180             } else {
4181                 width = getWidth();
4182                 height = getHeight();
4183                 translateX = 0;
4184                 translateY = 0;
4185             }
4186             if (!mEdgeGlowTop.isFinished()) {
4187                 final int restoreCount = canvas.save();
4188                 canvas.clipRect(translateX, translateY,
4189                          translateX + width ,translateY + mEdgeGlowTop.getMaxHeight());
4190                 final int edgeY = Math.min(0, scrollY + mFirstPositionDistanceGuess) + translateY;
4191                 canvas.translate(translateX, edgeY);
4192                 mEdgeGlowTop.setSize(width, height);
4193                 if (mEdgeGlowTop.draw(canvas)) {
4194                     invalidateTopGlow();
4195                 }
4196                 canvas.restoreToCount(restoreCount);
4197             }
4198             if (!mEdgeGlowBottom.isFinished()) {
4199                 final int restoreCount = canvas.save();
4200                 canvas.clipRect(translateX, translateY + height - mEdgeGlowBottom.getMaxHeight(),
4201                         translateX + width, translateY + height);
4202                 final int edgeX = -width + translateX;
4203                 final int edgeY = Math.max(getHeight(), scrollY + mLastPositionDistanceGuess)
4204                         - (clipToPadding ? mPaddingBottom : 0);
4205                 canvas.translate(edgeX, edgeY);
4206                 canvas.rotate(180, width, 0);
4207                 mEdgeGlowBottom.setSize(width, height);
4208                 if (mEdgeGlowBottom.draw(canvas)) {
4209                     invalidateBottomGlow();
4210                 }
4211                 canvas.restoreToCount(restoreCount);
4212             }
4213         }
4214     }
4215 
initOrResetVelocityTracker()4216     private void initOrResetVelocityTracker() {
4217         if (mVelocityTracker == null) {
4218             mVelocityTracker = VelocityTracker.obtain();
4219         } else {
4220             mVelocityTracker.clear();
4221         }
4222     }
4223 
initVelocityTrackerIfNotExists()4224     private void initVelocityTrackerIfNotExists() {
4225         if (mVelocityTracker == null) {
4226             mVelocityTracker = VelocityTracker.obtain();
4227         }
4228     }
4229 
recycleVelocityTracker()4230     private void recycleVelocityTracker() {
4231         if (mVelocityTracker != null) {
4232             mVelocityTracker.recycle();
4233             mVelocityTracker = null;
4234         }
4235     }
4236 
4237     @Override
requestDisallowInterceptTouchEvent(boolean disallowIntercept)4238     public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
4239         if (disallowIntercept) {
4240             recycleVelocityTracker();
4241         }
4242         super.requestDisallowInterceptTouchEvent(disallowIntercept);
4243     }
4244 
4245     @Override
onInterceptHoverEvent(MotionEvent event)4246     public boolean onInterceptHoverEvent(MotionEvent event) {
4247         if (mFastScroll != null && mFastScroll.onInterceptHoverEvent(event)) {
4248             return true;
4249         }
4250 
4251         return super.onInterceptHoverEvent(event);
4252     }
4253 
4254     @Override
onInterceptTouchEvent(MotionEvent ev)4255     public boolean onInterceptTouchEvent(MotionEvent ev) {
4256         final int actionMasked = ev.getActionMasked();
4257         View v;
4258 
4259         if (mPositionScroller != null) {
4260             mPositionScroller.stop();
4261         }
4262 
4263         if (mIsDetaching || !isAttachedToWindow()) {
4264             // Something isn't right.
4265             // Since we rely on being attached to get data set change notifications,
4266             // don't risk doing anything where we might try to resync and find things
4267             // in a bogus state.
4268             return false;
4269         }
4270 
4271         if (mFastScroll != null && mFastScroll.onInterceptTouchEvent(ev)) {
4272             return true;
4273         }
4274 
4275         switch (actionMasked) {
4276         case MotionEvent.ACTION_DOWN: {
4277             int touchMode = mTouchMode;
4278             if (touchMode == TOUCH_MODE_OVERFLING || touchMode == TOUCH_MODE_OVERSCROLL) {
4279                 mMotionCorrection = 0;
4280                 return true;
4281             }
4282 
4283             final int x = (int) ev.getX();
4284             final int y = (int) ev.getY();
4285             mActivePointerId = ev.getPointerId(0);
4286 
4287             int motionPosition = findMotionRow(y);
4288             if (touchMode != TOUCH_MODE_FLING && motionPosition >= 0) {
4289                 // User clicked on an actual view (and was not stopping a fling).
4290                 // Remember where the motion event started
4291                 v = getChildAt(motionPosition - mFirstPosition);
4292                 mMotionViewOriginalTop = v.getTop();
4293                 mMotionX = x;
4294                 mMotionY = y;
4295                 mMotionPosition = motionPosition;
4296                 mTouchMode = TOUCH_MODE_DOWN;
4297                 clearScrollingCache();
4298             }
4299             mLastY = Integer.MIN_VALUE;
4300             initOrResetVelocityTracker();
4301             mVelocityTracker.addMovement(ev);
4302             mNestedYOffset = 0;
4303             startNestedScroll(SCROLL_AXIS_VERTICAL);
4304             if (touchMode == TOUCH_MODE_FLING) {
4305                 return true;
4306             }
4307             break;
4308         }
4309 
4310         case MotionEvent.ACTION_MOVE: {
4311             switch (mTouchMode) {
4312             case TOUCH_MODE_DOWN:
4313                 int pointerIndex = ev.findPointerIndex(mActivePointerId);
4314                 if (pointerIndex == -1) {
4315                     pointerIndex = 0;
4316                     mActivePointerId = ev.getPointerId(pointerIndex);
4317                 }
4318                 final int y = (int) ev.getY(pointerIndex);
4319                 initVelocityTrackerIfNotExists();
4320                 mVelocityTracker.addMovement(ev);
4321                 if (startScrollIfNeeded((int) ev.getX(pointerIndex), y, null)) {
4322                     return true;
4323                 }
4324                 break;
4325             }
4326             break;
4327         }
4328 
4329         case MotionEvent.ACTION_CANCEL:
4330         case MotionEvent.ACTION_UP: {
4331             mTouchMode = TOUCH_MODE_REST;
4332             mActivePointerId = INVALID_POINTER;
4333             recycleVelocityTracker();
4334             reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4335             stopNestedScroll();
4336             break;
4337         }
4338 
4339         case MotionEvent.ACTION_POINTER_UP: {
4340             onSecondaryPointerUp(ev);
4341             break;
4342         }
4343         }
4344 
4345         return false;
4346     }
4347 
onSecondaryPointerUp(MotionEvent ev)4348     private void onSecondaryPointerUp(MotionEvent ev) {
4349         final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
4350                 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
4351         final int pointerId = ev.getPointerId(pointerIndex);
4352         if (pointerId == mActivePointerId) {
4353             // This was our active pointer going up. Choose a new
4354             // active pointer and adjust accordingly.
4355             // TODO: Make this decision more intelligent.
4356             final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
4357             mMotionX = (int) ev.getX(newPointerIndex);
4358             mMotionY = (int) ev.getY(newPointerIndex);
4359             mMotionCorrection = 0;
4360             mActivePointerId = ev.getPointerId(newPointerIndex);
4361         }
4362     }
4363 
4364     /**
4365      * {@inheritDoc}
4366      */
4367     @Override
addTouchables(ArrayList<View> views)4368     public void addTouchables(ArrayList<View> views) {
4369         final int count = getChildCount();
4370         final int firstPosition = mFirstPosition;
4371         final ListAdapter adapter = mAdapter;
4372 
4373         if (adapter == null) {
4374             return;
4375         }
4376 
4377         for (int i = 0; i < count; i++) {
4378             final View child = getChildAt(i);
4379             if (adapter.isEnabled(firstPosition + i)) {
4380                 views.add(child);
4381             }
4382             child.addTouchables(views);
4383         }
4384     }
4385 
4386     /**
4387      * Fires an "on scroll state changed" event to the registered
4388      * {@link android.widget.AbsListView.OnScrollListener}, if any. The state change
4389      * is fired only if the specified state is different from the previously known state.
4390      *
4391      * @param newState The new scroll state.
4392      */
reportScrollStateChange(int newState)4393     void reportScrollStateChange(int newState) {
4394         if (newState != mLastScrollState) {
4395             if (mOnScrollListener != null) {
4396                 mLastScrollState = newState;
4397                 mOnScrollListener.onScrollStateChanged(this, newState);
4398             }
4399         }
4400     }
4401 
4402     /**
4403      * Responsible for fling behavior. Use {@link #start(int)} to
4404      * initiate a fling. Each frame of the fling is handled in {@link #run()}.
4405      * A FlingRunnable will keep re-posting itself until the fling is done.
4406      *
4407      */
4408     private class FlingRunnable implements Runnable {
4409         /**
4410          * Tracks the decay of a fling scroll
4411          */
4412         private final OverScroller mScroller;
4413 
4414         /**
4415          * Y value reported by mScroller on the previous fling
4416          */
4417         private int mLastFlingY;
4418 
4419         private final Runnable mCheckFlywheel = new Runnable() {
4420             @Override
4421             public void run() {
4422                 final int activeId = mActivePointerId;
4423                 final VelocityTracker vt = mVelocityTracker;
4424                 final OverScroller scroller = mScroller;
4425                 if (vt == null || activeId == INVALID_POINTER) {
4426                     return;
4427                 }
4428 
4429                 vt.computeCurrentVelocity(1000, mMaximumVelocity);
4430                 final float yvel = -vt.getYVelocity(activeId);
4431 
4432                 if (Math.abs(yvel) >= mMinimumVelocity
4433                         && scroller.isScrollingInDirection(0, yvel)) {
4434                     // Keep the fling alive a little longer
4435                     postDelayed(this, FLYWHEEL_TIMEOUT);
4436                 } else {
4437                     endFling();
4438                     mTouchMode = TOUCH_MODE_SCROLL;
4439                     reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
4440                 }
4441             }
4442         };
4443 
4444         private static final int FLYWHEEL_TIMEOUT = 40; // milliseconds
4445 
FlingRunnable()4446         FlingRunnable() {
4447             mScroller = new OverScroller(getContext());
4448         }
4449 
start(int initialVelocity)4450         void start(int initialVelocity) {
4451             int initialY = initialVelocity < 0 ? Integer.MAX_VALUE : 0;
4452             mLastFlingY = initialY;
4453             mScroller.setInterpolator(null);
4454             mScroller.fling(0, initialY, 0, initialVelocity,
4455                     0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE);
4456             mTouchMode = TOUCH_MODE_FLING;
4457             postOnAnimation(this);
4458 
4459             if (PROFILE_FLINGING) {
4460                 if (!mFlingProfilingStarted) {
4461                     Debug.startMethodTracing("AbsListViewFling");
4462                     mFlingProfilingStarted = true;
4463                 }
4464             }
4465 
4466             if (mFlingStrictSpan == null) {
4467                 mFlingStrictSpan = StrictMode.enterCriticalSpan("AbsListView-fling");
4468             }
4469         }
4470 
4471         void startSpringback() {
4472             if (mScroller.springBack(0, mScrollY, 0, 0, 0, 0)) {
4473                 mTouchMode = TOUCH_MODE_OVERFLING;
4474                 invalidate();
4475                 postOnAnimation(this);
4476             } else {
4477                 mTouchMode = TOUCH_MODE_REST;
4478                 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4479             }
4480         }
4481 
4482         void startOverfling(int initialVelocity) {
4483             mScroller.setInterpolator(null);
4484             mScroller.fling(0, mScrollY, 0, initialVelocity, 0, 0,
4485                     Integer.MIN_VALUE, Integer.MAX_VALUE, 0, getHeight());
4486             mTouchMode = TOUCH_MODE_OVERFLING;
4487             invalidate();
4488             postOnAnimation(this);
4489         }
4490 
4491         void edgeReached(int delta) {
4492             mScroller.notifyVerticalEdgeReached(mScrollY, 0, mOverflingDistance);
4493             final int overscrollMode = getOverScrollMode();
4494             if (overscrollMode == OVER_SCROLL_ALWAYS ||
4495                     (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && !contentFits())) {
4496                 mTouchMode = TOUCH_MODE_OVERFLING;
4497                 final int vel = (int) mScroller.getCurrVelocity();
4498                 if (delta > 0) {
4499                     mEdgeGlowTop.onAbsorb(vel);
4500                 } else {
4501                     mEdgeGlowBottom.onAbsorb(vel);
4502                 }
4503             } else {
4504                 mTouchMode = TOUCH_MODE_REST;
4505                 if (mPositionScroller != null) {
4506                     mPositionScroller.stop();
4507                 }
4508             }
4509             invalidate();
4510             postOnAnimation(this);
4511         }
4512 
startScroll(int distance, int duration, boolean linear)4513         void startScroll(int distance, int duration, boolean linear) {
4514             int initialY = distance < 0 ? Integer.MAX_VALUE : 0;
4515             mLastFlingY = initialY;
4516             mScroller.setInterpolator(linear ? sLinearInterpolator : null);
4517             mScroller.startScroll(0, initialY, 0, distance, duration);
4518             mTouchMode = TOUCH_MODE_FLING;
4519             postOnAnimation(this);
4520         }
4521 
4522         void endFling() {
4523             mTouchMode = TOUCH_MODE_REST;
4524 
4525             removeCallbacks(this);
4526             removeCallbacks(mCheckFlywheel);
4527 
4528             reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4529             clearScrollingCache();
4530             mScroller.abortAnimation();
4531 
4532             if (mFlingStrictSpan != null) {
4533                 mFlingStrictSpan.finish();
4534                 mFlingStrictSpan = null;
4535             }
4536         }
4537 
4538         void flywheelTouch() {
4539             postDelayed(mCheckFlywheel, FLYWHEEL_TIMEOUT);
4540         }
4541 
4542         @Override
4543         public void run() {
4544             switch (mTouchMode) {
4545             default:
4546                 endFling();
4547                 return;
4548 
4549             case TOUCH_MODE_SCROLL:
4550                 if (mScroller.isFinished()) {
4551                     return;
4552                 }
4553                 // Fall through
4554             case TOUCH_MODE_FLING: {
4555                 if (mDataChanged) {
4556                     layoutChildren();
4557                 }
4558 
4559                 if (mItemCount == 0 || getChildCount() == 0) {
4560                     endFling();
4561                     return;
4562                 }
4563 
4564                 final OverScroller scroller = mScroller;
4565                 boolean more = scroller.computeScrollOffset();
4566                 final int y = scroller.getCurrY();
4567 
4568                 // Flip sign to convert finger direction to list items direction
4569                 // (e.g. finger moving down means list is moving towards the top)
4570                 int delta = mLastFlingY - y;
4571 
4572                 // Pretend that each frame of a fling scroll is a touch scroll
4573                 if (delta > 0) {
4574                     // List is moving towards the top. Use first view as mMotionPosition
4575                     mMotionPosition = mFirstPosition;
4576                     final View firstView = getChildAt(0);
4577                     mMotionViewOriginalTop = firstView.getTop();
4578 
4579                     // Don't fling more than 1 screen
4580                     delta = Math.min(getHeight() - mPaddingBottom - mPaddingTop - 1, delta);
4581                 } else {
4582                     // List is moving towards the bottom. Use last view as mMotionPosition
4583                     int offsetToLast = getChildCount() - 1;
4584                     mMotionPosition = mFirstPosition + offsetToLast;
4585 
4586                     final View lastView = getChildAt(offsetToLast);
4587                     mMotionViewOriginalTop = lastView.getTop();
4588 
4589                     // Don't fling more than 1 screen
4590                     delta = Math.max(-(getHeight() - mPaddingBottom - mPaddingTop - 1), delta);
4591                 }
4592 
4593                 // Check to see if we have bumped into the scroll limit
4594                 View motionView = getChildAt(mMotionPosition - mFirstPosition);
4595                 int oldTop = 0;
4596                 if (motionView != null) {
4597                     oldTop = motionView.getTop();
4598                 }
4599 
4600                 // Don't stop just because delta is zero (it could have been rounded)
4601                 final boolean atEdge = trackMotionScroll(delta, delta);
4602                 final boolean atEnd = atEdge && (delta != 0);
4603                 if (atEnd) {
4604                     if (motionView != null) {
4605                         // Tweak the scroll for how far we overshot
4606                         int overshoot = -(delta - (motionView.getTop() - oldTop));
4607                         overScrollBy(0, overshoot, 0, mScrollY, 0, 0,
4608                                 0, mOverflingDistance, false);
4609                     }
4610                     if (more) {
4611                         edgeReached(delta);
4612                     }
4613                     break;
4614                 }
4615 
4616                 if (more && !atEnd) {
4617                     if (atEdge) invalidate();
4618                     mLastFlingY = y;
4619                     postOnAnimation(this);
4620                 } else {
4621                     endFling();
4622 
4623                     if (PROFILE_FLINGING) {
4624                         if (mFlingProfilingStarted) {
4625                             Debug.stopMethodTracing();
4626                             mFlingProfilingStarted = false;
4627                         }
4628 
4629                         if (mFlingStrictSpan != null) {
4630                             mFlingStrictSpan.finish();
4631                             mFlingStrictSpan = null;
4632                         }
4633                     }
4634                 }
4635                 break;
4636             }
4637 
4638             case TOUCH_MODE_OVERFLING: {
4639                 final OverScroller scroller = mScroller;
4640                 if (scroller.computeScrollOffset()) {
4641                     final int scrollY = mScrollY;
4642                     final int currY = scroller.getCurrY();
4643                     final int deltaY = currY - scrollY;
4644                     if (overScrollBy(0, deltaY, 0, scrollY, 0, 0,
4645                             0, mOverflingDistance, false)) {
4646                         final boolean crossDown = scrollY <= 0 && currY > 0;
4647                         final boolean crossUp = scrollY >= 0 && currY < 0;
4648                         if (crossDown || crossUp) {
4649                             int velocity = (int) scroller.getCurrVelocity();
4650                             if (crossUp) velocity = -velocity;
4651 
4652                             // Don't flywheel from this; we're just continuing things.
4653                             scroller.abortAnimation();
4654                             start(velocity);
4655                         } else {
4656                             startSpringback();
4657                         }
4658                     } else {
4659                         invalidate();
4660                         postOnAnimation(this);
4661                     }
4662                 } else {
4663                     endFling();
4664                 }
4665                 break;
4666             }
4667             }
4668         }
4669     }
4670 
4671     /**
4672      * The amount of friction applied to flings. The default value
4673      * is {@link ViewConfiguration#getScrollFriction}.
4674      */
4675     public void setFriction(float friction) {
4676         if (mFlingRunnable == null) {
4677             mFlingRunnable = new FlingRunnable();
4678         }
4679         mFlingRunnable.mScroller.setFriction(friction);
4680     }
4681 
4682     /**
4683      * Sets a scale factor for the fling velocity. The initial scale
4684      * factor is 1.0.
4685      *
4686      * @param scale The scale factor to multiply the velocity by.
4687      */
4688     public void setVelocityScale(float scale) {
4689         mVelocityScale = scale;
4690     }
4691 
4692     /**
4693      * Override this for better control over position scrolling.
4694      */
4695     AbsPositionScroller createPositionScroller() {
4696         return new PositionScroller();
4697     }
4698 
4699     /**
4700      * Smoothly scroll to the specified adapter position. The view will
4701      * scroll such that the indicated position is displayed.
4702      * @param position Scroll to this adapter position.
4703      */
4704     public void smoothScrollToPosition(int position) {
4705         if (mPositionScroller == null) {
4706             mPositionScroller = createPositionScroller();
4707         }
4708         mPositionScroller.start(position);
4709     }
4710 
4711     /**
4712      * Smoothly scroll to the specified adapter position. The view will scroll
4713      * such that the indicated position is displayed <code>offset</code> pixels below
4714      * the top edge of the view. If this is impossible, (e.g. the offset would scroll
4715      * the first or last item beyond the boundaries of the list) it will get as close
4716      * as possible. The scroll will take <code>duration</code> milliseconds to complete.
4717      *
4718      * @param position Position to scroll to
4719      * @param offset Desired distance in pixels of <code>position</code> from the top
4720      *               of the view when scrolling is finished
4721      * @param duration Number of milliseconds to use for the scroll
4722      */
4723     public void smoothScrollToPositionFromTop(int position, int offset, int duration) {
4724         if (mPositionScroller == null) {
4725             mPositionScroller = createPositionScroller();
4726         }
4727         mPositionScroller.startWithOffset(position, offset, duration);
4728     }
4729 
4730     /**
4731      * Smoothly scroll to the specified adapter position. The view will scroll
4732      * such that the indicated position is displayed <code>offset</code> pixels below
4733      * the top edge of the view. If this is impossible, (e.g. the offset would scroll
4734      * the first or last item beyond the boundaries of the list) it will get as close
4735      * as possible.
4736      *
4737      * @param position Position to scroll to
4738      * @param offset Desired distance in pixels of <code>position</code> from the top
4739      *               of the view when scrolling is finished
4740      */
4741     public void smoothScrollToPositionFromTop(int position, int offset) {
4742         if (mPositionScroller == null) {
4743             mPositionScroller = createPositionScroller();
4744         }
4745         mPositionScroller.startWithOffset(position, offset);
4746     }
4747 
4748     /**
4749      * Smoothly scroll to the specified adapter position. The view will
4750      * scroll such that the indicated position is displayed, but it will
4751      * stop early if scrolling further would scroll boundPosition out of
4752      * view.
4753      *
4754      * @param position Scroll to this adapter position.
4755      * @param boundPosition Do not scroll if it would move this adapter
4756      *          position out of view.
4757      */
4758     public void smoothScrollToPosition(int position, int boundPosition) {
4759         if (mPositionScroller == null) {
4760             mPositionScroller = createPositionScroller();
4761         }
4762         mPositionScroller.start(position, boundPosition);
4763     }
4764 
4765     /**
4766      * Smoothly scroll by distance pixels over duration milliseconds.
4767      * @param distance Distance to scroll in pixels.
4768      * @param duration Duration of the scroll animation in milliseconds.
4769      */
4770     public void smoothScrollBy(int distance, int duration) {
4771         smoothScrollBy(distance, duration, false);
4772     }
4773 
4774     void smoothScrollBy(int distance, int duration, boolean linear) {
4775         if (mFlingRunnable == null) {
4776             mFlingRunnable = new FlingRunnable();
4777         }
4778 
4779         // No sense starting to scroll if we're not going anywhere
4780         final int firstPos = mFirstPosition;
4781         final int childCount = getChildCount();
4782         final int lastPos = firstPos + childCount;
4783         final int topLimit = getPaddingTop();
4784         final int bottomLimit = getHeight() - getPaddingBottom();
4785 
4786         if (distance == 0 || mItemCount == 0 || childCount == 0 ||
4787                 (firstPos == 0 && getChildAt(0).getTop() == topLimit && distance < 0) ||
4788                 (lastPos == mItemCount &&
4789                         getChildAt(childCount - 1).getBottom() == bottomLimit && distance > 0)) {
4790             mFlingRunnable.endFling();
4791             if (mPositionScroller != null) {
4792                 mPositionScroller.stop();
4793             }
4794         } else {
4795             reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
4796             mFlingRunnable.startScroll(distance, duration, linear);
4797         }
4798     }
4799 
4800     /**
4801      * Allows RemoteViews to scroll relatively to a position.
4802      */
4803     void smoothScrollByOffset(int position) {
4804         int index = -1;
4805         if (position < 0) {
4806             index = getFirstVisiblePosition();
4807         } else if (position > 0) {
4808             index = getLastVisiblePosition();
4809         }
4810 
4811         if (index > -1) {
4812             View child = getChildAt(index - getFirstVisiblePosition());
4813             if (child != null) {
4814                 Rect visibleRect = new Rect();
4815                 if (child.getGlobalVisibleRect(visibleRect)) {
4816                     // the child is partially visible
4817                     int childRectArea = child.getWidth() * child.getHeight();
4818                     int visibleRectArea = visibleRect.width() * visibleRect.height();
4819                     float visibleArea = (visibleRectArea / (float) childRectArea);
4820                     final float visibleThreshold = 0.75f;
4821                     if ((position < 0) && (visibleArea < visibleThreshold)) {
4822                         // the top index is not perceivably visible so offset
4823                         // to account for showing that top index as well
4824                         ++index;
4825                     } else if ((position > 0) && (visibleArea < visibleThreshold)) {
4826                         // the bottom index is not perceivably visible so offset
4827                         // to account for showing that bottom index as well
4828                         --index;
4829                     }
4830                 }
4831                 smoothScrollToPosition(Math.max(0, Math.min(getCount(), index + position)));
4832             }
4833         }
4834     }
4835 
4836     private void createScrollingCache() {
4837         if (mScrollingCacheEnabled && !mCachingStarted && !isHardwareAccelerated()) {
4838             setChildrenDrawnWithCacheEnabled(true);
4839             setChildrenDrawingCacheEnabled(true);
4840             mCachingStarted = mCachingActive = true;
4841         }
4842     }
4843 
4844     private void clearScrollingCache() {
4845         if (!isHardwareAccelerated()) {
4846             if (mClearScrollingCache == null) {
4847                 mClearScrollingCache = new Runnable() {
4848                     @Override
4849                     public void run() {
4850                         if (mCachingStarted) {
4851                             mCachingStarted = mCachingActive = false;
4852                             setChildrenDrawnWithCacheEnabled(false);
4853                             if ((mPersistentDrawingCache & PERSISTENT_SCROLLING_CACHE) == 0) {
4854                                 setChildrenDrawingCacheEnabled(false);
4855                             }
4856                             if (!isAlwaysDrawnWithCacheEnabled()) {
4857                                 invalidate();
4858                             }
4859                         }
4860                     }
4861                 };
4862             }
4863             post(mClearScrollingCache);
4864         }
4865     }
4866 
4867     /**
4868      * Scrolls the list items within the view by a specified number of pixels.
4869      *
4870      * @param y the amount of pixels to scroll by vertically
4871      * @see #canScrollList(int)
4872      */
4873     public void scrollListBy(int y) {
4874         trackMotionScroll(-y, -y);
4875     }
4876 
4877     /**
4878      * Check if the items in the list can be scrolled in a certain direction.
4879      *
4880      * @param direction Negative to check scrolling up, positive to check
4881      *            scrolling down.
4882      * @return true if the list can be scrolled in the specified direction,
4883      *         false otherwise.
4884      * @see #scrollListBy(int)
4885      */
4886     public boolean canScrollList(int direction) {
4887         final int childCount = getChildCount();
4888         if (childCount == 0) {
4889             return false;
4890         }
4891 
4892         final int firstPosition = mFirstPosition;
4893         final Rect listPadding = mListPadding;
4894         if (direction > 0) {
4895             final int lastBottom = getChildAt(childCount - 1).getBottom();
4896             final int lastPosition = firstPosition + childCount;
4897             return lastPosition < mItemCount || lastBottom > getHeight() - listPadding.bottom;
4898         } else {
4899             final int firstTop = getChildAt(0).getTop();
4900             return firstPosition > 0 || firstTop < listPadding.top;
4901         }
4902     }
4903 
4904     /**
4905      * Track a motion scroll
4906      *
4907      * @param deltaY Amount to offset mMotionView. This is the accumulated delta since the motion
4908      *        began. Positive numbers mean the user's finger is moving down the screen.
4909      * @param incrementalDeltaY Change in deltaY from the previous event.
4910      * @return true if we're already at the beginning/end of the list and have nothing to do.
4911      */
4912     boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
4913         final int childCount = getChildCount();
4914         if (childCount == 0) {
4915             return true;
4916         }
4917 
4918         final int firstTop = getChildAt(0).getTop();
4919         final int lastBottom = getChildAt(childCount - 1).getBottom();
4920 
4921         final Rect listPadding = mListPadding;
4922 
4923         // "effective padding" In this case is the amount of padding that affects
4924         // how much space should not be filled by items. If we don't clip to padding
4925         // there is no effective padding.
4926         int effectivePaddingTop = 0;
4927         int effectivePaddingBottom = 0;
4928         if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
4929             effectivePaddingTop = listPadding.top;
4930             effectivePaddingBottom = listPadding.bottom;
4931         }
4932 
4933          // FIXME account for grid vertical spacing too?
4934         final int spaceAbove = effectivePaddingTop - firstTop;
4935         final int end = getHeight() - effectivePaddingBottom;
4936         final int spaceBelow = lastBottom - end;
4937 
4938         final int height = getHeight() - mPaddingBottom - mPaddingTop;
4939         if (deltaY < 0) {
4940             deltaY = Math.max(-(height - 1), deltaY);
4941         } else {
4942             deltaY = Math.min(height - 1, deltaY);
4943         }
4944 
4945         if (incrementalDeltaY < 0) {
4946             incrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY);
4947         } else {
4948             incrementalDeltaY = Math.min(height - 1, incrementalDeltaY);
4949         }
4950 
4951         final int firstPosition = mFirstPosition;
4952 
4953         // Update our guesses for where the first and last views are
4954         if (firstPosition == 0) {
4955             mFirstPositionDistanceGuess = firstTop - listPadding.top;
4956         } else {
4957             mFirstPositionDistanceGuess += incrementalDeltaY;
4958         }
4959         if (firstPosition + childCount == mItemCount) {
4960             mLastPositionDistanceGuess = lastBottom + listPadding.bottom;
4961         } else {
4962             mLastPositionDistanceGuess += incrementalDeltaY;
4963         }
4964 
4965         final boolean cannotScrollDown = (firstPosition == 0 &&
4966                 firstTop >= listPadding.top && incrementalDeltaY >= 0);
4967         final boolean cannotScrollUp = (firstPosition + childCount == mItemCount &&
4968                 lastBottom <= getHeight() - listPadding.bottom && incrementalDeltaY <= 0);
4969 
4970         if (cannotScrollDown || cannotScrollUp) {
4971             return incrementalDeltaY != 0;
4972         }
4973 
4974         final boolean down = incrementalDeltaY < 0;
4975 
4976         final boolean inTouchMode = isInTouchMode();
4977         if (inTouchMode) {
4978             hideSelector();
4979         }
4980 
4981         final int headerViewsCount = getHeaderViewsCount();
4982         final int footerViewsStart = mItemCount - getFooterViewsCount();
4983 
4984         int start = 0;
4985         int count = 0;
4986 
4987         if (down) {
4988             int top = -incrementalDeltaY;
4989             if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
4990                 top += listPadding.top;
4991             }
4992             for (int i = 0; i < childCount; i++) {
4993                 final View child = getChildAt(i);
4994                 if (child.getBottom() >= top) {
4995                     break;
4996                 } else {
4997                     count++;
4998                     int position = firstPosition + i;
4999                     if (position >= headerViewsCount && position < footerViewsStart) {
5000                         // The view will be rebound to new data, clear any
5001                         // system-managed transient state.
5002                         child.clearAccessibilityFocus();
5003                         mRecycler.addScrapView(child, position);
5004                     }
5005                 }
5006             }
5007         } else {
5008             int bottom = getHeight() - incrementalDeltaY;
5009             if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
5010                 bottom -= listPadding.bottom;
5011             }
5012             for (int i = childCount - 1; i >= 0; i--) {
5013                 final View child = getChildAt(i);
5014                 if (child.getTop() <= bottom) {
5015                     break;
5016                 } else {
5017                     start = i;
5018                     count++;
5019                     int position = firstPosition + i;
5020                     if (position >= headerViewsCount && position < footerViewsStart) {
5021                         // The view will be rebound to new data, clear any
5022                         // system-managed transient state.
5023                         child.clearAccessibilityFocus();
5024                         mRecycler.addScrapView(child, position);
5025                     }
5026                 }
5027             }
5028         }
5029 
5030         mMotionViewNewTop = mMotionViewOriginalTop + deltaY;
5031 
5032         mBlockLayoutRequests = true;
5033 
5034         if (count > 0) {
5035             detachViewsFromParent(start, count);
5036             mRecycler.removeSkippedScrap();
5037         }
5038 
5039         // invalidate before moving the children to avoid unnecessary invalidate
5040         // calls to bubble up from the children all the way to the top
5041         if (!awakenScrollBars()) {
5042            invalidate();
5043         }
5044 
5045         offsetChildrenTopAndBottom(incrementalDeltaY);
5046 
5047         if (down) {
5048             mFirstPosition += count;
5049         }
5050 
5051         final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
5052         if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
5053             fillGap(down);
5054         }
5055 
5056         if (!inTouchMode && mSelectedPosition != INVALID_POSITION) {
5057             final int childIndex = mSelectedPosition - mFirstPosition;
5058             if (childIndex >= 0 && childIndex < getChildCount()) {
5059                 positionSelector(mSelectedPosition, getChildAt(childIndex));
5060             }
5061         } else if (mSelectorPosition != INVALID_POSITION) {
5062             final int childIndex = mSelectorPosition - mFirstPosition;
5063             if (childIndex >= 0 && childIndex < getChildCount()) {
5064                 positionSelector(INVALID_POSITION, getChildAt(childIndex));
5065             }
5066         } else {
5067             mSelectorRect.setEmpty();
5068         }
5069 
5070         mBlockLayoutRequests = false;
5071 
5072         invokeOnItemScrollListener();
5073 
5074         return false;
5075     }
5076 
5077     /**
5078      * Returns the number of header views in the list. Header views are special views
5079      * at the top of the list that should not be recycled during a layout.
5080      *
5081      * @return The number of header views, 0 in the default implementation.
5082      */
5083     int getHeaderViewsCount() {
5084         return 0;
5085     }
5086 
5087     /**
5088      * Returns the number of footer views in the list. Footer views are special views
5089      * at the bottom of the list that should not be recycled during a layout.
5090      *
5091      * @return The number of footer views, 0 in the default implementation.
5092      */
5093     int getFooterViewsCount() {
5094         return 0;
5095     }
5096 
5097     /**
5098      * Fills the gap left open by a touch-scroll. During a touch scroll, children that
5099      * remain on screen are shifted and the other ones are discarded. The role of this
5100      * method is to fill the gap thus created by performing a partial layout in the
5101      * empty space.
5102      *
5103      * @param down true if the scroll is going down, false if it is going up
5104      */
5105     abstract void fillGap(boolean down);
5106 
hideSelector()5107     void hideSelector() {
5108         if (mSelectedPosition != INVALID_POSITION) {
5109             if (mLayoutMode != LAYOUT_SPECIFIC) {
5110                 mResurrectToPosition = mSelectedPosition;
5111             }
5112             if (mNextSelectedPosition >= 0 && mNextSelectedPosition != mSelectedPosition) {
5113                 mResurrectToPosition = mNextSelectedPosition;
5114             }
5115             setSelectedPositionInt(INVALID_POSITION);
5116             setNextSelectedPositionInt(INVALID_POSITION);
5117             mSelectedTop = 0;
5118         }
5119     }
5120 
5121     /**
5122      * @return A position to select. First we try mSelectedPosition. If that has been clobbered by
5123      * entering touch mode, we then try mResurrectToPosition. Values are pinned to the range
5124      * of items available in the adapter
5125      */
reconcileSelectedPosition()5126     int reconcileSelectedPosition() {
5127         int position = mSelectedPosition;
5128         if (position < 0) {
5129             position = mResurrectToPosition;
5130         }
5131         position = Math.max(0, position);
5132         position = Math.min(position, mItemCount - 1);
5133         return position;
5134     }
5135 
5136     /**
5137      * Find the row closest to y. This row will be used as the motion row when scrolling
5138      *
5139      * @param y Where the user touched
5140      * @return The position of the first (or only) item in the row containing y
5141      */
5142     abstract int findMotionRow(int y);
5143 
5144     /**
5145      * Find the row closest to y. This row will be used as the motion row when scrolling.
5146      *
5147      * @param y Where the user touched
5148      * @return The position of the first (or only) item in the row closest to y
5149      */
findClosestMotionRow(int y)5150     int findClosestMotionRow(int y) {
5151         final int childCount = getChildCount();
5152         if (childCount == 0) {
5153             return INVALID_POSITION;
5154         }
5155 
5156         final int motionRow = findMotionRow(y);
5157         return motionRow != INVALID_POSITION ? motionRow : mFirstPosition + childCount - 1;
5158     }
5159 
5160     /**
5161      * Causes all the views to be rebuilt and redrawn.
5162      */
invalidateViews()5163     public void invalidateViews() {
5164         mDataChanged = true;
5165         rememberSyncState();
5166         requestLayout();
5167         invalidate();
5168     }
5169 
5170     /**
5171      * If there is a selection returns false.
5172      * Otherwise resurrects the selection and returns true if resurrected.
5173      */
resurrectSelectionIfNeeded()5174     boolean resurrectSelectionIfNeeded() {
5175         if (mSelectedPosition < 0 && resurrectSelection()) {
5176             updateSelectorState();
5177             return true;
5178         }
5179         return false;
5180     }
5181 
5182     /**
5183      * Makes the item at the supplied position selected.
5184      *
5185      * @param position the position of the new selection
5186      */
5187     abstract void setSelectionInt(int position);
5188 
5189     /**
5190      * Attempt to bring the selection back if the user is switching from touch
5191      * to trackball mode
5192      * @return Whether selection was set to something.
5193      */
resurrectSelection()5194     boolean resurrectSelection() {
5195         final int childCount = getChildCount();
5196 
5197         if (childCount <= 0) {
5198             return false;
5199         }
5200 
5201         int selectedTop = 0;
5202         int selectedPos;
5203         int childrenTop = mListPadding.top;
5204         int childrenBottom = mBottom - mTop - mListPadding.bottom;
5205         final int firstPosition = mFirstPosition;
5206         final int toPosition = mResurrectToPosition;
5207         boolean down = true;
5208 
5209         if (toPosition >= firstPosition && toPosition < firstPosition + childCount) {
5210             selectedPos = toPosition;
5211 
5212             final View selected = getChildAt(selectedPos - mFirstPosition);
5213             selectedTop = selected.getTop();
5214             int selectedBottom = selected.getBottom();
5215 
5216             // We are scrolled, don't get in the fade
5217             if (selectedTop < childrenTop) {
5218                 selectedTop = childrenTop + getVerticalFadingEdgeLength();
5219             } else if (selectedBottom > childrenBottom) {
5220                 selectedTop = childrenBottom - selected.getMeasuredHeight()
5221                         - getVerticalFadingEdgeLength();
5222             }
5223         } else {
5224             if (toPosition < firstPosition) {
5225                 // Default to selecting whatever is first
5226                 selectedPos = firstPosition;
5227                 for (int i = 0; i < childCount; i++) {
5228                     final View v = getChildAt(i);
5229                     final int top = v.getTop();
5230 
5231                     if (i == 0) {
5232                         // Remember the position of the first item
5233                         selectedTop = top;
5234                         // See if we are scrolled at all
5235                         if (firstPosition > 0 || top < childrenTop) {
5236                             // If we are scrolled, don't select anything that is
5237                             // in the fade region
5238                             childrenTop += getVerticalFadingEdgeLength();
5239                         }
5240                     }
5241                     if (top >= childrenTop) {
5242                         // Found a view whose top is fully visisble
5243                         selectedPos = firstPosition + i;
5244                         selectedTop = top;
5245                         break;
5246                     }
5247                 }
5248             } else {
5249                 final int itemCount = mItemCount;
5250                 down = false;
5251                 selectedPos = firstPosition + childCount - 1;
5252 
5253                 for (int i = childCount - 1; i >= 0; i--) {
5254                     final View v = getChildAt(i);
5255                     final int top = v.getTop();
5256                     final int bottom = v.getBottom();
5257 
5258                     if (i == childCount - 1) {
5259                         selectedTop = top;
5260                         if (firstPosition + childCount < itemCount || bottom > childrenBottom) {
5261                             childrenBottom -= getVerticalFadingEdgeLength();
5262                         }
5263                     }
5264 
5265                     if (bottom <= childrenBottom) {
5266                         selectedPos = firstPosition + i;
5267                         selectedTop = top;
5268                         break;
5269                     }
5270                 }
5271             }
5272         }
5273 
5274         mResurrectToPosition = INVALID_POSITION;
5275         removeCallbacks(mFlingRunnable);
5276         if (mPositionScroller != null) {
5277             mPositionScroller.stop();
5278         }
5279         mTouchMode = TOUCH_MODE_REST;
5280         clearScrollingCache();
5281         mSpecificTop = selectedTop;
5282         selectedPos = lookForSelectablePosition(selectedPos, down);
5283         if (selectedPos >= firstPosition && selectedPos <= getLastVisiblePosition()) {
5284             mLayoutMode = LAYOUT_SPECIFIC;
5285             updateSelectorState();
5286             setSelectionInt(selectedPos);
5287             invokeOnItemScrollListener();
5288         } else {
5289             selectedPos = INVALID_POSITION;
5290         }
5291         reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
5292 
5293         return selectedPos >= 0;
5294     }
5295 
confirmCheckedPositionsById()5296     void confirmCheckedPositionsById() {
5297         // Clear out the positional check states, we'll rebuild it below from IDs.
5298         mCheckStates.clear();
5299 
5300         boolean checkedCountChanged = false;
5301         for (int checkedIndex = 0; checkedIndex < mCheckedIdStates.size(); checkedIndex++) {
5302             final long id = mCheckedIdStates.keyAt(checkedIndex);
5303             final int lastPos = mCheckedIdStates.valueAt(checkedIndex);
5304 
5305             final long lastPosId = mAdapter.getItemId(lastPos);
5306             if (id != lastPosId) {
5307                 // Look around to see if the ID is nearby. If not, uncheck it.
5308                 final int start = Math.max(0, lastPos - CHECK_POSITION_SEARCH_DISTANCE);
5309                 final int end = Math.min(lastPos + CHECK_POSITION_SEARCH_DISTANCE, mItemCount);
5310                 boolean found = false;
5311                 for (int searchPos = start; searchPos < end; searchPos++) {
5312                     final long searchId = mAdapter.getItemId(searchPos);
5313                     if (id == searchId) {
5314                         found = true;
5315                         mCheckStates.put(searchPos, true);
5316                         mCheckedIdStates.setValueAt(checkedIndex, searchPos);
5317                         break;
5318                     }
5319                 }
5320 
5321                 if (!found) {
5322                     mCheckedIdStates.delete(id);
5323                     checkedIndex--;
5324                     mCheckedItemCount--;
5325                     checkedCountChanged = true;
5326                     if (mChoiceActionMode != null && mMultiChoiceModeCallback != null) {
5327                         mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
5328                                 lastPos, id, false);
5329                     }
5330                 }
5331             } else {
5332                 mCheckStates.put(lastPos, true);
5333             }
5334         }
5335 
5336         if (checkedCountChanged && mChoiceActionMode != null) {
5337             mChoiceActionMode.invalidate();
5338         }
5339     }
5340 
5341     @Override
handleDataChanged()5342     protected void handleDataChanged() {
5343         int count = mItemCount;
5344         int lastHandledItemCount = mLastHandledItemCount;
5345         mLastHandledItemCount = mItemCount;
5346 
5347         if (mChoiceMode != CHOICE_MODE_NONE && mAdapter != null && mAdapter.hasStableIds()) {
5348             confirmCheckedPositionsById();
5349         }
5350 
5351         // TODO: In the future we can recycle these views based on stable ID instead.
5352         mRecycler.clearTransientStateViews();
5353 
5354         if (count > 0) {
5355             int newPos;
5356             int selectablePos;
5357 
5358             // Find the row we are supposed to sync to
5359             if (mNeedSync) {
5360                 // Update this first, since setNextSelectedPositionInt inspects it
5361                 mNeedSync = false;
5362                 mPendingSync = null;
5363 
5364                 if (mTranscriptMode == TRANSCRIPT_MODE_ALWAYS_SCROLL) {
5365                     mLayoutMode = LAYOUT_FORCE_BOTTOM;
5366                     return;
5367                 } else if (mTranscriptMode == TRANSCRIPT_MODE_NORMAL) {
5368                     if (mForceTranscriptScroll) {
5369                         mForceTranscriptScroll = false;
5370                         mLayoutMode = LAYOUT_FORCE_BOTTOM;
5371                         return;
5372                     }
5373                     final int childCount = getChildCount();
5374                     final int listBottom = getHeight() - getPaddingBottom();
5375                     final View lastChild = getChildAt(childCount - 1);
5376                     final int lastBottom = lastChild != null ? lastChild.getBottom() : listBottom;
5377                     if (mFirstPosition + childCount >= lastHandledItemCount &&
5378                             lastBottom <= listBottom) {
5379                         mLayoutMode = LAYOUT_FORCE_BOTTOM;
5380                         return;
5381                     }
5382                     // Something new came in and we didn't scroll; give the user a clue that
5383                     // there's something new.
5384                     awakenScrollBars();
5385                 }
5386 
5387                 switch (mSyncMode) {
5388                 case SYNC_SELECTED_POSITION:
5389                     if (isInTouchMode()) {
5390                         // We saved our state when not in touch mode. (We know this because
5391                         // mSyncMode is SYNC_SELECTED_POSITION.) Now we are trying to
5392                         // restore in touch mode. Just leave mSyncPosition as it is (possibly
5393                         // adjusting if the available range changed) and return.
5394                         mLayoutMode = LAYOUT_SYNC;
5395                         mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
5396 
5397                         return;
5398                     } else {
5399                         // See if we can find a position in the new data with the same
5400                         // id as the old selection. This will change mSyncPosition.
5401                         newPos = findSyncPosition();
5402                         if (newPos >= 0) {
5403                             // Found it. Now verify that new selection is still selectable
5404                             selectablePos = lookForSelectablePosition(newPos, true);
5405                             if (selectablePos == newPos) {
5406                                 // Same row id is selected
5407                                 mSyncPosition = newPos;
5408 
5409                                 if (mSyncHeight == getHeight()) {
5410                                     // If we are at the same height as when we saved state, try
5411                                     // to restore the scroll position too.
5412                                     mLayoutMode = LAYOUT_SYNC;
5413                                 } else {
5414                                     // We are not the same height as when the selection was saved, so
5415                                     // don't try to restore the exact position
5416                                     mLayoutMode = LAYOUT_SET_SELECTION;
5417                                 }
5418 
5419                                 // Restore selection
5420                                 setNextSelectedPositionInt(newPos);
5421                                 return;
5422                             }
5423                         }
5424                     }
5425                     break;
5426                 case SYNC_FIRST_POSITION:
5427                     // Leave mSyncPosition as it is -- just pin to available range
5428                     mLayoutMode = LAYOUT_SYNC;
5429                     mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
5430 
5431                     return;
5432                 }
5433             }
5434 
5435             if (!isInTouchMode()) {
5436                 // We couldn't find matching data -- try to use the same position
5437                 newPos = getSelectedItemPosition();
5438 
5439                 // Pin position to the available range
5440                 if (newPos >= count) {
5441                     newPos = count - 1;
5442                 }
5443                 if (newPos < 0) {
5444                     newPos = 0;
5445                 }
5446 
5447                 // Make sure we select something selectable -- first look down
5448                 selectablePos = lookForSelectablePosition(newPos, true);
5449 
5450                 if (selectablePos >= 0) {
5451                     setNextSelectedPositionInt(selectablePos);
5452                     return;
5453                 } else {
5454                     // Looking down didn't work -- try looking up
5455                     selectablePos = lookForSelectablePosition(newPos, false);
5456                     if (selectablePos >= 0) {
5457                         setNextSelectedPositionInt(selectablePos);
5458                         return;
5459                     }
5460                 }
5461             } else {
5462 
5463                 // We already know where we want to resurrect the selection
5464                 if (mResurrectToPosition >= 0) {
5465                     return;
5466                 }
5467             }
5468 
5469         }
5470 
5471         // Nothing is selected. Give up and reset everything.
5472         mLayoutMode = mStackFromBottom ? LAYOUT_FORCE_BOTTOM : LAYOUT_FORCE_TOP;
5473         mSelectedPosition = INVALID_POSITION;
5474         mSelectedRowId = INVALID_ROW_ID;
5475         mNextSelectedPosition = INVALID_POSITION;
5476         mNextSelectedRowId = INVALID_ROW_ID;
5477         mNeedSync = false;
5478         mPendingSync = null;
5479         mSelectorPosition = INVALID_POSITION;
5480         checkSelectionChanged();
5481     }
5482 
5483     @Override
onDisplayHint(int hint)5484     protected void onDisplayHint(int hint) {
5485         super.onDisplayHint(hint);
5486         switch (hint) {
5487             case INVISIBLE:
5488                 if (mPopup != null && mPopup.isShowing()) {
5489                     dismissPopup();
5490                 }
5491                 break;
5492             case VISIBLE:
5493                 if (mFiltered && mPopup != null && !mPopup.isShowing()) {
5494                     showPopup();
5495                 }
5496                 break;
5497         }
5498         mPopupHidden = hint == INVISIBLE;
5499     }
5500 
5501     /**
5502      * Removes the filter window
5503      */
dismissPopup()5504     private void dismissPopup() {
5505         if (mPopup != null) {
5506             mPopup.dismiss();
5507         }
5508     }
5509 
5510     /**
5511      * Shows the filter window
5512      */
showPopup()5513     private void showPopup() {
5514         // Make sure we have a window before showing the popup
5515         if (getWindowVisibility() == View.VISIBLE) {
5516             createTextFilter(true);
5517             positionPopup();
5518             // Make sure we get focus if we are showing the popup
5519             checkFocus();
5520         }
5521     }
5522 
positionPopup()5523     private void positionPopup() {
5524         int screenHeight = getResources().getDisplayMetrics().heightPixels;
5525         final int[] xy = new int[2];
5526         getLocationOnScreen(xy);
5527         // TODO: The 20 below should come from the theme
5528         // TODO: And the gravity should be defined in the theme as well
5529         final int bottomGap = screenHeight - xy[1] - getHeight() + (int) (mDensityScale * 20);
5530         if (!mPopup.isShowing()) {
5531             mPopup.showAtLocation(this, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL,
5532                     xy[0], bottomGap);
5533         } else {
5534             mPopup.update(xy[0], bottomGap, -1, -1);
5535         }
5536     }
5537 
5538     /**
5539      * What is the distance between the source and destination rectangles given the direction of
5540      * focus navigation between them? The direction basically helps figure out more quickly what is
5541      * self evident by the relationship between the rects...
5542      *
5543      * @param source the source rectangle
5544      * @param dest the destination rectangle
5545      * @param direction the direction
5546      * @return the distance between the rectangles
5547      */
getDistance(Rect source, Rect dest, int direction)5548     static int getDistance(Rect source, Rect dest, int direction) {
5549         int sX, sY; // source x, y
5550         int dX, dY; // dest x, y
5551         switch (direction) {
5552         case View.FOCUS_RIGHT:
5553             sX = source.right;
5554             sY = source.top + source.height() / 2;
5555             dX = dest.left;
5556             dY = dest.top + dest.height() / 2;
5557             break;
5558         case View.FOCUS_DOWN:
5559             sX = source.left + source.width() / 2;
5560             sY = source.bottom;
5561             dX = dest.left + dest.width() / 2;
5562             dY = dest.top;
5563             break;
5564         case View.FOCUS_LEFT:
5565             sX = source.left;
5566             sY = source.top + source.height() / 2;
5567             dX = dest.right;
5568             dY = dest.top + dest.height() / 2;
5569             break;
5570         case View.FOCUS_UP:
5571             sX = source.left + source.width() / 2;
5572             sY = source.top;
5573             dX = dest.left + dest.width() / 2;
5574             dY = dest.bottom;
5575             break;
5576         case View.FOCUS_FORWARD:
5577         case View.FOCUS_BACKWARD:
5578             sX = source.right + source.width() / 2;
5579             sY = source.top + source.height() / 2;
5580             dX = dest.left + dest.width() / 2;
5581             dY = dest.top + dest.height() / 2;
5582             break;
5583         default:
5584             throw new IllegalArgumentException("direction must be one of "
5585                     + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, "
5586                     + "FOCUS_FORWARD, FOCUS_BACKWARD}.");
5587         }
5588         int deltaX = dX - sX;
5589         int deltaY = dY - sY;
5590         return deltaY * deltaY + deltaX * deltaX;
5591     }
5592 
5593     @Override
isInFilterMode()5594     protected boolean isInFilterMode() {
5595         return mFiltered;
5596     }
5597 
5598     /**
5599      * Sends a key to the text filter window
5600      *
5601      * @param keyCode The keycode for the event
5602      * @param event The actual key event
5603      *
5604      * @return True if the text filter handled the event, false otherwise.
5605      */
sendToTextFilter(int keyCode, int count, KeyEvent event)5606     boolean sendToTextFilter(int keyCode, int count, KeyEvent event) {
5607         if (!acceptFilter()) {
5608             return false;
5609         }
5610 
5611         boolean handled = false;
5612         boolean okToSend = true;
5613         switch (keyCode) {
5614         case KeyEvent.KEYCODE_DPAD_UP:
5615         case KeyEvent.KEYCODE_DPAD_DOWN:
5616         case KeyEvent.KEYCODE_DPAD_LEFT:
5617         case KeyEvent.KEYCODE_DPAD_RIGHT:
5618         case KeyEvent.KEYCODE_DPAD_CENTER:
5619         case KeyEvent.KEYCODE_ENTER:
5620             okToSend = false;
5621             break;
5622         case KeyEvent.KEYCODE_BACK:
5623             if (mFiltered && mPopup != null && mPopup.isShowing()) {
5624                 if (event.getAction() == KeyEvent.ACTION_DOWN
5625                         && event.getRepeatCount() == 0) {
5626                     KeyEvent.DispatcherState state = getKeyDispatcherState();
5627                     if (state != null) {
5628                         state.startTracking(event, this);
5629                     }
5630                     handled = true;
5631                 } else if (event.getAction() == KeyEvent.ACTION_UP
5632                         && event.isTracking() && !event.isCanceled()) {
5633                     handled = true;
5634                     mTextFilter.setText("");
5635                 }
5636             }
5637             okToSend = false;
5638             break;
5639         case KeyEvent.KEYCODE_SPACE:
5640             // Only send spaces once we are filtered
5641             okToSend = mFiltered;
5642             break;
5643         }
5644 
5645         if (okToSend) {
5646             createTextFilter(true);
5647 
5648             KeyEvent forwardEvent = event;
5649             if (forwardEvent.getRepeatCount() > 0) {
5650                 forwardEvent = KeyEvent.changeTimeRepeat(event, event.getEventTime(), 0);
5651             }
5652 
5653             int action = event.getAction();
5654             switch (action) {
5655                 case KeyEvent.ACTION_DOWN:
5656                     handled = mTextFilter.onKeyDown(keyCode, forwardEvent);
5657                     break;
5658 
5659                 case KeyEvent.ACTION_UP:
5660                     handled = mTextFilter.onKeyUp(keyCode, forwardEvent);
5661                     break;
5662 
5663                 case KeyEvent.ACTION_MULTIPLE:
5664                     handled = mTextFilter.onKeyMultiple(keyCode, count, event);
5665                     break;
5666             }
5667         }
5668         return handled;
5669     }
5670 
5671     /**
5672      * Return an InputConnection for editing of the filter text.
5673      */
5674     @Override
onCreateInputConnection(EditorInfo outAttrs)5675     public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
5676         if (isTextFilterEnabled()) {
5677             if (mPublicInputConnection == null) {
5678                 mDefInputConnection = new BaseInputConnection(this, false);
5679                 mPublicInputConnection = new InputConnectionWrapper(outAttrs);
5680             }
5681             outAttrs.inputType = EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_FILTER;
5682             outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE;
5683             return mPublicInputConnection;
5684         }
5685         return null;
5686     }
5687 
5688     private class InputConnectionWrapper implements InputConnection {
5689         private final EditorInfo mOutAttrs;
5690         private InputConnection mTarget;
5691 
InputConnectionWrapper(EditorInfo outAttrs)5692         public InputConnectionWrapper(EditorInfo outAttrs) {
5693             mOutAttrs = outAttrs;
5694         }
5695 
getTarget()5696         private InputConnection getTarget() {
5697             if (mTarget == null) {
5698                 mTarget = getTextFilterInput().onCreateInputConnection(mOutAttrs);
5699             }
5700             return mTarget;
5701         }
5702 
5703         @Override
reportFullscreenMode(boolean enabled)5704         public boolean reportFullscreenMode(boolean enabled) {
5705             // Use our own input connection, since it is
5706             // the "real" one the IME is talking with.
5707             return mDefInputConnection.reportFullscreenMode(enabled);
5708         }
5709 
5710         @Override
performEditorAction(int editorAction)5711         public boolean performEditorAction(int editorAction) {
5712             // The editor is off in its own window; we need to be
5713             // the one that does this.
5714             if (editorAction == EditorInfo.IME_ACTION_DONE) {
5715                 InputMethodManager imm = (InputMethodManager)
5716                         getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
5717                 if (imm != null) {
5718                     imm.hideSoftInputFromWindow(getWindowToken(), 0);
5719                 }
5720                 return true;
5721             }
5722             return false;
5723         }
5724 
5725         @Override
sendKeyEvent(KeyEvent event)5726         public boolean sendKeyEvent(KeyEvent event) {
5727             // Use our own input connection, since the filter
5728             // text view may not be shown in a window so has
5729             // no ViewAncestor to dispatch events with.
5730             return mDefInputConnection.sendKeyEvent(event);
5731         }
5732 
5733         @Override
getTextBeforeCursor(int n, int flags)5734         public CharSequence getTextBeforeCursor(int n, int flags) {
5735             if (mTarget == null) return "";
5736             return mTarget.getTextBeforeCursor(n, flags);
5737         }
5738 
5739         @Override
getTextAfterCursor(int n, int flags)5740         public CharSequence getTextAfterCursor(int n, int flags) {
5741             if (mTarget == null) return "";
5742             return mTarget.getTextAfterCursor(n, flags);
5743         }
5744 
5745         @Override
getSelectedText(int flags)5746         public CharSequence getSelectedText(int flags) {
5747             if (mTarget == null) return "";
5748             return mTarget.getSelectedText(flags);
5749         }
5750 
5751         @Override
getCursorCapsMode(int reqModes)5752         public int getCursorCapsMode(int reqModes) {
5753             if (mTarget == null) return InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
5754             return mTarget.getCursorCapsMode(reqModes);
5755         }
5756 
5757         @Override
getExtractedText(ExtractedTextRequest request, int flags)5758         public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
5759             return getTarget().getExtractedText(request, flags);
5760         }
5761 
5762         @Override
deleteSurroundingText(int beforeLength, int afterLength)5763         public boolean deleteSurroundingText(int beforeLength, int afterLength) {
5764             return getTarget().deleteSurroundingText(beforeLength, afterLength);
5765         }
5766 
5767         @Override
setComposingText(CharSequence text, int newCursorPosition)5768         public boolean setComposingText(CharSequence text, int newCursorPosition) {
5769             return getTarget().setComposingText(text, newCursorPosition);
5770         }
5771 
5772         @Override
setComposingRegion(int start, int end)5773         public boolean setComposingRegion(int start, int end) {
5774             return getTarget().setComposingRegion(start, end);
5775         }
5776 
5777         @Override
finishComposingText()5778         public boolean finishComposingText() {
5779             return mTarget == null || mTarget.finishComposingText();
5780         }
5781 
5782         @Override
commitText(CharSequence text, int newCursorPosition)5783         public boolean commitText(CharSequence text, int newCursorPosition) {
5784             return getTarget().commitText(text, newCursorPosition);
5785         }
5786 
5787         @Override
commitCompletion(CompletionInfo text)5788         public boolean commitCompletion(CompletionInfo text) {
5789             return getTarget().commitCompletion(text);
5790         }
5791 
5792         @Override
commitCorrection(CorrectionInfo correctionInfo)5793         public boolean commitCorrection(CorrectionInfo correctionInfo) {
5794             return getTarget().commitCorrection(correctionInfo);
5795         }
5796 
5797         @Override
setSelection(int start, int end)5798         public boolean setSelection(int start, int end) {
5799             return getTarget().setSelection(start, end);
5800         }
5801 
5802         @Override
performContextMenuAction(int id)5803         public boolean performContextMenuAction(int id) {
5804             return getTarget().performContextMenuAction(id);
5805         }
5806 
5807         @Override
beginBatchEdit()5808         public boolean beginBatchEdit() {
5809             return getTarget().beginBatchEdit();
5810         }
5811 
5812         @Override
endBatchEdit()5813         public boolean endBatchEdit() {
5814             return getTarget().endBatchEdit();
5815         }
5816 
5817         @Override
clearMetaKeyStates(int states)5818         public boolean clearMetaKeyStates(int states) {
5819             return getTarget().clearMetaKeyStates(states);
5820         }
5821 
5822         @Override
performPrivateCommand(String action, Bundle data)5823         public boolean performPrivateCommand(String action, Bundle data) {
5824             return getTarget().performPrivateCommand(action, data);
5825         }
5826 
5827         @Override
requestCursorUpdates(int cursorUpdateMode)5828         public boolean requestCursorUpdates(int cursorUpdateMode) {
5829             return getTarget().requestCursorUpdates(cursorUpdateMode);
5830         }
5831     }
5832 
5833     /**
5834      * For filtering we proxy an input connection to an internal text editor,
5835      * and this allows the proxying to happen.
5836      */
5837     @Override
checkInputConnectionProxy(View view)5838     public boolean checkInputConnectionProxy(View view) {
5839         return view == mTextFilter;
5840     }
5841 
5842     /**
5843      * Creates the window for the text filter and populates it with an EditText field;
5844      *
5845      * @param animateEntrance true if the window should appear with an animation
5846      */
createTextFilter(boolean animateEntrance)5847     private void createTextFilter(boolean animateEntrance) {
5848         if (mPopup == null) {
5849             PopupWindow p = new PopupWindow(getContext());
5850             p.setFocusable(false);
5851             p.setTouchable(false);
5852             p.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
5853             p.setContentView(getTextFilterInput());
5854             p.setWidth(LayoutParams.WRAP_CONTENT);
5855             p.setHeight(LayoutParams.WRAP_CONTENT);
5856             p.setBackgroundDrawable(null);
5857             mPopup = p;
5858             getViewTreeObserver().addOnGlobalLayoutListener(this);
5859             mGlobalLayoutListenerAddedFilter = true;
5860         }
5861         if (animateEntrance) {
5862             mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilter);
5863         } else {
5864             mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilterRestore);
5865         }
5866     }
5867 
getTextFilterInput()5868     private EditText getTextFilterInput() {
5869         if (mTextFilter == null) {
5870             final LayoutInflater layoutInflater = LayoutInflater.from(getContext());
5871             mTextFilter = (EditText) layoutInflater.inflate(
5872                     com.android.internal.R.layout.typing_filter, null);
5873             // For some reason setting this as the "real" input type changes
5874             // the text view in some way that it doesn't work, and I don't
5875             // want to figure out why this is.
5876             mTextFilter.setRawInputType(EditorInfo.TYPE_CLASS_TEXT
5877                     | EditorInfo.TYPE_TEXT_VARIATION_FILTER);
5878             mTextFilter.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI);
5879             mTextFilter.addTextChangedListener(this);
5880         }
5881         return mTextFilter;
5882     }
5883 
5884     /**
5885      * Clear the text filter.
5886      */
clearTextFilter()5887     public void clearTextFilter() {
5888         if (mFiltered) {
5889             getTextFilterInput().setText("");
5890             mFiltered = false;
5891             if (mPopup != null && mPopup.isShowing()) {
5892                 dismissPopup();
5893             }
5894         }
5895     }
5896 
5897     /**
5898      * Returns if the ListView currently has a text filter.
5899      */
hasTextFilter()5900     public boolean hasTextFilter() {
5901         return mFiltered;
5902     }
5903 
5904     @Override
onGlobalLayout()5905     public void onGlobalLayout() {
5906         if (isShown()) {
5907             // Show the popup if we are filtered
5908             if (mFiltered && mPopup != null && !mPopup.isShowing() && !mPopupHidden) {
5909                 showPopup();
5910             }
5911         } else {
5912             // Hide the popup when we are no longer visible
5913             if (mPopup != null && mPopup.isShowing()) {
5914                 dismissPopup();
5915             }
5916         }
5917 
5918     }
5919 
5920     /**
5921      * For our text watcher that is associated with the text filter.  Does
5922      * nothing.
5923      */
5924     @Override
beforeTextChanged(CharSequence s, int start, int count, int after)5925     public void beforeTextChanged(CharSequence s, int start, int count, int after) {
5926     }
5927 
5928     /**
5929      * For our text watcher that is associated with the text filter. Performs
5930      * the actual filtering as the text changes, and takes care of hiding and
5931      * showing the popup displaying the currently entered filter text.
5932      */
5933     @Override
onTextChanged(CharSequence s, int start, int before, int count)5934     public void onTextChanged(CharSequence s, int start, int before, int count) {
5935         if (isTextFilterEnabled()) {
5936             createTextFilter(true);
5937             int length = s.length();
5938             boolean showing = mPopup.isShowing();
5939             if (!showing && length > 0) {
5940                 // Show the filter popup if necessary
5941                 showPopup();
5942                 mFiltered = true;
5943             } else if (showing && length == 0) {
5944                 // Remove the filter popup if the user has cleared all text
5945                 dismissPopup();
5946                 mFiltered = false;
5947             }
5948             if (mAdapter instanceof Filterable) {
5949                 Filter f = ((Filterable) mAdapter).getFilter();
5950                 // Filter should not be null when we reach this part
5951                 if (f != null) {
5952                     f.filter(s, this);
5953                 } else {
5954                     throw new IllegalStateException("You cannot call onTextChanged with a non "
5955                             + "filterable adapter");
5956                 }
5957             }
5958         }
5959     }
5960 
5961     /**
5962      * For our text watcher that is associated with the text filter.  Does
5963      * nothing.
5964      */
5965     @Override
afterTextChanged(Editable s)5966     public void afterTextChanged(Editable s) {
5967     }
5968 
5969     @Override
onFilterComplete(int count)5970     public void onFilterComplete(int count) {
5971         if (mSelectedPosition < 0 && count > 0) {
5972             mResurrectToPosition = INVALID_POSITION;
5973             resurrectSelection();
5974         }
5975     }
5976 
5977     @Override
generateDefaultLayoutParams()5978     protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
5979         return new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
5980                 ViewGroup.LayoutParams.WRAP_CONTENT, 0);
5981     }
5982 
5983     @Override
generateLayoutParams(ViewGroup.LayoutParams p)5984     protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
5985         return new LayoutParams(p);
5986     }
5987 
5988     @Override
generateLayoutParams(AttributeSet attrs)5989     public LayoutParams generateLayoutParams(AttributeSet attrs) {
5990         return new AbsListView.LayoutParams(getContext(), attrs);
5991     }
5992 
5993     @Override
checkLayoutParams(ViewGroup.LayoutParams p)5994     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
5995         return p instanceof AbsListView.LayoutParams;
5996     }
5997 
5998     /**
5999      * Puts the list or grid into transcript mode. In this mode the list or grid will always scroll
6000      * to the bottom to show new items.
6001      *
6002      * @param mode the transcript mode to set
6003      *
6004      * @see #TRANSCRIPT_MODE_DISABLED
6005      * @see #TRANSCRIPT_MODE_NORMAL
6006      * @see #TRANSCRIPT_MODE_ALWAYS_SCROLL
6007      */
setTranscriptMode(int mode)6008     public void setTranscriptMode(int mode) {
6009         mTranscriptMode = mode;
6010     }
6011 
6012     /**
6013      * Returns the current transcript mode.
6014      *
6015      * @return {@link #TRANSCRIPT_MODE_DISABLED}, {@link #TRANSCRIPT_MODE_NORMAL} or
6016      *         {@link #TRANSCRIPT_MODE_ALWAYS_SCROLL}
6017      */
getTranscriptMode()6018     public int getTranscriptMode() {
6019         return mTranscriptMode;
6020     }
6021 
6022     @Override
getSolidColor()6023     public int getSolidColor() {
6024         return mCacheColorHint;
6025     }
6026 
6027     /**
6028      * When set to a non-zero value, the cache color hint indicates that this list is always drawn
6029      * on top of a solid, single-color, opaque background.
6030      *
6031      * Zero means that what's behind this object is translucent (non solid) or is not made of a
6032      * single color. This hint will not affect any existing background drawable set on this view (
6033      * typically set via {@link #setBackgroundDrawable(Drawable)}).
6034      *
6035      * @param color The background color
6036      */
setCacheColorHint(@olorInt int color)6037     public void setCacheColorHint(@ColorInt int color) {
6038         if (color != mCacheColorHint) {
6039             mCacheColorHint = color;
6040             int count = getChildCount();
6041             for (int i = 0; i < count; i++) {
6042                 getChildAt(i).setDrawingCacheBackgroundColor(color);
6043             }
6044             mRecycler.setCacheColorHint(color);
6045         }
6046     }
6047 
6048     /**
6049      * When set to a non-zero value, the cache color hint indicates that this list is always drawn
6050      * on top of a solid, single-color, opaque background
6051      *
6052      * @return The cache color hint
6053      */
6054     @ViewDebug.ExportedProperty(category = "drawing")
6055     @ColorInt
getCacheColorHint()6056     public int getCacheColorHint() {
6057         return mCacheColorHint;
6058     }
6059 
6060     /**
6061      * Move all views (excluding headers and footers) held by this AbsListView into the supplied
6062      * List. This includes views displayed on the screen as well as views stored in AbsListView's
6063      * internal view recycler.
6064      *
6065      * @param views A list into which to put the reclaimed views
6066      */
reclaimViews(List<View> views)6067     public void reclaimViews(List<View> views) {
6068         int childCount = getChildCount();
6069         RecyclerListener listener = mRecycler.mRecyclerListener;
6070 
6071         // Reclaim views on screen
6072         for (int i = 0; i < childCount; i++) {
6073             View child = getChildAt(i);
6074             AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
6075             // Don't reclaim header or footer views, or views that should be ignored
6076             if (lp != null && mRecycler.shouldRecycleViewType(lp.viewType)) {
6077                 views.add(child);
6078                 child.setAccessibilityDelegate(null);
6079                 if (listener != null) {
6080                     // Pretend they went through the scrap heap
6081                     listener.onMovedToScrapHeap(child);
6082                 }
6083             }
6084         }
6085         mRecycler.reclaimScrapViews(views);
6086         removeAllViewsInLayout();
6087     }
6088 
finishGlows()6089     private void finishGlows() {
6090         if (mEdgeGlowTop != null) {
6091             mEdgeGlowTop.finish();
6092             mEdgeGlowBottom.finish();
6093         }
6094     }
6095 
6096     /**
6097      * Sets up this AbsListView to use a remote views adapter which connects to a RemoteViewsService
6098      * through the specified intent.
6099      * @param intent the intent used to identify the RemoteViewsService for the adapter to connect to.
6100      */
setRemoteViewsAdapter(Intent intent)6101     public void setRemoteViewsAdapter(Intent intent) {
6102         // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
6103         // service handling the specified intent.
6104         if (mRemoteAdapter != null) {
6105             Intent.FilterComparison fcNew = new Intent.FilterComparison(intent);
6106             Intent.FilterComparison fcOld = new Intent.FilterComparison(
6107                     mRemoteAdapter.getRemoteViewsServiceIntent());
6108             if (fcNew.equals(fcOld)) {
6109                 return;
6110             }
6111         }
6112         mDeferNotifyDataSetChanged = false;
6113         // Otherwise, create a new RemoteViewsAdapter for binding
6114         mRemoteAdapter = new RemoteViewsAdapter(getContext(), intent, this);
6115         if (mRemoteAdapter.isDataReady()) {
6116             setAdapter(mRemoteAdapter);
6117         }
6118     }
6119 
6120     /**
6121      * Sets up the onClickHandler to be used by the RemoteViewsAdapter when inflating RemoteViews
6122      *
6123      * @param handler The OnClickHandler to use when inflating RemoteViews.
6124      *
6125      * @hide
6126      */
setRemoteViewsOnClickHandler(OnClickHandler handler)6127     public void setRemoteViewsOnClickHandler(OnClickHandler handler) {
6128         // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
6129         // service handling the specified intent.
6130         if (mRemoteAdapter != null) {
6131             mRemoteAdapter.setRemoteViewsOnClickHandler(handler);
6132         }
6133     }
6134 
6135     /**
6136      * This defers a notifyDataSetChanged on the pending RemoteViewsAdapter if it has not
6137      * connected yet.
6138      */
6139     @Override
deferNotifyDataSetChanged()6140     public void deferNotifyDataSetChanged() {
6141         mDeferNotifyDataSetChanged = true;
6142     }
6143 
6144     /**
6145      * Called back when the adapter connects to the RemoteViewsService.
6146      */
6147     @Override
onRemoteAdapterConnected()6148     public boolean onRemoteAdapterConnected() {
6149         if (mRemoteAdapter != mAdapter) {
6150             setAdapter(mRemoteAdapter);
6151             if (mDeferNotifyDataSetChanged) {
6152                 mRemoteAdapter.notifyDataSetChanged();
6153                 mDeferNotifyDataSetChanged = false;
6154             }
6155             return false;
6156         } else if (mRemoteAdapter != null) {
6157             mRemoteAdapter.superNotifyDataSetChanged();
6158             return true;
6159         }
6160         return false;
6161     }
6162 
6163     /**
6164      * Called back when the adapter disconnects from the RemoteViewsService.
6165      */
6166     @Override
onRemoteAdapterDisconnected()6167     public void onRemoteAdapterDisconnected() {
6168         // If the remote adapter disconnects, we keep it around
6169         // since the currently displayed items are still cached.
6170         // Further, we want the service to eventually reconnect
6171         // when necessary, as triggered by this view requesting
6172         // items from the Adapter.
6173     }
6174 
6175     /**
6176      * Hints the RemoteViewsAdapter, if it exists, about which views are currently
6177      * being displayed by the AbsListView.
6178      */
setVisibleRangeHint(int start, int end)6179     void setVisibleRangeHint(int start, int end) {
6180         if (mRemoteAdapter != null) {
6181             mRemoteAdapter.setVisibleRangeHint(start, end);
6182         }
6183     }
6184 
6185     /**
6186      * Sets the recycler listener to be notified whenever a View is set aside in
6187      * the recycler for later reuse. This listener can be used to free resources
6188      * associated to the View.
6189      *
6190      * @param listener The recycler listener to be notified of views set aside
6191      *        in the recycler.
6192      *
6193      * @see android.widget.AbsListView.RecycleBin
6194      * @see android.widget.AbsListView.RecyclerListener
6195      */
setRecyclerListener(RecyclerListener listener)6196     public void setRecyclerListener(RecyclerListener listener) {
6197         mRecycler.mRecyclerListener = listener;
6198     }
6199 
6200     class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver {
6201         @Override
onChanged()6202         public void onChanged() {
6203             super.onChanged();
6204             if (mFastScroll != null) {
6205                 mFastScroll.onSectionsChanged();
6206             }
6207         }
6208 
6209         @Override
onInvalidated()6210         public void onInvalidated() {
6211             super.onInvalidated();
6212             if (mFastScroll != null) {
6213                 mFastScroll.onSectionsChanged();
6214             }
6215         }
6216     }
6217 
6218     /**
6219      * A MultiChoiceModeListener receives events for {@link AbsListView#CHOICE_MODE_MULTIPLE_MODAL}.
6220      * It acts as the {@link ActionMode.Callback} for the selection mode and also receives
6221      * {@link #onItemCheckedStateChanged(ActionMode, int, long, boolean)} events when the user
6222      * selects and deselects list items.
6223      */
6224     public interface MultiChoiceModeListener extends ActionMode.Callback {
6225         /**
6226          * Called when an item is checked or unchecked during selection mode.
6227          *
6228          * @param mode The {@link ActionMode} providing the selection mode
6229          * @param position Adapter position of the item that was checked or unchecked
6230          * @param id Adapter ID of the item that was checked or unchecked
6231          * @param checked <code>true</code> if the item is now checked, <code>false</code>
6232          *                if the item is now unchecked.
6233          */
6234         public void onItemCheckedStateChanged(ActionMode mode,
6235                 int position, long id, boolean checked);
6236     }
6237 
6238     class MultiChoiceModeWrapper implements MultiChoiceModeListener {
6239         private MultiChoiceModeListener mWrapped;
6240 
setWrapped(MultiChoiceModeListener wrapped)6241         public void setWrapped(MultiChoiceModeListener wrapped) {
6242             mWrapped = wrapped;
6243         }
6244 
hasWrappedCallback()6245         public boolean hasWrappedCallback() {
6246             return mWrapped != null;
6247         }
6248 
6249         @Override
onCreateActionMode(ActionMode mode, Menu menu)6250         public boolean onCreateActionMode(ActionMode mode, Menu menu) {
6251             if (mWrapped.onCreateActionMode(mode, menu)) {
6252                 // Initialize checked graphic state?
6253                 setLongClickable(false);
6254                 return true;
6255             }
6256             return false;
6257         }
6258 
6259         @Override
onPrepareActionMode(ActionMode mode, Menu menu)6260         public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
6261             return mWrapped.onPrepareActionMode(mode, menu);
6262         }
6263 
6264         @Override
onActionItemClicked(ActionMode mode, MenuItem item)6265         public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
6266             return mWrapped.onActionItemClicked(mode, item);
6267         }
6268 
6269         @Override
onDestroyActionMode(ActionMode mode)6270         public void onDestroyActionMode(ActionMode mode) {
6271             mWrapped.onDestroyActionMode(mode);
6272             mChoiceActionMode = null;
6273 
6274             // Ending selection mode means deselecting everything.
6275             clearChoices();
6276 
6277             mDataChanged = true;
6278             rememberSyncState();
6279             requestLayout();
6280 
6281             setLongClickable(true);
6282         }
6283 
6284         @Override
onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked)6285         public void onItemCheckedStateChanged(ActionMode mode,
6286                 int position, long id, boolean checked) {
6287             mWrapped.onItemCheckedStateChanged(mode, position, id, checked);
6288 
6289             // If there are no items selected we no longer need the selection mode.
6290             if (getCheckedItemCount() == 0) {
6291                 mode.finish();
6292             }
6293         }
6294     }
6295 
6296     /**
6297      * AbsListView extends LayoutParams to provide a place to hold the view type.
6298      */
6299     public static class LayoutParams extends ViewGroup.LayoutParams {
6300         /**
6301          * View type for this view, as returned by
6302          * {@link android.widget.Adapter#getItemViewType(int) }
6303          */
6304         @ViewDebug.ExportedProperty(category = "list", mapping = {
6305             @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_IGNORE, to = "ITEM_VIEW_TYPE_IGNORE"),
6306             @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_HEADER_OR_FOOTER, to = "ITEM_VIEW_TYPE_HEADER_OR_FOOTER")
6307         })
6308         int viewType;
6309 
6310         /**
6311          * When this boolean is set, the view has been added to the AbsListView
6312          * at least once. It is used to know whether headers/footers have already
6313          * been added to the list view and whether they should be treated as
6314          * recycled views or not.
6315          */
6316         @ViewDebug.ExportedProperty(category = "list")
6317         boolean recycledHeaderFooter;
6318 
6319         /**
6320          * When an AbsListView is measured with an AT_MOST measure spec, it needs
6321          * to obtain children views to measure itself. When doing so, the children
6322          * are not attached to the window, but put in the recycler which assumes
6323          * they've been attached before. Setting this flag will force the reused
6324          * view to be attached to the window rather than just attached to the
6325          * parent.
6326          */
6327         @ViewDebug.ExportedProperty(category = "list")
6328         boolean forceAdd;
6329 
6330         /**
6331          * The position the view was removed from when pulled out of the
6332          * scrap heap.
6333          * @hide
6334          */
6335         int scrappedFromPosition;
6336 
6337         /**
6338          * The ID the view represents
6339          */
6340         long itemId = -1;
6341 
6342         /** Whether the adapter considers the item enabled. */
6343         boolean isEnabled;
6344 
LayoutParams(Context c, AttributeSet attrs)6345         public LayoutParams(Context c, AttributeSet attrs) {
6346             super(c, attrs);
6347         }
6348 
LayoutParams(int w, int h)6349         public LayoutParams(int w, int h) {
6350             super(w, h);
6351         }
6352 
LayoutParams(int w, int h, int viewType)6353         public LayoutParams(int w, int h, int viewType) {
6354             super(w, h);
6355             this.viewType = viewType;
6356         }
6357 
LayoutParams(ViewGroup.LayoutParams source)6358         public LayoutParams(ViewGroup.LayoutParams source) {
6359             super(source);
6360         }
6361 
6362         /** @hide */
6363         @Override
encodeProperties(@onNull ViewHierarchyEncoder encoder)6364         protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
6365             super.encodeProperties(encoder);
6366 
6367             encoder.addProperty("list:viewType", viewType);
6368             encoder.addProperty("list:recycledHeaderFooter", recycledHeaderFooter);
6369             encoder.addProperty("list:forceAdd", forceAdd);
6370             encoder.addProperty("list:isEnabled", isEnabled);
6371         }
6372     }
6373 
6374     /**
6375      * A RecyclerListener is used to receive a notification whenever a View is placed
6376      * inside the RecycleBin's scrap heap. This listener is used to free resources
6377      * associated to Views placed in the RecycleBin.
6378      *
6379      * @see android.widget.AbsListView.RecycleBin
6380      * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
6381      */
6382     public static interface RecyclerListener {
6383         /**
6384          * Indicates that the specified View was moved into the recycler's scrap heap.
6385          * The view is not displayed on screen any more and any expensive resource
6386          * associated with the view should be discarded.
6387          *
6388          * @param view
6389          */
6390         void onMovedToScrapHeap(View view);
6391     }
6392 
6393     /**
6394      * The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of
6395      * storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the
6396      * start of a layout. By construction, they are displaying current information. At the end of
6397      * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that
6398      * could potentially be used by the adapter to avoid allocating views unnecessarily.
6399      *
6400      * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
6401      * @see android.widget.AbsListView.RecyclerListener
6402      */
6403     class RecycleBin {
6404         private RecyclerListener mRecyclerListener;
6405 
6406         /**
6407          * The position of the first view stored in mActiveViews.
6408          */
6409         private int mFirstActivePosition;
6410 
6411         /**
6412          * Views that were on screen at the start of layout. This array is populated at the start of
6413          * layout, and at the end of layout all view in mActiveViews are moved to mScrapViews.
6414          * Views in mActiveViews represent a contiguous range of Views, with position of the first
6415          * view store in mFirstActivePosition.
6416          */
6417         private View[] mActiveViews = new View[0];
6418 
6419         /**
6420          * Unsorted views that can be used by the adapter as a convert view.
6421          */
6422         private ArrayList<View>[] mScrapViews;
6423 
6424         private int mViewTypeCount;
6425 
6426         private ArrayList<View> mCurrentScrap;
6427 
6428         private ArrayList<View> mSkippedScrap;
6429 
6430         private SparseArray<View> mTransientStateViews;
6431         private LongSparseArray<View> mTransientStateViewsById;
6432 
setViewTypeCount(int viewTypeCount)6433         public void setViewTypeCount(int viewTypeCount) {
6434             if (viewTypeCount < 1) {
6435                 throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
6436             }
6437             //noinspection unchecked
6438             ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
6439             for (int i = 0; i < viewTypeCount; i++) {
6440                 scrapViews[i] = new ArrayList<View>();
6441             }
6442             mViewTypeCount = viewTypeCount;
6443             mCurrentScrap = scrapViews[0];
6444             mScrapViews = scrapViews;
6445         }
6446 
markChildrenDirty()6447         public void markChildrenDirty() {
6448             if (mViewTypeCount == 1) {
6449                 final ArrayList<View> scrap = mCurrentScrap;
6450                 final int scrapCount = scrap.size();
6451                 for (int i = 0; i < scrapCount; i++) {
6452                     scrap.get(i).forceLayout();
6453                 }
6454             } else {
6455                 final int typeCount = mViewTypeCount;
6456                 for (int i = 0; i < typeCount; i++) {
6457                     final ArrayList<View> scrap = mScrapViews[i];
6458                     final int scrapCount = scrap.size();
6459                     for (int j = 0; j < scrapCount; j++) {
6460                         scrap.get(j).forceLayout();
6461                     }
6462                 }
6463             }
6464             if (mTransientStateViews != null) {
6465                 final int count = mTransientStateViews.size();
6466                 for (int i = 0; i < count; i++) {
6467                     mTransientStateViews.valueAt(i).forceLayout();
6468                 }
6469             }
6470             if (mTransientStateViewsById != null) {
6471                 final int count = mTransientStateViewsById.size();
6472                 for (int i = 0; i < count; i++) {
6473                     mTransientStateViewsById.valueAt(i).forceLayout();
6474                 }
6475             }
6476         }
6477 
shouldRecycleViewType(int viewType)6478         public boolean shouldRecycleViewType(int viewType) {
6479             return viewType >= 0;
6480         }
6481 
6482         /**
6483          * Clears the scrap heap.
6484          */
clear()6485         void clear() {
6486             if (mViewTypeCount == 1) {
6487                 final ArrayList<View> scrap = mCurrentScrap;
6488                 clearScrap(scrap);
6489             } else {
6490                 final int typeCount = mViewTypeCount;
6491                 for (int i = 0; i < typeCount; i++) {
6492                     final ArrayList<View> scrap = mScrapViews[i];
6493                     clearScrap(scrap);
6494                 }
6495             }
6496 
6497             clearTransientStateViews();
6498         }
6499 
6500         /**
6501          * Fill ActiveViews with all of the children of the AbsListView.
6502          *
6503          * @param childCount The minimum number of views mActiveViews should hold
6504          * @param firstActivePosition The position of the first view that will be stored in
6505          *        mActiveViews
6506          */
fillActiveViews(int childCount, int firstActivePosition)6507         void fillActiveViews(int childCount, int firstActivePosition) {
6508             if (mActiveViews.length < childCount) {
6509                 mActiveViews = new View[childCount];
6510             }
6511             mFirstActivePosition = firstActivePosition;
6512 
6513             //noinspection MismatchedReadAndWriteOfArray
6514             final View[] activeViews = mActiveViews;
6515             for (int i = 0; i < childCount; i++) {
6516                 View child = getChildAt(i);
6517                 AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
6518                 // Don't put header or footer views into the scrap heap
6519                 if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
6520                     // Note:  We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views.
6521                     //        However, we will NOT place them into scrap views.
6522                     activeViews[i] = child;
6523                     // Remember the position so that setupChild() doesn't reset state.
6524                     lp.scrappedFromPosition = firstActivePosition + i;
6525                 }
6526             }
6527         }
6528 
6529         /**
6530          * Get the view corresponding to the specified position. The view will be removed from
6531          * mActiveViews if it is found.
6532          *
6533          * @param position The position to look up in mActiveViews
6534          * @return The view if it is found, null otherwise
6535          */
getActiveView(int position)6536         View getActiveView(int position) {
6537             int index = position - mFirstActivePosition;
6538             final View[] activeViews = mActiveViews;
6539             if (index >=0 && index < activeViews.length) {
6540                 final View match = activeViews[index];
6541                 activeViews[index] = null;
6542                 return match;
6543             }
6544             return null;
6545         }
6546 
getTransientStateView(int position)6547         View getTransientStateView(int position) {
6548             if (mAdapter != null && mAdapterHasStableIds && mTransientStateViewsById != null) {
6549                 long id = mAdapter.getItemId(position);
6550                 View result = mTransientStateViewsById.get(id);
6551                 mTransientStateViewsById.remove(id);
6552                 return result;
6553             }
6554             if (mTransientStateViews != null) {
6555                 final int index = mTransientStateViews.indexOfKey(position);
6556                 if (index >= 0) {
6557                     View result = mTransientStateViews.valueAt(index);
6558                     mTransientStateViews.removeAt(index);
6559                     return result;
6560                 }
6561             }
6562             return null;
6563         }
6564 
6565         /**
6566          * Dumps and fully detaches any currently saved views with transient
6567          * state.
6568          */
clearTransientStateViews()6569         void clearTransientStateViews() {
6570             final SparseArray<View> viewsByPos = mTransientStateViews;
6571             if (viewsByPos != null) {
6572                 final int N = viewsByPos.size();
6573                 for (int i = 0; i < N; i++) {
6574                     removeDetachedView(viewsByPos.valueAt(i), false);
6575                 }
6576                 viewsByPos.clear();
6577             }
6578 
6579             final LongSparseArray<View> viewsById = mTransientStateViewsById;
6580             if (viewsById != null) {
6581                 final int N = viewsById.size();
6582                 for (int i = 0; i < N; i++) {
6583                     removeDetachedView(viewsById.valueAt(i), false);
6584                 }
6585                 viewsById.clear();
6586             }
6587         }
6588 
6589         /**
6590          * @return A view from the ScrapViews collection. These are unordered.
6591          */
getScrapView(int position)6592         View getScrapView(int position) {
6593             final int whichScrap = mAdapter.getItemViewType(position);
6594             if (whichScrap < 0) {
6595                 return null;
6596             }
6597             if (mViewTypeCount == 1) {
6598                 return retrieveFromScrap(mCurrentScrap, position);
6599             } else if (whichScrap < mScrapViews.length) {
6600                 return retrieveFromScrap(mScrapViews[whichScrap], position);
6601             }
6602             return null;
6603         }
6604 
6605         /**
6606          * Puts a view into the list of scrap views.
6607          * <p>
6608          * If the list data hasn't changed or the adapter has stable IDs, views
6609          * with transient state will be preserved for later retrieval.
6610          *
6611          * @param scrap The view to add
6612          * @param position The view's position within its parent
6613          */
addScrapView(View scrap, int position)6614         void addScrapView(View scrap, int position) {
6615             final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
6616             if (lp == null) {
6617                 // Can't recycle, but we don't know anything about the view.
6618                 // Ignore it completely.
6619                 return;
6620             }
6621 
6622             lp.scrappedFromPosition = position;
6623 
6624             // Remove but don't scrap header or footer views, or views that
6625             // should otherwise not be recycled.
6626             final int viewType = lp.viewType;
6627             if (!shouldRecycleViewType(viewType)) {
6628                 // Can't recycle. If it's not a header or footer, which have
6629                 // special handling and should be ignored, then skip the scrap
6630                 // heap and we'll fully detach the view later.
6631                 if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
6632                     getSkippedScrap().add(scrap);
6633                 }
6634                 return;
6635             }
6636 
6637             scrap.dispatchStartTemporaryDetach();
6638 
6639             // The the accessibility state of the view may change while temporary
6640             // detached and we do not allow detached views to fire accessibility
6641             // events. So we are announcing that the subtree changed giving a chance
6642             // to clients holding on to a view in this subtree to refresh it.
6643             notifyViewAccessibilityStateChangedIfNeeded(
6644                     AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
6645 
6646             // Don't scrap views that have transient state.
6647             final boolean scrapHasTransientState = scrap.hasTransientState();
6648             if (scrapHasTransientState) {
6649                 if (mAdapter != null && mAdapterHasStableIds) {
6650                     // If the adapter has stable IDs, we can reuse the view for
6651                     // the same data.
6652                     if (mTransientStateViewsById == null) {
6653                         mTransientStateViewsById = new LongSparseArray<>();
6654                     }
6655                     mTransientStateViewsById.put(lp.itemId, scrap);
6656                 } else if (!mDataChanged) {
6657                     // If the data hasn't changed, we can reuse the views at
6658                     // their old positions.
6659                     if (mTransientStateViews == null) {
6660                         mTransientStateViews = new SparseArray<>();
6661                     }
6662                     mTransientStateViews.put(position, scrap);
6663                 } else {
6664                     // Otherwise, we'll have to remove the view and start over.
6665                     getSkippedScrap().add(scrap);
6666                 }
6667             } else {
6668                 if (mViewTypeCount == 1) {
6669                     mCurrentScrap.add(scrap);
6670                 } else {
6671                     mScrapViews[viewType].add(scrap);
6672                 }
6673 
6674                 if (mRecyclerListener != null) {
6675                     mRecyclerListener.onMovedToScrapHeap(scrap);
6676                 }
6677             }
6678         }
6679 
getSkippedScrap()6680         private ArrayList<View> getSkippedScrap() {
6681             if (mSkippedScrap == null) {
6682                 mSkippedScrap = new ArrayList<>();
6683             }
6684             return mSkippedScrap;
6685         }
6686 
6687         /**
6688          * Finish the removal of any views that skipped the scrap heap.
6689          */
removeSkippedScrap()6690         void removeSkippedScrap() {
6691             if (mSkippedScrap == null) {
6692                 return;
6693             }
6694             final int count = mSkippedScrap.size();
6695             for (int i = 0; i < count; i++) {
6696                 removeDetachedView(mSkippedScrap.get(i), false);
6697             }
6698             mSkippedScrap.clear();
6699         }
6700 
6701         /**
6702          * Move all views remaining in mActiveViews to mScrapViews.
6703          */
scrapActiveViews()6704         void scrapActiveViews() {
6705             final View[] activeViews = mActiveViews;
6706             final boolean hasListener = mRecyclerListener != null;
6707             final boolean multipleScraps = mViewTypeCount > 1;
6708 
6709             ArrayList<View> scrapViews = mCurrentScrap;
6710             final int count = activeViews.length;
6711             for (int i = count - 1; i >= 0; i--) {
6712                 final View victim = activeViews[i];
6713                 if (victim != null) {
6714                     final AbsListView.LayoutParams lp
6715                             = (AbsListView.LayoutParams) victim.getLayoutParams();
6716                     final int whichScrap = lp.viewType;
6717 
6718                     activeViews[i] = null;
6719 
6720                     if (victim.hasTransientState()) {
6721                         // Store views with transient state for later use.
6722                         victim.dispatchStartTemporaryDetach();
6723 
6724                         if (mAdapter != null && mAdapterHasStableIds) {
6725                             if (mTransientStateViewsById == null) {
6726                                 mTransientStateViewsById = new LongSparseArray<View>();
6727                             }
6728                             long id = mAdapter.getItemId(mFirstActivePosition + i);
6729                             mTransientStateViewsById.put(id, victim);
6730                         } else if (!mDataChanged) {
6731                             if (mTransientStateViews == null) {
6732                                 mTransientStateViews = new SparseArray<View>();
6733                             }
6734                             mTransientStateViews.put(mFirstActivePosition + i, victim);
6735                         } else if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
6736                             // The data has changed, we can't keep this view.
6737                             removeDetachedView(victim, false);
6738                         }
6739                     } else if (!shouldRecycleViewType(whichScrap)) {
6740                         // Discard non-recyclable views except headers/footers.
6741                         if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
6742                             removeDetachedView(victim, false);
6743                         }
6744                     } else {
6745                         // Store everything else on the appropriate scrap heap.
6746                         if (multipleScraps) {
6747                             scrapViews = mScrapViews[whichScrap];
6748                         }
6749 
6750                         victim.dispatchStartTemporaryDetach();
6751                         lp.scrappedFromPosition = mFirstActivePosition + i;
6752                         scrapViews.add(victim);
6753 
6754                         if (hasListener) {
6755                             mRecyclerListener.onMovedToScrapHeap(victim);
6756                         }
6757                     }
6758                 }
6759             }
6760 
6761             pruneScrapViews();
6762         }
6763 
6764         /**
6765          * Makes sure that the size of mScrapViews does not exceed the size of
6766          * mActiveViews, which can happen if an adapter does not recycle its
6767          * views. Removes cached transient state views that no longer have
6768          * transient state.
6769          */
pruneScrapViews()6770         private void pruneScrapViews() {
6771             final int maxViews = mActiveViews.length;
6772             final int viewTypeCount = mViewTypeCount;
6773             final ArrayList<View>[] scrapViews = mScrapViews;
6774             for (int i = 0; i < viewTypeCount; ++i) {
6775                 final ArrayList<View> scrapPile = scrapViews[i];
6776                 int size = scrapPile.size();
6777                 final int extras = size - maxViews;
6778                 size--;
6779                 for (int j = 0; j < extras; j++) {
6780                     removeDetachedView(scrapPile.remove(size--), false);
6781                 }
6782             }
6783 
6784             final SparseArray<View> transViewsByPos = mTransientStateViews;
6785             if (transViewsByPos != null) {
6786                 for (int i = 0; i < transViewsByPos.size(); i++) {
6787                     final View v = transViewsByPos.valueAt(i);
6788                     if (!v.hasTransientState()) {
6789                         removeDetachedView(v, false);
6790                         transViewsByPos.removeAt(i);
6791                         i--;
6792                     }
6793                 }
6794             }
6795 
6796             final LongSparseArray<View> transViewsById = mTransientStateViewsById;
6797             if (transViewsById != null) {
6798                 for (int i = 0; i < transViewsById.size(); i++) {
6799                     final View v = transViewsById.valueAt(i);
6800                     if (!v.hasTransientState()) {
6801                         removeDetachedView(v, false);
6802                         transViewsById.removeAt(i);
6803                         i--;
6804                     }
6805                 }
6806             }
6807         }
6808 
6809         /**
6810          * Puts all views in the scrap heap into the supplied list.
6811          */
reclaimScrapViews(List<View> views)6812         void reclaimScrapViews(List<View> views) {
6813             if (mViewTypeCount == 1) {
6814                 views.addAll(mCurrentScrap);
6815             } else {
6816                 final int viewTypeCount = mViewTypeCount;
6817                 final ArrayList<View>[] scrapViews = mScrapViews;
6818                 for (int i = 0; i < viewTypeCount; ++i) {
6819                     final ArrayList<View> scrapPile = scrapViews[i];
6820                     views.addAll(scrapPile);
6821                 }
6822             }
6823         }
6824 
6825         /**
6826          * Updates the cache color hint of all known views.
6827          *
6828          * @param color The new cache color hint.
6829          */
setCacheColorHint(int color)6830         void setCacheColorHint(int color) {
6831             if (mViewTypeCount == 1) {
6832                 final ArrayList<View> scrap = mCurrentScrap;
6833                 final int scrapCount = scrap.size();
6834                 for (int i = 0; i < scrapCount; i++) {
6835                     scrap.get(i).setDrawingCacheBackgroundColor(color);
6836                 }
6837             } else {
6838                 final int typeCount = mViewTypeCount;
6839                 for (int i = 0; i < typeCount; i++) {
6840                     final ArrayList<View> scrap = mScrapViews[i];
6841                     final int scrapCount = scrap.size();
6842                     for (int j = 0; j < scrapCount; j++) {
6843                         scrap.get(j).setDrawingCacheBackgroundColor(color);
6844                     }
6845                 }
6846             }
6847             // Just in case this is called during a layout pass
6848             final View[] activeViews = mActiveViews;
6849             final int count = activeViews.length;
6850             for (int i = 0; i < count; ++i) {
6851                 final View victim = activeViews[i];
6852                 if (victim != null) {
6853                     victim.setDrawingCacheBackgroundColor(color);
6854                 }
6855             }
6856         }
6857 
retrieveFromScrap(ArrayList<View> scrapViews, int position)6858         private View retrieveFromScrap(ArrayList<View> scrapViews, int position) {
6859             final int size = scrapViews.size();
6860             if (size > 0) {
6861                 // See if we still have a view for this position or ID.
6862                 for (int i = 0; i < size; i++) {
6863                     final View view = scrapViews.get(i);
6864                     final AbsListView.LayoutParams params =
6865                             (AbsListView.LayoutParams) view.getLayoutParams();
6866 
6867                     if (mAdapterHasStableIds) {
6868                         final long id = mAdapter.getItemId(position);
6869                         if (id == params.itemId) {
6870                             return scrapViews.remove(i);
6871                         }
6872                     } else if (params.scrappedFromPosition == position) {
6873                         final View scrap = scrapViews.remove(i);
6874                         clearAccessibilityFromScrap(scrap);
6875                         return scrap;
6876                     }
6877                 }
6878                 final View scrap = scrapViews.remove(size - 1);
6879                 clearAccessibilityFromScrap(scrap);
6880                 return scrap;
6881             } else {
6882                 return null;
6883             }
6884         }
6885 
clearScrap(final ArrayList<View> scrap)6886         private void clearScrap(final ArrayList<View> scrap) {
6887             final int scrapCount = scrap.size();
6888             for (int j = 0; j < scrapCount; j++) {
6889                 removeDetachedView(scrap.remove(scrapCount - 1 - j), false);
6890             }
6891         }
6892 
clearAccessibilityFromScrap(View view)6893         private void clearAccessibilityFromScrap(View view) {
6894             view.clearAccessibilityFocus();
6895             view.setAccessibilityDelegate(null);
6896         }
6897 
removeDetachedView(View child, boolean animate)6898         private void removeDetachedView(View child, boolean animate) {
6899             child.setAccessibilityDelegate(null);
6900             AbsListView.this.removeDetachedView(child, animate);
6901         }
6902     }
6903 
6904     /**
6905      * Returns the height of the view for the specified position.
6906      *
6907      * @param position the item position
6908      * @return view height in pixels
6909      */
getHeightForPosition(int position)6910     int getHeightForPosition(int position) {
6911         final int firstVisiblePosition = getFirstVisiblePosition();
6912         final int childCount = getChildCount();
6913         final int index = position - firstVisiblePosition;
6914         if (index >= 0 && index < childCount) {
6915             // Position is on-screen, use existing view.
6916             final View view = getChildAt(index);
6917             return view.getHeight();
6918         } else {
6919             // Position is off-screen, obtain & recycle view.
6920             final View view = obtainView(position, mIsScrap);
6921             view.measure(mWidthMeasureSpec, MeasureSpec.UNSPECIFIED);
6922             final int height = view.getMeasuredHeight();
6923             mRecycler.addScrapView(view, position);
6924             return height;
6925         }
6926     }
6927 
6928     /**
6929      * Sets the selected item and positions the selection y pixels from the top edge
6930      * of the ListView. (If in touch mode, the item will not be selected but it will
6931      * still be positioned appropriately.)
6932      *
6933      * @param position Index (starting at 0) of the data item to be selected.
6934      * @param y The distance from the top edge of the ListView (plus padding) that the
6935      *        item will be positioned.
6936      */
setSelectionFromTop(int position, int y)6937     public void setSelectionFromTop(int position, int y) {
6938         if (mAdapter == null) {
6939             return;
6940         }
6941 
6942         if (!isInTouchMode()) {
6943             position = lookForSelectablePosition(position, true);
6944             if (position >= 0) {
6945                 setNextSelectedPositionInt(position);
6946             }
6947         } else {
6948             mResurrectToPosition = position;
6949         }
6950 
6951         if (position >= 0) {
6952             mLayoutMode = LAYOUT_SPECIFIC;
6953             mSpecificTop = mListPadding.top + y;
6954 
6955             if (mNeedSync) {
6956                 mSyncPosition = position;
6957                 mSyncRowId = mAdapter.getItemId(position);
6958             }
6959 
6960             if (mPositionScroller != null) {
6961                 mPositionScroller.stop();
6962             }
6963             requestLayout();
6964         }
6965     }
6966 
6967     /** @hide */
6968     @Override
encodeProperties(@onNull ViewHierarchyEncoder encoder)6969     protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
6970         super.encodeProperties(encoder);
6971 
6972         encoder.addProperty("drawing:cacheColorHint", getCacheColorHint());
6973         encoder.addProperty("list:fastScrollEnabled", isFastScrollEnabled());
6974         encoder.addProperty("list:scrollingCacheEnabled", isScrollingCacheEnabled());
6975         encoder.addProperty("list:smoothScrollbarEnabled", isSmoothScrollbarEnabled());
6976         encoder.addProperty("list:stackFromBottom", isStackFromBottom());
6977         encoder.addProperty("list:textFilterEnabled", isTextFilterEnabled());
6978 
6979         View selectedView = getSelectedView();
6980         if (selectedView != null) {
6981             encoder.addPropertyKey("selectedView");
6982             selectedView.encode(encoder);
6983         }
6984     }
6985 
6986     /**
6987      * Abstract positon scroller used to handle smooth scrolling.
6988      */
6989     static abstract class AbsPositionScroller {
6990         public abstract void start(int position);
6991         public abstract void start(int position, int boundPosition);
6992         public abstract void startWithOffset(int position, int offset);
6993         public abstract void startWithOffset(int position, int offset, int duration);
6994         public abstract void stop();
6995     }
6996 
6997     /**
6998      * Default position scroller that simulates a fling.
6999      */
7000     class PositionScroller extends AbsPositionScroller implements Runnable {
7001         private static final int SCROLL_DURATION = 200;
7002 
7003         private static final int MOVE_DOWN_POS = 1;
7004         private static final int MOVE_UP_POS = 2;
7005         private static final int MOVE_DOWN_BOUND = 3;
7006         private static final int MOVE_UP_BOUND = 4;
7007         private static final int MOVE_OFFSET = 5;
7008 
7009         private int mMode;
7010         private int mTargetPos;
7011         private int mBoundPos;
7012         private int mLastSeenPos;
7013         private int mScrollDuration;
7014         private final int mExtraScroll;
7015 
7016         private int mOffsetFromTop;
7017 
PositionScroller()7018         PositionScroller() {
7019             mExtraScroll = ViewConfiguration.get(mContext).getScaledFadingEdgeLength();
7020         }
7021 
7022         @Override
start(final int position)7023         public void start(final int position) {
7024             stop();
7025 
7026             if (mDataChanged) {
7027                 // Wait until we're back in a stable state to try this.
7028                 mPositionScrollAfterLayout = new Runnable() {
7029                     @Override public void run() {
7030                         start(position);
7031                     }
7032                 };
7033                 return;
7034             }
7035 
7036             final int childCount = getChildCount();
7037             if (childCount == 0) {
7038                 // Can't scroll without children.
7039                 return;
7040             }
7041 
7042             final int firstPos = mFirstPosition;
7043             final int lastPos = firstPos + childCount - 1;
7044 
7045             int viewTravelCount;
7046             int clampedPosition = Math.max(0, Math.min(getCount() - 1, position));
7047             if (clampedPosition < firstPos) {
7048                 viewTravelCount = firstPos - clampedPosition + 1;
7049                 mMode = MOVE_UP_POS;
7050             } else if (clampedPosition > lastPos) {
7051                 viewTravelCount = clampedPosition - lastPos + 1;
7052                 mMode = MOVE_DOWN_POS;
7053             } else {
7054                 scrollToVisible(clampedPosition, INVALID_POSITION, SCROLL_DURATION);
7055                 return;
7056             }
7057 
7058             if (viewTravelCount > 0) {
7059                 mScrollDuration = SCROLL_DURATION / viewTravelCount;
7060             } else {
7061                 mScrollDuration = SCROLL_DURATION;
7062             }
7063             mTargetPos = clampedPosition;
7064             mBoundPos = INVALID_POSITION;
7065             mLastSeenPos = INVALID_POSITION;
7066 
7067             postOnAnimation(this);
7068         }
7069 
7070         @Override
start(final int position, final int boundPosition)7071         public void start(final int position, final int boundPosition) {
7072             stop();
7073 
7074             if (boundPosition == INVALID_POSITION) {
7075                 start(position);
7076                 return;
7077             }
7078 
7079             if (mDataChanged) {
7080                 // Wait until we're back in a stable state to try this.
7081                 mPositionScrollAfterLayout = new Runnable() {
7082                     @Override public void run() {
7083                         start(position, boundPosition);
7084                     }
7085                 };
7086                 return;
7087             }
7088 
7089             final int childCount = getChildCount();
7090             if (childCount == 0) {
7091                 // Can't scroll without children.
7092                 return;
7093             }
7094 
7095             final int firstPos = mFirstPosition;
7096             final int lastPos = firstPos + childCount - 1;
7097 
7098             int viewTravelCount;
7099             int clampedPosition = Math.max(0, Math.min(getCount() - 1, position));
7100             if (clampedPosition < firstPos) {
7101                 final int boundPosFromLast = lastPos - boundPosition;
7102                 if (boundPosFromLast < 1) {
7103                     // Moving would shift our bound position off the screen. Abort.
7104                     return;
7105                 }
7106 
7107                 final int posTravel = firstPos - clampedPosition + 1;
7108                 final int boundTravel = boundPosFromLast - 1;
7109                 if (boundTravel < posTravel) {
7110                     viewTravelCount = boundTravel;
7111                     mMode = MOVE_UP_BOUND;
7112                 } else {
7113                     viewTravelCount = posTravel;
7114                     mMode = MOVE_UP_POS;
7115                 }
7116             } else if (clampedPosition > lastPos) {
7117                 final int boundPosFromFirst = boundPosition - firstPos;
7118                 if (boundPosFromFirst < 1) {
7119                     // Moving would shift our bound position off the screen. Abort.
7120                     return;
7121                 }
7122 
7123                 final int posTravel = clampedPosition - lastPos + 1;
7124                 final int boundTravel = boundPosFromFirst - 1;
7125                 if (boundTravel < posTravel) {
7126                     viewTravelCount = boundTravel;
7127                     mMode = MOVE_DOWN_BOUND;
7128                 } else {
7129                     viewTravelCount = posTravel;
7130                     mMode = MOVE_DOWN_POS;
7131                 }
7132             } else {
7133                 scrollToVisible(clampedPosition, boundPosition, SCROLL_DURATION);
7134                 return;
7135             }
7136 
7137             if (viewTravelCount > 0) {
7138                 mScrollDuration = SCROLL_DURATION / viewTravelCount;
7139             } else {
7140                 mScrollDuration = SCROLL_DURATION;
7141             }
7142             mTargetPos = clampedPosition;
7143             mBoundPos = boundPosition;
7144             mLastSeenPos = INVALID_POSITION;
7145 
7146             postOnAnimation(this);
7147         }
7148 
7149         @Override
startWithOffset(int position, int offset)7150         public void startWithOffset(int position, int offset) {
7151             startWithOffset(position, offset, SCROLL_DURATION);
7152         }
7153 
7154         @Override
startWithOffset(final int position, int offset, final int duration)7155         public void startWithOffset(final int position, int offset, final int duration) {
7156             stop();
7157 
7158             if (mDataChanged) {
7159                 // Wait until we're back in a stable state to try this.
7160                 final int postOffset = offset;
7161                 mPositionScrollAfterLayout = new Runnable() {
7162                     @Override public void run() {
7163                         startWithOffset(position, postOffset, duration);
7164                     }
7165                 };
7166                 return;
7167             }
7168 
7169             final int childCount = getChildCount();
7170             if (childCount == 0) {
7171                 // Can't scroll without children.
7172                 return;
7173             }
7174 
7175             offset += getPaddingTop();
7176 
7177             mTargetPos = Math.max(0, Math.min(getCount() - 1, position));
7178             mOffsetFromTop = offset;
7179             mBoundPos = INVALID_POSITION;
7180             mLastSeenPos = INVALID_POSITION;
7181             mMode = MOVE_OFFSET;
7182 
7183             final int firstPos = mFirstPosition;
7184             final int lastPos = firstPos + childCount - 1;
7185 
7186             int viewTravelCount;
7187             if (mTargetPos < firstPos) {
7188                 viewTravelCount = firstPos - mTargetPos;
7189             } else if (mTargetPos > lastPos) {
7190                 viewTravelCount = mTargetPos - lastPos;
7191             } else {
7192                 // On-screen, just scroll.
7193                 final int targetTop = getChildAt(mTargetPos - firstPos).getTop();
7194                 smoothScrollBy(targetTop - offset, duration, true);
7195                 return;
7196             }
7197 
7198             // Estimate how many screens we should travel
7199             final float screenTravelCount = (float) viewTravelCount / childCount;
7200             mScrollDuration = screenTravelCount < 1 ?
7201                     duration : (int) (duration / screenTravelCount);
7202             mLastSeenPos = INVALID_POSITION;
7203 
7204             postOnAnimation(this);
7205         }
7206 
7207         /**
7208          * Scroll such that targetPos is in the visible padded region without scrolling
7209          * boundPos out of view. Assumes targetPos is onscreen.
7210          */
7211         private void scrollToVisible(int targetPos, int boundPos, int duration) {
7212             final int firstPos = mFirstPosition;
7213             final int childCount = getChildCount();
7214             final int lastPos = firstPos + childCount - 1;
7215             final int paddedTop = mListPadding.top;
7216             final int paddedBottom = getHeight() - mListPadding.bottom;
7217 
7218             if (targetPos < firstPos || targetPos > lastPos) {
7219                 Log.w(TAG, "scrollToVisible called with targetPos " + targetPos +
7220                         " not visible [" + firstPos + ", " + lastPos + "]");
7221             }
7222             if (boundPos < firstPos || boundPos > lastPos) {
7223                 // boundPos doesn't matter, it's already offscreen.
7224                 boundPos = INVALID_POSITION;
7225             }
7226 
7227             final View targetChild = getChildAt(targetPos - firstPos);
7228             final int targetTop = targetChild.getTop();
7229             final int targetBottom = targetChild.getBottom();
7230             int scrollBy = 0;
7231 
7232             if (targetBottom > paddedBottom) {
7233                 scrollBy = targetBottom - paddedBottom;
7234             }
7235             if (targetTop < paddedTop) {
7236                 scrollBy = targetTop - paddedTop;
7237             }
7238 
7239             if (scrollBy == 0) {
7240                 return;
7241             }
7242 
7243             if (boundPos >= 0) {
7244                 final View boundChild = getChildAt(boundPos - firstPos);
7245                 final int boundTop = boundChild.getTop();
7246                 final int boundBottom = boundChild.getBottom();
7247                 final int absScroll = Math.abs(scrollBy);
7248 
7249                 if (scrollBy < 0 && boundBottom + absScroll > paddedBottom) {
7250                     // Don't scroll the bound view off the bottom of the screen.
7251                     scrollBy = Math.max(0, boundBottom - paddedBottom);
7252                 } else if (scrollBy > 0 && boundTop - absScroll < paddedTop) {
7253                     // Don't scroll the bound view off the top of the screen.
7254                     scrollBy = Math.min(0, boundTop - paddedTop);
7255                 }
7256             }
7257 
7258             smoothScrollBy(scrollBy, duration);
7259         }
7260 
7261         @Override
stop()7262         public void stop() {
7263             removeCallbacks(this);
7264         }
7265 
7266         @Override
run()7267         public void run() {
7268             final int listHeight = getHeight();
7269             final int firstPos = mFirstPosition;
7270 
7271             switch (mMode) {
7272             case MOVE_DOWN_POS: {
7273                 final int lastViewIndex = getChildCount() - 1;
7274                 final int lastPos = firstPos + lastViewIndex;
7275 
7276                 if (lastViewIndex < 0) {
7277                     return;
7278                 }
7279 
7280                 if (lastPos == mLastSeenPos) {
7281                     // No new views, let things keep going.
7282                     postOnAnimation(this);
7283                     return;
7284                 }
7285 
7286                 final View lastView = getChildAt(lastViewIndex);
7287                 final int lastViewHeight = lastView.getHeight();
7288                 final int lastViewTop = lastView.getTop();
7289                 final int lastViewPixelsShowing = listHeight - lastViewTop;
7290                 final int extraScroll = lastPos < mItemCount - 1 ?
7291                         Math.max(mListPadding.bottom, mExtraScroll) : mListPadding.bottom;
7292 
7293                 final int scrollBy = lastViewHeight - lastViewPixelsShowing + extraScroll;
7294                 smoothScrollBy(scrollBy, mScrollDuration, true);
7295 
7296                 mLastSeenPos = lastPos;
7297                 if (lastPos < mTargetPos) {
7298                     postOnAnimation(this);
7299                 }
7300                 break;
7301             }
7302 
7303             case MOVE_DOWN_BOUND: {
7304                 final int nextViewIndex = 1;
7305                 final int childCount = getChildCount();
7306 
7307                 if (firstPos == mBoundPos || childCount <= nextViewIndex
7308                         || firstPos + childCount >= mItemCount) {
7309                     return;
7310                 }
7311                 final int nextPos = firstPos + nextViewIndex;
7312 
7313                 if (nextPos == mLastSeenPos) {
7314                     // No new views, let things keep going.
7315                     postOnAnimation(this);
7316                     return;
7317                 }
7318 
7319                 final View nextView = getChildAt(nextViewIndex);
7320                 final int nextViewHeight = nextView.getHeight();
7321                 final int nextViewTop = nextView.getTop();
7322                 final int extraScroll = Math.max(mListPadding.bottom, mExtraScroll);
7323                 if (nextPos < mBoundPos) {
7324                     smoothScrollBy(Math.max(0, nextViewHeight + nextViewTop - extraScroll),
7325                             mScrollDuration, true);
7326 
7327                     mLastSeenPos = nextPos;
7328 
7329                     postOnAnimation(this);
7330                 } else  {
7331                     if (nextViewTop > extraScroll) {
7332                         smoothScrollBy(nextViewTop - extraScroll, mScrollDuration, true);
7333                     }
7334                 }
7335                 break;
7336             }
7337 
7338             case MOVE_UP_POS: {
7339                 if (firstPos == mLastSeenPos) {
7340                     // No new views, let things keep going.
7341                     postOnAnimation(this);
7342                     return;
7343                 }
7344 
7345                 final View firstView = getChildAt(0);
7346                 if (firstView == null) {
7347                     return;
7348                 }
7349                 final int firstViewTop = firstView.getTop();
7350                 final int extraScroll = firstPos > 0 ?
7351                         Math.max(mExtraScroll, mListPadding.top) : mListPadding.top;
7352 
7353                 smoothScrollBy(firstViewTop - extraScroll, mScrollDuration, true);
7354 
7355                 mLastSeenPos = firstPos;
7356 
7357                 if (firstPos > mTargetPos) {
7358                     postOnAnimation(this);
7359                 }
7360                 break;
7361             }
7362 
7363             case MOVE_UP_BOUND: {
7364                 final int lastViewIndex = getChildCount() - 2;
7365                 if (lastViewIndex < 0) {
7366                     return;
7367                 }
7368                 final int lastPos = firstPos + lastViewIndex;
7369 
7370                 if (lastPos == mLastSeenPos) {
7371                     // No new views, let things keep going.
7372                     postOnAnimation(this);
7373                     return;
7374                 }
7375 
7376                 final View lastView = getChildAt(lastViewIndex);
7377                 final int lastViewHeight = lastView.getHeight();
7378                 final int lastViewTop = lastView.getTop();
7379                 final int lastViewPixelsShowing = listHeight - lastViewTop;
7380                 final int extraScroll = Math.max(mListPadding.top, mExtraScroll);
7381                 mLastSeenPos = lastPos;
7382                 if (lastPos > mBoundPos) {
7383                     smoothScrollBy(-(lastViewPixelsShowing - extraScroll), mScrollDuration, true);
7384                     postOnAnimation(this);
7385                 } else {
7386                     final int bottom = listHeight - extraScroll;
7387                     final int lastViewBottom = lastViewTop + lastViewHeight;
7388                     if (bottom > lastViewBottom) {
7389                         smoothScrollBy(-(bottom - lastViewBottom), mScrollDuration, true);
7390                     }
7391                 }
7392                 break;
7393             }
7394 
7395             case MOVE_OFFSET: {
7396                 if (mLastSeenPos == firstPos) {
7397                     // No new views, let things keep going.
7398                     postOnAnimation(this);
7399                     return;
7400                 }
7401 
7402                 mLastSeenPos = firstPos;
7403 
7404                 final int childCount = getChildCount();
7405                 final int position = mTargetPos;
7406                 final int lastPos = firstPos + childCount - 1;
7407 
7408                 int viewTravelCount = 0;
7409                 if (position < firstPos) {
7410                     viewTravelCount = firstPos - position + 1;
7411                 } else if (position > lastPos) {
7412                     viewTravelCount = position - lastPos;
7413                 }
7414 
7415                 // Estimate how many screens we should travel
7416                 final float screenTravelCount = (float) viewTravelCount / childCount;
7417 
7418                 final float modifier = Math.min(Math.abs(screenTravelCount), 1.f);
7419                 if (position < firstPos) {
7420                     final int distance = (int) (-getHeight() * modifier);
7421                     final int duration = (int) (mScrollDuration * modifier);
7422                     smoothScrollBy(distance, duration, true);
7423                     postOnAnimation(this);
7424                 } else if (position > lastPos) {
7425                     final int distance = (int) (getHeight() * modifier);
7426                     final int duration = (int) (mScrollDuration * modifier);
7427                     smoothScrollBy(distance, duration, true);
7428                     postOnAnimation(this);
7429                 } else {
7430                     // On-screen, just scroll.
7431                     final int targetTop = getChildAt(position - firstPos).getTop();
7432                     final int distance = targetTop - mOffsetFromTop;
7433                     final int duration = (int) (mScrollDuration *
7434                             ((float) Math.abs(distance) / getHeight()));
7435                     smoothScrollBy(distance, duration, true);
7436                 }
7437                 break;
7438             }
7439 
7440             default:
7441                 break;
7442             }
7443         }
7444     }
7445 }
7446