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