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