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