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