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