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