• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2006 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.widget;
18 
19 import android.annotation.ColorInt;
20 import android.annotation.DrawableRes;
21 import android.annotation.NonNull;
22 import android.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                                     invalidateTopGlow();
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                                     invalidateBottomGlow();
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                             invalidateTopGlow();
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                             invalidateBottomGlow();
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             consumed = mEdgeGlowTop.onPullDistance((float) deltaY / getHeight(),
3787                     (float) x / getWidth());
3788             if (consumed != 0f) {
3789                 invalidateTopGlow();
3790             }
3791         } else if (mEdgeGlowBottom.getDistance() != 0) {
3792             consumed = -mEdgeGlowBottom.onPullDistance((float) -deltaY / getHeight(),
3793                     1f - (float) x / getWidth());
3794             if (consumed != 0f) {
3795                 invalidateBottomGlow();
3796             }
3797         }
3798         int pixelsConsumed = Math.round(consumed * getHeight());
3799         return deltaY - pixelsConsumed;
3800     }
3801 
3802     /**
3803      * @return <code>true</code> if either the top or bottom edge glow is currently active or
3804      * <code>false</code> if it has no value to release.
3805      */
isGlowActive()3806     private boolean isGlowActive() {
3807         return mEdgeGlowBottom.getDistance() != 0 || mEdgeGlowTop.getDistance() != 0;
3808     }
3809 
invalidateTopGlow()3810     private void invalidateTopGlow() {
3811         if (!shouldDisplayEdgeEffects()) {
3812             return;
3813         }
3814         final boolean clipToPadding = getClipToPadding();
3815         final int top = clipToPadding ? mPaddingTop : 0;
3816         final int left = clipToPadding ? mPaddingLeft : 0;
3817         final int right = clipToPadding ? getWidth() - mPaddingRight : getWidth();
3818         invalidate(left, top, right, top + mEdgeGlowTop.getMaxHeight());
3819     }
3820 
invalidateBottomGlow()3821     private void invalidateBottomGlow() {
3822         if (!shouldDisplayEdgeEffects()) {
3823             return;
3824         }
3825         final boolean clipToPadding = getClipToPadding();
3826         final int bottom = clipToPadding ? getHeight() - mPaddingBottom : getHeight();
3827         final int left = clipToPadding ? mPaddingLeft : 0;
3828         final int right = clipToPadding ? getWidth() - mPaddingRight : getWidth();
3829         invalidate(left, bottom - mEdgeGlowBottom.getMaxHeight(), right, bottom);
3830     }
3831 
3832     @Override
onTouchModeChanged(boolean isInTouchMode)3833     public void onTouchModeChanged(boolean isInTouchMode) {
3834         if (isInTouchMode) {
3835             // Get rid of the selection when we enter touch mode
3836             hideSelector();
3837             // Layout, but only if we already have done so previously.
3838             // (Otherwise may clobber a LAYOUT_SYNC layout that was requested to restore
3839             // state.)
3840             if (getHeight() > 0 && getChildCount() > 0) {
3841                 // We do not lose focus initiating a touch (since AbsListView is focusable in
3842                 // touch mode). Force an initial layout to get rid of the selection.
3843                 layoutChildren();
3844             }
3845             updateSelectorState();
3846         } else {
3847             int touchMode = mTouchMode;
3848             if (touchMode == TOUCH_MODE_OVERSCROLL || touchMode == TOUCH_MODE_OVERFLING) {
3849                 if (mFlingRunnable != null) {
3850                     mFlingRunnable.endFling();
3851                 }
3852                 if (mPositionScroller != null) {
3853                     mPositionScroller.stop();
3854                 }
3855 
3856                 if (mScrollY != 0) {
3857                     mScrollY = 0;
3858                     invalidateParentCaches();
3859                     finishGlows();
3860                     invalidate();
3861                 }
3862             }
3863         }
3864     }
3865 
3866     /** @hide */
3867     @Override
handleScrollBarDragging(MotionEvent event)3868     protected boolean handleScrollBarDragging(MotionEvent event) {
3869         // Doesn't support normal scroll bar dragging. Use FastScroller.
3870         return false;
3871     }
3872 
3873     @Override
onTouchEvent(MotionEvent ev)3874     public boolean onTouchEvent(MotionEvent ev) {
3875         if (!isEnabled()) {
3876             // A disabled view that is clickable still consumes the touch
3877             // events, it just doesn't respond to them.
3878             return isClickable() || isLongClickable();
3879         }
3880 
3881         if (mPositionScroller != null) {
3882             mPositionScroller.stop();
3883         }
3884 
3885         if (mIsDetaching || !isAttachedToWindow()) {
3886             // Something isn't right.
3887             // Since we rely on being attached to get data set change notifications,
3888             // don't risk doing anything where we might try to resync and find things
3889             // in a bogus state.
3890             return false;
3891         }
3892 
3893         startNestedScroll(SCROLL_AXIS_VERTICAL);
3894 
3895         if (mFastScroll != null && mFastScroll.onTouchEvent(ev)) {
3896             return true;
3897         }
3898 
3899         initVelocityTrackerIfNotExists();
3900         final MotionEvent vtev = MotionEvent.obtain(ev);
3901 
3902         final int actionMasked = ev.getActionMasked();
3903         if (actionMasked == MotionEvent.ACTION_DOWN) {
3904             mNestedYOffset = 0;
3905         }
3906         vtev.offsetLocation(0, mNestedYOffset);
3907         switch (actionMasked) {
3908             case MotionEvent.ACTION_DOWN: {
3909                 onTouchDown(ev);
3910                 break;
3911             }
3912 
3913             case MotionEvent.ACTION_MOVE: {
3914                 onTouchMove(ev, vtev);
3915                 break;
3916             }
3917 
3918             case MotionEvent.ACTION_UP: {
3919                 onTouchUp(ev);
3920                 break;
3921             }
3922 
3923             case MotionEvent.ACTION_CANCEL: {
3924                 onTouchCancel();
3925                 break;
3926             }
3927 
3928             case MotionEvent.ACTION_POINTER_UP: {
3929                 onSecondaryPointerUp(ev);
3930                 final int x = mMotionX;
3931                 final int y = mMotionY;
3932                 final int motionPosition = pointToPosition(x, y);
3933                 if (motionPosition >= 0) {
3934                     // Remember where the motion event started
3935                     final View child = getChildAt(motionPosition - mFirstPosition);
3936                     mMotionViewOriginalTop = child.getTop();
3937                     mMotionPosition = motionPosition;
3938                 }
3939                 mLastY = y;
3940                 break;
3941             }
3942 
3943             case MotionEvent.ACTION_POINTER_DOWN: {
3944                 // New pointers take over dragging duties
3945                 final int index = ev.getActionIndex();
3946                 final int id = ev.getPointerId(index);
3947                 final int x = (int) ev.getX(index);
3948                 final int y = (int) ev.getY(index);
3949                 mMotionCorrection = 0;
3950                 mActivePointerId = id;
3951                 mMotionX = x;
3952                 mMotionY = y;
3953                 final int motionPosition = pointToPosition(x, y);
3954                 if (motionPosition >= 0) {
3955                     // Remember where the motion event started
3956                     final View child = getChildAt(motionPosition - mFirstPosition);
3957                     mMotionViewOriginalTop = child.getTop();
3958                     mMotionPosition = motionPosition;
3959                 }
3960                 mLastY = y;
3961                 break;
3962             }
3963         }
3964 
3965         if (mVelocityTracker != null) {
3966             mVelocityTracker.addMovement(vtev);
3967         }
3968         vtev.recycle();
3969         return true;
3970     }
3971 
onTouchDown(MotionEvent ev)3972     private void onTouchDown(MotionEvent ev) {
3973         mHasPerformedLongPress = false;
3974         mActivePointerId = ev.getPointerId(0);
3975         hideSelector();
3976 
3977         if (mTouchMode == TOUCH_MODE_OVERFLING) {
3978             // Stopped the fling. It is a scroll.
3979             if (mFlingRunnable != null) {
3980                 mFlingRunnable.endFling();
3981             }
3982             if (mPositionScroller != null) {
3983                 mPositionScroller.stop();
3984             }
3985             mTouchMode = TOUCH_MODE_OVERSCROLL;
3986             mMotionX = (int) ev.getX();
3987             mMotionY = (int) ev.getY();
3988             mLastY = mMotionY;
3989             mMotionCorrection = 0;
3990             mDirection = 0;
3991             stopEdgeGlowRecede(ev.getX());
3992         } else {
3993             final int x = (int) ev.getX();
3994             final int y = (int) ev.getY();
3995             int motionPosition = pointToPosition(x, y);
3996 
3997             if (!mDataChanged) {
3998                 if (mTouchMode == TOUCH_MODE_FLING) {
3999                     // Stopped a fling. It is a scroll.
4000                     createScrollingCache();
4001                     mTouchMode = TOUCH_MODE_SCROLL;
4002                     mMotionCorrection = 0;
4003                     motionPosition = findMotionRow(y);
4004                     if (mFlingRunnable != null) {
4005                         mFlingRunnable.flywheelTouch();
4006                     }
4007                     stopEdgeGlowRecede(x);
4008                 } else if ((motionPosition >= 0) && getAdapter().isEnabled(motionPosition)) {
4009                     // User clicked on an actual view (and was not stopping a
4010                     // fling). It might be a click or a scroll. Assume it is a
4011                     // click until proven otherwise.
4012                     mTouchMode = TOUCH_MODE_DOWN;
4013 
4014                     // FIXME Debounce
4015                     if (mPendingCheckForTap == null) {
4016                         mPendingCheckForTap = new CheckForTap();
4017                     }
4018 
4019                     mPendingCheckForTap.x = ev.getX();
4020                     mPendingCheckForTap.y = ev.getY();
4021                     postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
4022                 }
4023             }
4024 
4025             if (motionPosition >= 0) {
4026                 // Remember where the motion event started
4027                 final View v = getChildAt(motionPosition - mFirstPosition);
4028                 mMotionViewOriginalTop = v.getTop();
4029             }
4030 
4031             mMotionX = x;
4032             mMotionY = y;
4033             mMotionPosition = motionPosition;
4034             mLastY = Integer.MIN_VALUE;
4035         }
4036 
4037         if (mTouchMode == TOUCH_MODE_DOWN && mMotionPosition != INVALID_POSITION
4038                 && performButtonActionOnTouchDown(ev)) {
4039                 removeCallbacks(mPendingCheckForTap);
4040         }
4041     }
4042 
stopEdgeGlowRecede(float x)4043     private void stopEdgeGlowRecede(float x) {
4044         if (mEdgeGlowTop.getDistance() != 0) {
4045             mEdgeGlowTop.onPullDistance(0, x / getWidth());
4046         }
4047         if (mEdgeGlowBottom.getDistance() != 0) {
4048             mEdgeGlowBottom.onPullDistance(0, x / getWidth());
4049         }
4050     }
4051 
onTouchMove(MotionEvent ev, MotionEvent vtev)4052     private void onTouchMove(MotionEvent ev, MotionEvent vtev) {
4053         if (mHasPerformedLongPress) {
4054             // Consume all move events following a successful long press.
4055             return;
4056         }
4057 
4058         int pointerIndex = ev.findPointerIndex(mActivePointerId);
4059         if (pointerIndex == -1) {
4060             pointerIndex = 0;
4061             mActivePointerId = ev.getPointerId(pointerIndex);
4062         }
4063 
4064         if (mDataChanged) {
4065             // Re-sync everything if data has been changed
4066             // since the scroll operation can query the adapter.
4067             layoutChildren();
4068         }
4069 
4070         final int y = (int) ev.getY(pointerIndex);
4071 
4072         switch (mTouchMode) {
4073             case TOUCH_MODE_DOWN:
4074             case TOUCH_MODE_TAP:
4075             case TOUCH_MODE_DONE_WAITING:
4076                 // Check if we have moved far enough that it looks more like a
4077                 // scroll than a tap. If so, we'll enter scrolling mode.
4078                 if (startScrollIfNeeded((int) ev.getX(pointerIndex), y, vtev)) {
4079                     break;
4080                 }
4081                 // Otherwise, check containment within list bounds. If we're
4082                 // outside bounds, cancel any active presses.
4083                 final View motionView = getChildAt(mMotionPosition - mFirstPosition);
4084                 final float x = ev.getX(pointerIndex);
4085                 if (!pointInView(x, y, mTouchSlop)) {
4086                     setPressed(false);
4087                     if (motionView != null) {
4088                         motionView.setPressed(false);
4089                     }
4090                     removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
4091                             mPendingCheckForTap : mPendingCheckForLongPress);
4092                     mTouchMode = TOUCH_MODE_DONE_WAITING;
4093                     updateSelectorState();
4094                 } else if (motionView != null) {
4095                     // Still within bounds, update the hotspot.
4096                     final float[] point = mTmpPoint;
4097                     point[0] = x;
4098                     point[1] = y;
4099                     transformPointToViewLocal(point, motionView);
4100                     motionView.drawableHotspotChanged(point[0], point[1]);
4101                 }
4102                 break;
4103             case TOUCH_MODE_SCROLL:
4104             case TOUCH_MODE_OVERSCROLL:
4105                 scrollIfNeeded((int) ev.getX(pointerIndex), y, vtev);
4106                 break;
4107         }
4108     }
4109 
onTouchUp(MotionEvent ev)4110     private void onTouchUp(MotionEvent ev) {
4111         switch (mTouchMode) {
4112             case TOUCH_MODE_DOWN:
4113             case TOUCH_MODE_TAP:
4114             case TOUCH_MODE_DONE_WAITING:
4115                 final int motionPosition = mMotionPosition;
4116                 final View child = getChildAt(motionPosition - mFirstPosition);
4117                 if (child != null) {
4118                     if (mTouchMode != TOUCH_MODE_DOWN) {
4119                         child.setPressed(false);
4120                     }
4121 
4122                     final float x = ev.getX();
4123                     final boolean inList =
4124                             x > mListPadding.left && x < getWidth() - mListPadding.right;
4125                     if (inList && !child.hasExplicitFocusable()) {
4126                         if (mPerformClick == null) {
4127                             mPerformClick = new PerformClick();
4128                         }
4129 
4130                         final AbsListView.PerformClick performClick = mPerformClick;
4131                         performClick.mClickMotionPosition = motionPosition;
4132                         performClick.rememberWindowAttachCount();
4133 
4134                         mResurrectToPosition = motionPosition;
4135 
4136                         if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) {
4137                             removeCallbacks(mTouchMode == TOUCH_MODE_DOWN
4138                                     ? mPendingCheckForTap : mPendingCheckForLongPress);
4139                             mLayoutMode = LAYOUT_NORMAL;
4140                             if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
4141                                 mTouchMode = TOUCH_MODE_TAP;
4142                                 setSelectedPositionInt(mMotionPosition);
4143                                 layoutChildren();
4144                                 child.setPressed(true);
4145                                 positionSelector(mMotionPosition, child);
4146                                 setPressed(true);
4147                                 if (mSelector != null) {
4148                                     Drawable d = mSelector.getCurrent();
4149                                     if (d != null && d instanceof TransitionDrawable) {
4150                                         ((TransitionDrawable) d).resetTransition();
4151                                     }
4152                                     mSelector.setHotspot(x, ev.getY());
4153                                 }
4154                                 if (mTouchModeReset != null) {
4155                                     removeCallbacks(mTouchModeReset);
4156                                 }
4157                                 mTouchModeReset = new Runnable() {
4158                                     @Override
4159                                     public void run() {
4160                                         mTouchModeReset = null;
4161                                         mTouchMode = TOUCH_MODE_REST;
4162                                         child.setPressed(false);
4163                                         setPressed(false);
4164                                         if (!mDataChanged && !mIsDetaching
4165                                                 && isAttachedToWindow()) {
4166                                             performClick.run();
4167                                         }
4168                                     }
4169                                 };
4170                                 postDelayed(mTouchModeReset,
4171                                         ViewConfiguration.getPressedStateDuration());
4172                             } else {
4173                                 mTouchMode = TOUCH_MODE_REST;
4174                                 updateSelectorState();
4175                             }
4176                             return;
4177                         } else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
4178                             performClick.run();
4179                         }
4180                     }
4181                 }
4182                 mTouchMode = TOUCH_MODE_REST;
4183                 updateSelectorState();
4184                 break;
4185             case TOUCH_MODE_SCROLL:
4186                 final int childCount = getChildCount();
4187                 if (childCount > 0) {
4188                     final int firstChildTop = getChildAt(0).getTop();
4189                     final int lastChildBottom = getChildAt(childCount - 1).getBottom();
4190                     final int contentTop = mListPadding.top;
4191                     final int contentBottom = getHeight() - mListPadding.bottom;
4192                     if (mFirstPosition == 0 && firstChildTop >= contentTop
4193                             && mFirstPosition + childCount < mItemCount
4194                             && lastChildBottom <= getHeight() - contentBottom) {
4195                         mTouchMode = TOUCH_MODE_REST;
4196                         reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4197                     } else {
4198                         final VelocityTracker velocityTracker = mVelocityTracker;
4199                         velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
4200 
4201                         final int initialVelocity = (int)
4202                                 (velocityTracker.getYVelocity(mActivePointerId) * mVelocityScale);
4203                         // Fling if we have enough velocity and we aren't at a boundary.
4204                         // Since we can potentially overfling more than we can overscroll, don't
4205                         // allow the weird behavior where you can scroll to a boundary then
4206                         // fling further.
4207                         boolean flingVelocity = Math.abs(initialVelocity) > mMinimumVelocity;
4208                         if (flingVelocity && !mEdgeGlowTop.isFinished()) {
4209                             mEdgeGlowTop.onAbsorb(initialVelocity);
4210                         } else if (flingVelocity && !mEdgeGlowBottom.isFinished()) {
4211                             mEdgeGlowBottom.onAbsorb(-initialVelocity);
4212                         } else if (flingVelocity
4213                                 && !((mFirstPosition == 0
4214                                 && firstChildTop == contentTop - mOverscrollDistance)
4215                                 || (mFirstPosition + childCount == mItemCount
4216                                 && lastChildBottom == contentBottom + mOverscrollDistance))
4217                         ) {
4218                             if (!dispatchNestedPreFling(0, -initialVelocity)) {
4219                                 if (mFlingRunnable == null) {
4220                                     mFlingRunnable = new FlingRunnable();
4221                                 }
4222                                 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
4223                                 mFlingRunnable.start(-initialVelocity);
4224                                 dispatchNestedFling(0, -initialVelocity, true);
4225                             } else {
4226                                 mTouchMode = TOUCH_MODE_REST;
4227                                 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4228                             }
4229                         } else {
4230                             mTouchMode = TOUCH_MODE_REST;
4231                             reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4232                             if (mFlingRunnable != null) {
4233                                 mFlingRunnable.endFling();
4234                             }
4235                             if (mPositionScroller != null) {
4236                                 mPositionScroller.stop();
4237                             }
4238                             if (flingVelocity && !dispatchNestedPreFling(0, -initialVelocity)) {
4239                                 dispatchNestedFling(0, -initialVelocity, false);
4240                             }
4241                         }
4242                     }
4243                 } else {
4244                     mTouchMode = TOUCH_MODE_REST;
4245                     reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4246                 }
4247                 break;
4248 
4249             case TOUCH_MODE_OVERSCROLL:
4250                 if (mFlingRunnable == null) {
4251                     mFlingRunnable = new FlingRunnable();
4252                 }
4253                 final VelocityTracker velocityTracker = mVelocityTracker;
4254                 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
4255                 final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
4256 
4257                 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
4258                 if (Math.abs(initialVelocity) > mMinimumVelocity) {
4259                     mFlingRunnable.startOverfling(-initialVelocity);
4260                 } else {
4261                     mFlingRunnable.startSpringback();
4262                 }
4263 
4264                 break;
4265         }
4266 
4267         setPressed(false);
4268 
4269         if (shouldDisplayEdgeEffects()) {
4270             mEdgeGlowTop.onRelease();
4271             mEdgeGlowBottom.onRelease();
4272         }
4273 
4274         // Need to redraw since we probably aren't drawing the selector anymore
4275         invalidate();
4276         removeCallbacks(mPendingCheckForLongPress);
4277         recycleVelocityTracker();
4278 
4279         mActivePointerId = INVALID_POINTER;
4280 
4281         if (PROFILE_SCROLLING) {
4282             if (mScrollProfilingStarted) {
4283                 Debug.stopMethodTracing();
4284                 mScrollProfilingStarted = false;
4285             }
4286         }
4287 
4288         if (mScrollStrictSpan != null) {
4289             mScrollStrictSpan.finish();
4290             mScrollStrictSpan = null;
4291         }
4292     }
4293 
shouldDisplayEdgeEffects()4294     private boolean shouldDisplayEdgeEffects() {
4295         return getOverScrollMode() != OVER_SCROLL_NEVER;
4296     }
4297 
onTouchCancel()4298     private void onTouchCancel() {
4299         switch (mTouchMode) {
4300         case TOUCH_MODE_OVERSCROLL:
4301             if (mFlingRunnable == null) {
4302                 mFlingRunnable = new FlingRunnable();
4303             }
4304             mFlingRunnable.startSpringback();
4305             break;
4306 
4307         case TOUCH_MODE_OVERFLING:
4308             // Do nothing - let it play out.
4309             break;
4310 
4311         default:
4312             mTouchMode = TOUCH_MODE_REST;
4313             setPressed(false);
4314             final View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
4315             if (motionView != null) {
4316                 motionView.setPressed(false);
4317             }
4318             clearScrollingCache();
4319             removeCallbacks(mPendingCheckForLongPress);
4320             recycleVelocityTracker();
4321         }
4322 
4323         if (shouldDisplayEdgeEffects()) {
4324             mEdgeGlowTop.onRelease();
4325             mEdgeGlowBottom.onRelease();
4326         }
4327         mActivePointerId = INVALID_POINTER;
4328     }
4329 
4330     @Override
onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY)4331     protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
4332         if (mScrollY != scrollY) {
4333             onScrollChanged(mScrollX, scrollY, mScrollX, mScrollY);
4334             mScrollY = scrollY;
4335             invalidateParentIfNeeded();
4336 
4337             awakenScrollBars();
4338         }
4339     }
4340 
4341     @Override
onGenericMotionEvent(MotionEvent event)4342     public boolean onGenericMotionEvent(MotionEvent event) {
4343         switch (event.getAction()) {
4344             case MotionEvent.ACTION_SCROLL:
4345                 final float axisValue;
4346                 if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
4347                     axisValue = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
4348                 } else if (event.isFromSource(InputDevice.SOURCE_ROTARY_ENCODER)) {
4349                     axisValue = event.getAxisValue(MotionEvent.AXIS_SCROLL);
4350                 } else {
4351                     axisValue = 0;
4352                 }
4353 
4354                 final int delta = Math.round(axisValue * mVerticalScrollFactor);
4355                 if (delta != 0) {
4356                     if (!trackMotionScroll(delta, delta)) {
4357                         return true;
4358                     }
4359                 }
4360                 break;
4361             case MotionEvent.ACTION_BUTTON_PRESS:
4362                 if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
4363                     int actionButton = event.getActionButton();
4364                     if ((actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY
4365                             || actionButton == MotionEvent.BUTTON_SECONDARY)
4366                             && (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP)) {
4367                         if (performStylusButtonPressAction(event)) {
4368                             removeCallbacks(mPendingCheckForLongPress);
4369                             removeCallbacks(mPendingCheckForTap);
4370                         }
4371                     }
4372                 }
4373                 break;
4374         }
4375 
4376         return super.onGenericMotionEvent(event);
4377     }
4378 
4379     /**
4380      * Initiate a fling with the given velocity.
4381      *
4382      * <p>Applications can use this method to manually initiate a fling as if the user
4383      * initiated it via touch interaction.</p>
4384      *
4385      * @param velocityY Vertical velocity in pixels per second. Note that this is velocity of
4386      *                  content, not velocity of a touch that initiated the fling.
4387      */
fling(int velocityY)4388     public void fling(int velocityY) {
4389         if (mFlingRunnable == null) {
4390             mFlingRunnable = new FlingRunnable();
4391         }
4392         reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
4393         mFlingRunnable.start(velocityY);
4394     }
4395 
4396     @Override
onStartNestedScroll(View child, View target, int nestedScrollAxes)4397     public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
4398         return ((nestedScrollAxes & SCROLL_AXIS_VERTICAL) != 0);
4399     }
4400 
4401     @Override
onNestedScrollAccepted(View child, View target, int axes)4402     public void onNestedScrollAccepted(View child, View target, int axes) {
4403         super.onNestedScrollAccepted(child, target, axes);
4404         startNestedScroll(SCROLL_AXIS_VERTICAL);
4405     }
4406 
4407     @Override
onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed)4408     public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
4409             int dxUnconsumed, int dyUnconsumed) {
4410         final int motionIndex = getChildCount() / 2;
4411         final View motionView = getChildAt(motionIndex);
4412         final int oldTop = motionView != null ? motionView.getTop() : 0;
4413         if (motionView == null || trackMotionScroll(-dyUnconsumed, -dyUnconsumed)) {
4414             int myUnconsumed = dyUnconsumed;
4415             int myConsumed = 0;
4416             if (motionView != null) {
4417                 myConsumed = motionView.getTop() - oldTop;
4418                 myUnconsumed -= myConsumed;
4419             }
4420             dispatchNestedScroll(0, myConsumed, 0, myUnconsumed, null);
4421         }
4422     }
4423 
4424     @Override
onNestedFling(View target, float velocityX, float velocityY, boolean consumed)4425     public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
4426         final int childCount = getChildCount();
4427         if (!consumed && childCount > 0 && canScrollList((int) velocityY) &&
4428                 Math.abs(velocityY) > mMinimumVelocity) {
4429             reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
4430             if (mFlingRunnable == null) {
4431                 mFlingRunnable = new FlingRunnable();
4432             }
4433             if (!dispatchNestedPreFling(0, velocityY)) {
4434                 mFlingRunnable.start((int) velocityY);
4435             }
4436             return true;
4437         }
4438         return dispatchNestedFling(velocityX, velocityY, consumed);
4439     }
4440 
4441     @Override
draw(Canvas canvas)4442     public void draw(Canvas canvas) {
4443         super.draw(canvas);
4444         if (shouldDisplayEdgeEffects()) {
4445             final int scrollY = mScrollY;
4446             final boolean clipToPadding = getClipToPadding();
4447             final int width;
4448             final int height;
4449             final int translateX;
4450             final int translateY;
4451 
4452             if (clipToPadding) {
4453                 width = getWidth() - mPaddingLeft - mPaddingRight;
4454                 height = getHeight() - mPaddingTop - mPaddingBottom;
4455                 translateX = mPaddingLeft;
4456                 translateY = mPaddingTop;
4457             } else {
4458                 width = getWidth();
4459                 height = getHeight();
4460                 translateX = 0;
4461                 translateY = 0;
4462             }
4463             mEdgeGlowTop.setSize(width, height);
4464             mEdgeGlowBottom.setSize(width, height);
4465             if (!mEdgeGlowTop.isFinished()) {
4466                 final int restoreCount = canvas.save();
4467                 canvas.clipRect(translateX, translateY,
4468                          translateX + width ,translateY + mEdgeGlowTop.getMaxHeight());
4469                 final int edgeY = Math.min(0, scrollY + mFirstPositionDistanceGuess) + translateY;
4470                 canvas.translate(translateX, edgeY);
4471                 if (mEdgeGlowTop.draw(canvas)) {
4472                     invalidateTopGlow();
4473                 }
4474                 canvas.restoreToCount(restoreCount);
4475             }
4476             if (!mEdgeGlowBottom.isFinished()) {
4477                 final int restoreCount = canvas.save();
4478                 canvas.clipRect(translateX, translateY + height - mEdgeGlowBottom.getMaxHeight(),
4479                         translateX + width, translateY + height);
4480                 final int edgeX = -width + translateX;
4481                 final int edgeY = Math.max(getHeight(), scrollY + mLastPositionDistanceGuess)
4482                         - (clipToPadding ? mPaddingBottom : 0);
4483                 canvas.translate(edgeX, edgeY);
4484                 canvas.rotate(180, width, 0);
4485                 if (mEdgeGlowBottom.draw(canvas)) {
4486                     invalidateBottomGlow();
4487                 }
4488                 canvas.restoreToCount(restoreCount);
4489             }
4490         }
4491     }
4492 
initOrResetVelocityTracker()4493     private void initOrResetVelocityTracker() {
4494         if (mVelocityTracker == null) {
4495             mVelocityTracker = VelocityTracker.obtain();
4496         } else {
4497             mVelocityTracker.clear();
4498         }
4499     }
4500 
initVelocityTrackerIfNotExists()4501     private void initVelocityTrackerIfNotExists() {
4502         if (mVelocityTracker == null) {
4503             mVelocityTracker = VelocityTracker.obtain();
4504         }
4505     }
4506 
recycleVelocityTracker()4507     private void recycleVelocityTracker() {
4508         if (mVelocityTracker != null) {
4509             mVelocityTracker.recycle();
4510             mVelocityTracker = null;
4511         }
4512     }
4513 
4514     @Override
requestDisallowInterceptTouchEvent(boolean disallowIntercept)4515     public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
4516         if (disallowIntercept) {
4517             recycleVelocityTracker();
4518         }
4519         super.requestDisallowInterceptTouchEvent(disallowIntercept);
4520     }
4521 
4522     @Override
onInterceptHoverEvent(MotionEvent event)4523     public boolean onInterceptHoverEvent(MotionEvent event) {
4524         if (mFastScroll != null && mFastScroll.onInterceptHoverEvent(event)) {
4525             return true;
4526         }
4527 
4528         return super.onInterceptHoverEvent(event);
4529     }
4530 
4531     @Override
onResolvePointerIcon(MotionEvent event, int pointerIndex)4532     public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
4533         if (mFastScroll != null) {
4534             PointerIcon pointerIcon = mFastScroll.onResolvePointerIcon(event, pointerIndex);
4535             if (pointerIcon != null) {
4536                 return pointerIcon;
4537             }
4538         }
4539         return super.onResolvePointerIcon(event, pointerIndex);
4540     }
4541 
4542     @Override
onInterceptTouchEvent(MotionEvent ev)4543     public boolean onInterceptTouchEvent(MotionEvent ev) {
4544         final int actionMasked = ev.getActionMasked();
4545         View v;
4546 
4547         if (mPositionScroller != null) {
4548             mPositionScroller.stop();
4549         }
4550 
4551         if (mIsDetaching || !isAttachedToWindow()) {
4552             // Something isn't right.
4553             // Since we rely on being attached to get data set change notifications,
4554             // don't risk doing anything where we might try to resync and find things
4555             // in a bogus state.
4556             return false;
4557         }
4558 
4559         if (mFastScroll != null && mFastScroll.onInterceptTouchEvent(ev)) {
4560             return true;
4561         }
4562 
4563         switch (actionMasked) {
4564             case MotionEvent.ACTION_DOWN: {
4565                 int touchMode = mTouchMode;
4566                 if (touchMode == TOUCH_MODE_OVERFLING || touchMode == TOUCH_MODE_OVERSCROLL) {
4567                     mMotionCorrection = 0;
4568                     return true;
4569                 }
4570 
4571                 final int x = (int) ev.getX();
4572                 final int y = (int) ev.getY();
4573                 mActivePointerId = ev.getPointerId(0);
4574 
4575                 int motionPosition = findMotionRow(y);
4576                 if (isGlowActive()) {
4577                     // Pressed during edge effect, so this is considered the same as a fling catch.
4578                     touchMode = mTouchMode = TOUCH_MODE_FLING;
4579                 } else if (touchMode != TOUCH_MODE_FLING && motionPosition >= 0) {
4580                     // User clicked on an actual view (and was not stopping a fling).
4581                     // Remember where the motion event started
4582                     v = getChildAt(motionPosition - mFirstPosition);
4583                     mMotionViewOriginalTop = v.getTop();
4584                     mMotionX = x;
4585                     mMotionY = y;
4586                     mMotionPosition = motionPosition;
4587                     mTouchMode = TOUCH_MODE_DOWN;
4588                     clearScrollingCache();
4589                 }
4590                 mLastY = Integer.MIN_VALUE;
4591                 initOrResetVelocityTracker();
4592                 mVelocityTracker.addMovement(ev);
4593                 mNestedYOffset = 0;
4594                 startNestedScroll(SCROLL_AXIS_VERTICAL);
4595                 if (touchMode == TOUCH_MODE_FLING) {
4596                     return true;
4597                 }
4598                 break;
4599             }
4600 
4601             case MotionEvent.ACTION_MOVE: {
4602                 switch (mTouchMode) {
4603                     case TOUCH_MODE_DOWN:
4604                         int pointerIndex = ev.findPointerIndex(mActivePointerId);
4605                         if (pointerIndex == -1) {
4606                             pointerIndex = 0;
4607                             mActivePointerId = ev.getPointerId(pointerIndex);
4608                         }
4609                         final int y = (int) ev.getY(pointerIndex);
4610                         initVelocityTrackerIfNotExists();
4611                         mVelocityTracker.addMovement(ev);
4612                         if (startScrollIfNeeded((int) ev.getX(pointerIndex), y, null)) {
4613                             return true;
4614                         }
4615                         break;
4616                 }
4617                 break;
4618             }
4619 
4620             case MotionEvent.ACTION_CANCEL:
4621             case MotionEvent.ACTION_UP: {
4622                 mTouchMode = TOUCH_MODE_REST;
4623                 mActivePointerId = INVALID_POINTER;
4624                 recycleVelocityTracker();
4625                 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4626                 stopNestedScroll();
4627                 break;
4628             }
4629 
4630             case MotionEvent.ACTION_POINTER_UP: {
4631                 onSecondaryPointerUp(ev);
4632                 break;
4633             }
4634         }
4635 
4636         return false;
4637     }
4638 
onSecondaryPointerUp(MotionEvent ev)4639     private void onSecondaryPointerUp(MotionEvent ev) {
4640         final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
4641                 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
4642         final int pointerId = ev.getPointerId(pointerIndex);
4643         if (pointerId == mActivePointerId) {
4644             // This was our active pointer going up. Choose a new
4645             // active pointer and adjust accordingly.
4646             // TODO: Make this decision more intelligent.
4647             final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
4648             mMotionX = (int) ev.getX(newPointerIndex);
4649             mMotionY = (int) ev.getY(newPointerIndex);
4650             mMotionCorrection = 0;
4651             mActivePointerId = ev.getPointerId(newPointerIndex);
4652         }
4653     }
4654 
4655     /**
4656      * {@inheritDoc}
4657      */
4658     @Override
addTouchables(ArrayList<View> views)4659     public void addTouchables(ArrayList<View> views) {
4660         final int count = getChildCount();
4661         final int firstPosition = mFirstPosition;
4662         final ListAdapter adapter = mAdapter;
4663 
4664         if (adapter == null) {
4665             return;
4666         }
4667 
4668         for (int i = 0; i < count; i++) {
4669             final View child = getChildAt(i);
4670             if (adapter.isEnabled(firstPosition + i)) {
4671                 views.add(child);
4672             }
4673             child.addTouchables(views);
4674         }
4675     }
4676 
4677     /**
4678      * Fires an "on scroll state changed" event to the registered
4679      * {@link android.widget.AbsListView.OnScrollListener}, if any. The state change
4680      * is fired only if the specified state is different from the previously known state.
4681      *
4682      * @param newState The new scroll state.
4683      */
4684     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123769710)
reportScrollStateChange(int newState)4685     void reportScrollStateChange(int newState) {
4686         if (newState != mLastScrollState) {
4687             if (mOnScrollListener != null) {
4688                 mLastScrollState = newState;
4689                 mOnScrollListener.onScrollStateChanged(this, newState);
4690             }
4691         }
4692     }
4693 
4694     /**
4695      * Responsible for fling behavior. Use {@link #start(int)} to
4696      * initiate a fling. Each frame of the fling is handled in {@link #run()}.
4697      * A FlingRunnable will keep re-posting itself until the fling is done.
4698      *
4699      */
4700     private class FlingRunnable implements Runnable {
4701         /**
4702          * Tracks the decay of a fling scroll
4703          */
4704         @UnsupportedAppUsage
4705         private final OverScroller mScroller;
4706 
4707         /**
4708          * Y value reported by mScroller on the previous fling
4709          */
4710         private int mLastFlingY;
4711 
4712         /**
4713          * If true, {@link #endFling()} will not report scroll state change to
4714          * {@link OnScrollListener#SCROLL_STATE_IDLE}.
4715          */
4716         private boolean mSuppressIdleStateChangeCall;
4717 
4718         private final Runnable mCheckFlywheel = new Runnable() {
4719             @Override
4720             public void run() {
4721                 final int activeId = mActivePointerId;
4722                 final VelocityTracker vt = mVelocityTracker;
4723                 final OverScroller scroller = mScroller;
4724                 if (vt == null || activeId == INVALID_POINTER) {
4725                     return;
4726                 }
4727 
4728                 vt.computeCurrentVelocity(1000, mMaximumVelocity);
4729                 final float yvel = -vt.getYVelocity(activeId);
4730 
4731                 if (Math.abs(yvel) >= mMinimumVelocity
4732                         && scroller.isScrollingInDirection(0, yvel)) {
4733                     // Keep the fling alive a little longer
4734                     postDelayed(this, FLYWHEEL_TIMEOUT);
4735                 } else {
4736                     endFling();
4737                     mTouchMode = TOUCH_MODE_SCROLL;
4738                     reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
4739                 }
4740             }
4741         };
4742 
4743         private static final int FLYWHEEL_TIMEOUT = 40; // milliseconds
4744 
FlingRunnable()4745         FlingRunnable() {
4746             mScroller = new OverScroller(getContext());
4747         }
4748 
4749         // Use AbsListView#fling(int) instead
4750         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
start(int initialVelocity)4751         void start(int initialVelocity) {
4752             int initialY = initialVelocity < 0 ? Integer.MAX_VALUE : 0;
4753             mLastFlingY = initialY;
4754             mScroller.setInterpolator(null);
4755             mScroller.fling(0, initialY, 0, initialVelocity,
4756                     0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE);
4757             mTouchMode = TOUCH_MODE_FLING;
4758             mSuppressIdleStateChangeCall = false;
4759             removeCallbacks(this);
4760             postOnAnimation(this);
4761 
4762             if (PROFILE_FLINGING) {
4763                 if (!mFlingProfilingStarted) {
4764                     Debug.startMethodTracing("AbsListViewFling");
4765                     mFlingProfilingStarted = true;
4766                 }
4767             }
4768 
4769             if (mFlingStrictSpan == null) {
4770                 mFlingStrictSpan = StrictMode.enterCriticalSpan("AbsListView-fling");
4771             }
4772         }
4773 
4774         void startSpringback() {
4775             mSuppressIdleStateChangeCall = false;
4776             if (mScroller.springBack(0, mScrollY, 0, 0, 0, 0)) {
4777                 mTouchMode = TOUCH_MODE_OVERFLING;
4778                 invalidate();
4779                 postOnAnimation(this);
4780             } else {
4781                 mTouchMode = TOUCH_MODE_REST;
4782                 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4783             }
4784         }
4785 
4786         void startOverfling(int initialVelocity) {
4787             mScroller.setInterpolator(null);
4788             mScroller.fling(0, mScrollY, 0, initialVelocity, 0, 0,
4789                     Integer.MIN_VALUE, Integer.MAX_VALUE, 0, getHeight());
4790             mTouchMode = TOUCH_MODE_OVERFLING;
4791             mSuppressIdleStateChangeCall = false;
4792             invalidate();
4793             postOnAnimation(this);
4794         }
4795 
4796         void edgeReached(int delta) {
4797             mScroller.notifyVerticalEdgeReached(mScrollY, 0, mOverflingDistance);
4798             final int overscrollMode = getOverScrollMode();
4799             if (overscrollMode == OVER_SCROLL_ALWAYS ||
4800                     (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && !contentFits())) {
4801                 mTouchMode = TOUCH_MODE_OVERFLING;
4802                 final int vel = (int) mScroller.getCurrVelocity();
4803                 if (delta > 0) {
4804                     mEdgeGlowTop.onAbsorb(vel);
4805                 } else {
4806                     mEdgeGlowBottom.onAbsorb(vel);
4807                 }
4808             } else {
4809                 mTouchMode = TOUCH_MODE_REST;
4810                 if (mPositionScroller != null) {
4811                     mPositionScroller.stop();
4812                 }
4813             }
4814             invalidate();
4815             postOnAnimation(this);
4816         }
4817 
startScroll(int distance, int duration, boolean linear, boolean suppressEndFlingStateChangeCall)4818         void startScroll(int distance, int duration, boolean linear,
4819                 boolean suppressEndFlingStateChangeCall) {
4820             int initialY = distance < 0 ? Integer.MAX_VALUE : 0;
4821             mLastFlingY = initialY;
4822             mScroller.setInterpolator(linear ? sLinearInterpolator : null);
4823             mScroller.startScroll(0, initialY, 0, distance, duration);
4824             mTouchMode = TOUCH_MODE_FLING;
4825             mSuppressIdleStateChangeCall = suppressEndFlingStateChangeCall;
4826             postOnAnimation(this);
4827         }
4828 
4829         // To interrupt a fling early you should use smoothScrollBy(0,0) instead
4830         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
4831         void endFling() {
4832             mTouchMode = TOUCH_MODE_REST;
4833 
4834             removeCallbacks(this);
4835             removeCallbacks(mCheckFlywheel);
4836 
4837             if (!mSuppressIdleStateChangeCall) {
4838                 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4839             }
4840             clearScrollingCache();
4841             mScroller.abortAnimation();
4842 
4843             if (mFlingStrictSpan != null) {
4844                 mFlingStrictSpan.finish();
4845                 mFlingStrictSpan = null;
4846             }
4847         }
4848 
4849         void flywheelTouch() {
4850             postDelayed(mCheckFlywheel, FLYWHEEL_TIMEOUT);
4851         }
4852 
4853         @Override
4854         public void run() {
4855             switch (mTouchMode) {
4856             default:
4857                 endFling();
4858                 return;
4859 
4860             case TOUCH_MODE_SCROLL:
4861                 if (mScroller.isFinished()) {
4862                     return;
4863                 }
4864                 // Fall through
4865             case TOUCH_MODE_FLING: {
4866                 if (mDataChanged) {
4867                     layoutChildren();
4868                 }
4869 
4870                 if (mItemCount == 0 || getChildCount() == 0) {
4871                     endFling();
4872                     return;
4873                 }
4874 
4875                 final OverScroller scroller = mScroller;
4876                 boolean more = scroller.computeScrollOffset();
4877                 final int y = scroller.getCurrY();
4878 
4879                 // Flip sign to convert finger direction to list items direction
4880                 // (e.g. finger moving down means list is moving towards the top)
4881                 int delta = mLastFlingY - y;
4882 
4883                 // Pretend that each frame of a fling scroll is a touch scroll
4884                 if (delta > 0) {
4885                     // List is moving towards the top. Use first view as mMotionPosition
4886                     mMotionPosition = mFirstPosition;
4887                     final View firstView = getChildAt(0);
4888                     mMotionViewOriginalTop = firstView.getTop();
4889 
4890                     // Don't fling more than 1 screen
4891                     delta = Math.min(getHeight() - mPaddingBottom - mPaddingTop - 1, delta);
4892                 } else {
4893                     // List is moving towards the bottom. Use last view as mMotionPosition
4894                     int offsetToLast = getChildCount() - 1;
4895                     mMotionPosition = mFirstPosition + offsetToLast;
4896 
4897                     final View lastView = getChildAt(offsetToLast);
4898                     mMotionViewOriginalTop = lastView.getTop();
4899 
4900                     // Don't fling more than 1 screen
4901                     delta = Math.max(-(getHeight() - mPaddingBottom - mPaddingTop - 1), delta);
4902                 }
4903 
4904                 // Check to see if we have bumped into the scroll limit
4905                 View motionView = getChildAt(mMotionPosition - mFirstPosition);
4906                 int oldTop = 0;
4907                 if (motionView != null) {
4908                     oldTop = motionView.getTop();
4909                 }
4910 
4911                 // Don't stop just because delta is zero (it could have been rounded)
4912                 final boolean atEdge = trackMotionScroll(delta, delta);
4913                 final boolean atEnd = atEdge && (delta != 0);
4914                 if (atEnd) {
4915                     if (motionView != null) {
4916                         // Tweak the scroll for how far we overshot
4917                         int overshoot = -(delta - (motionView.getTop() - oldTop));
4918                         overScrollBy(0, overshoot, 0, mScrollY, 0, 0,
4919                                 0, mOverflingDistance, false);
4920                     }
4921                     if (more) {
4922                         edgeReached(delta);
4923                     }
4924                     break;
4925                 }
4926 
4927                 if (more && !atEnd) {
4928                     if (atEdge) invalidate();
4929                     mLastFlingY = y;
4930                     postOnAnimation(this);
4931                 } else {
4932                     endFling();
4933 
4934                     if (PROFILE_FLINGING) {
4935                         if (mFlingProfilingStarted) {
4936                             Debug.stopMethodTracing();
4937                             mFlingProfilingStarted = false;
4938                         }
4939 
4940                         if (mFlingStrictSpan != null) {
4941                             mFlingStrictSpan.finish();
4942                             mFlingStrictSpan = null;
4943                         }
4944                     }
4945                 }
4946                 break;
4947             }
4948 
4949             case TOUCH_MODE_OVERFLING: {
4950                 final OverScroller scroller = mScroller;
4951                 if (scroller.computeScrollOffset()) {
4952                     final int scrollY = mScrollY;
4953                     final int currY = scroller.getCurrY();
4954                     final int deltaY = currY - scrollY;
4955                     if (overScrollBy(0, deltaY, 0, scrollY, 0, 0,
4956                             0, mOverflingDistance, false)) {
4957                         final boolean crossDown = scrollY <= 0 && currY > 0;
4958                         final boolean crossUp = scrollY >= 0 && currY < 0;
4959                         if (crossDown || crossUp) {
4960                             int velocity = (int) scroller.getCurrVelocity();
4961                             if (crossUp) velocity = -velocity;
4962 
4963                             // Don't flywheel from this; we're just continuing things.
4964                             scroller.abortAnimation();
4965                             start(velocity);
4966                         } else {
4967                             startSpringback();
4968                         }
4969                     } else {
4970                         invalidate();
4971                         postOnAnimation(this);
4972                     }
4973                 } else {
4974                     endFling();
4975                 }
4976                 break;
4977             }
4978             }
4979         }
4980     }
4981 
4982     /**
4983      * The amount of friction applied to flings. The default value
4984      * is {@link ViewConfiguration#getScrollFriction}.
4985      */
4986     public void setFriction(float friction) {
4987         if (mFlingRunnable == null) {
4988             mFlingRunnable = new FlingRunnable();
4989         }
4990         mFlingRunnable.mScroller.setFriction(friction);
4991     }
4992 
4993     /**
4994      * Sets a scale factor for the fling velocity. The initial scale
4995      * factor is 1.0.
4996      *
4997      * @param scale The scale factor to multiply the velocity by.
4998      */
4999     public void setVelocityScale(float scale) {
5000         mVelocityScale = scale;
5001     }
5002 
5003     /**
5004      * Override this for better control over position scrolling.
5005      */
5006     AbsPositionScroller createPositionScroller() {
5007         return new PositionScroller();
5008     }
5009 
5010     /**
5011      * Smoothly scroll to the specified adapter position. The view will
5012      * scroll such that the indicated position is displayed.
5013      * @param position Scroll to this adapter position.
5014      */
5015     public void smoothScrollToPosition(int position) {
5016         if (mPositionScroller == null) {
5017             mPositionScroller = createPositionScroller();
5018         }
5019         mPositionScroller.start(position);
5020     }
5021 
5022     /**
5023      * Smoothly scroll to the specified adapter position. The view will scroll
5024      * such that the indicated position is displayed <code>offset</code> pixels below
5025      * the top edge of the view. If this is impossible, (e.g. the offset would scroll
5026      * the first or last item beyond the boundaries of the list) it will get as close
5027      * as possible. The scroll will take <code>duration</code> milliseconds to complete.
5028      *
5029      * @param position Position to scroll to
5030      * @param offset Desired distance in pixels of <code>position</code> from the top
5031      *               of the view when scrolling is finished
5032      * @param duration Number of milliseconds to use for the scroll
5033      */
5034     public void smoothScrollToPositionFromTop(int position, int offset, int duration) {
5035         if (mPositionScroller == null) {
5036             mPositionScroller = createPositionScroller();
5037         }
5038         mPositionScroller.startWithOffset(position, offset, duration);
5039     }
5040 
5041     /**
5042      * Smoothly scroll to the specified adapter position. The view will scroll
5043      * such that the indicated position is displayed <code>offset</code> pixels below
5044      * the top edge of the view. If this is impossible, (e.g. the offset would scroll
5045      * the first or last item beyond the boundaries of the list) it will get as close
5046      * as possible.
5047      *
5048      * @param position Position to scroll to
5049      * @param offset Desired distance in pixels of <code>position</code> from the top
5050      *               of the view when scrolling is finished
5051      */
5052     public void smoothScrollToPositionFromTop(int position, int offset) {
5053         if (mPositionScroller == null) {
5054             mPositionScroller = createPositionScroller();
5055         }
5056         mPositionScroller.startWithOffset(position, offset);
5057     }
5058 
5059     /**
5060      * Smoothly scroll to the specified adapter position. The view will
5061      * scroll such that the indicated position is displayed, but it will
5062      * stop early if scrolling further would scroll boundPosition out of
5063      * view.
5064      *
5065      * @param position Scroll to this adapter position.
5066      * @param boundPosition Do not scroll if it would move this adapter
5067      *          position out of view.
5068      */
5069     public void smoothScrollToPosition(int position, int boundPosition) {
5070         if (mPositionScroller == null) {
5071             mPositionScroller = createPositionScroller();
5072         }
5073         mPositionScroller.start(position, boundPosition);
5074     }
5075 
5076     /**
5077      * Smoothly scroll by distance pixels over duration milliseconds.
5078      * @param distance Distance to scroll in pixels.
5079      * @param duration Duration of the scroll animation in milliseconds.
5080      */
5081     public void smoothScrollBy(int distance, int duration) {
5082         smoothScrollBy(distance, duration, false, false);
5083     }
5084 
5085     @UnsupportedAppUsage
5086     void smoothScrollBy(int distance, int duration, boolean linear,
5087             boolean suppressEndFlingStateChangeCall) {
5088         if (mFlingRunnable == null) {
5089             mFlingRunnable = new FlingRunnable();
5090         }
5091 
5092         // No sense starting to scroll if we're not going anywhere
5093         final int firstPos = mFirstPosition;
5094         final int childCount = getChildCount();
5095         final int lastPos = firstPos + childCount;
5096         final int topLimit = getPaddingTop();
5097         final int bottomLimit = getHeight() - getPaddingBottom();
5098 
5099         if (distance == 0 || mItemCount == 0 || childCount == 0 ||
5100                 (firstPos == 0 && getChildAt(0).getTop() == topLimit && distance < 0) ||
5101                 (lastPos == mItemCount &&
5102                         getChildAt(childCount - 1).getBottom() == bottomLimit && distance > 0)) {
5103             mFlingRunnable.endFling();
5104             if (mPositionScroller != null) {
5105                 mPositionScroller.stop();
5106             }
5107         } else {
5108             reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
5109             mFlingRunnable.startScroll(distance, duration, linear, suppressEndFlingStateChangeCall);
5110         }
5111     }
5112 
5113     /**
5114      * Allows RemoteViews to scroll relatively to a position.
5115      */
5116     void smoothScrollByOffset(int position) {
5117         int index = -1;
5118         if (position < 0) {
5119             index = getFirstVisiblePosition();
5120         } else if (position > 0) {
5121             index = getLastVisiblePosition();
5122         }
5123 
5124         if (index > -1) {
5125             View child = getChildAt(index - getFirstVisiblePosition());
5126             if (child != null) {
5127                 Rect visibleRect = new Rect();
5128                 if (child.getGlobalVisibleRect(visibleRect)) {
5129                     // the child is partially visible
5130                     int childRectArea = child.getWidth() * child.getHeight();
5131                     int visibleRectArea = visibleRect.width() * visibleRect.height();
5132                     float visibleArea = (visibleRectArea / (float) childRectArea);
5133                     final float visibleThreshold = 0.75f;
5134                     if ((position < 0) && (visibleArea < visibleThreshold)) {
5135                         // the top index is not perceivably visible so offset
5136                         // to account for showing that top index as well
5137                         ++index;
5138                     } else if ((position > 0) && (visibleArea < visibleThreshold)) {
5139                         // the bottom index is not perceivably visible so offset
5140                         // to account for showing that bottom index as well
5141                         --index;
5142                     }
5143                 }
5144                 smoothScrollToPosition(Math.max(0, Math.min(getCount(), index + position)));
5145             }
5146         }
5147     }
5148 
5149     private void createScrollingCache() {
5150         if (mScrollingCacheEnabled && !mCachingStarted && !isHardwareAccelerated()) {
5151             setChildrenDrawnWithCacheEnabled(true);
5152             setChildrenDrawingCacheEnabled(true);
5153             mCachingStarted = mCachingActive = true;
5154         }
5155     }
5156 
5157     private void clearScrollingCache() {
5158         if (!isHardwareAccelerated()) {
5159             if (mClearScrollingCache == null) {
5160                 mClearScrollingCache = new Runnable() {
5161                     @Override
5162                     public void run() {
5163                         if (mCachingStarted) {
5164                             mCachingStarted = mCachingActive = false;
5165                             setChildrenDrawnWithCacheEnabled(false);
5166                             if ((mPersistentDrawingCache & PERSISTENT_SCROLLING_CACHE) == 0) {
5167                                 setChildrenDrawingCacheEnabled(false);
5168                             }
5169                             if (!isAlwaysDrawnWithCacheEnabled()) {
5170                                 invalidate();
5171                             }
5172                         }
5173                     }
5174                 };
5175             }
5176             post(mClearScrollingCache);
5177         }
5178     }
5179 
5180     /**
5181      * Scrolls the list items within the view by a specified number of pixels.
5182      *
5183      * <p>The actual amount of scroll is capped by the list content viewport height
5184      * which is the list height minus top and bottom paddings minus one pixel.</p>
5185      *
5186      * @param y the amount of pixels to scroll by vertically
5187      * @see #canScrollList(int)
5188      */
5189     public void scrollListBy(int y) {
5190         trackMotionScroll(-y, -y);
5191     }
5192 
5193     /**
5194      * Check if the items in the list can be scrolled in a certain direction.
5195      *
5196      * @param direction Negative to check scrolling up, positive to check
5197      *            scrolling down.
5198      * @return true if the list can be scrolled in the specified direction,
5199      *         false otherwise.
5200      * @see #scrollListBy(int)
5201      */
5202     public boolean canScrollList(int direction) {
5203         final int childCount = getChildCount();
5204         if (childCount == 0) {
5205             return false;
5206         }
5207 
5208         final int firstPosition = mFirstPosition;
5209         final Rect listPadding = mListPadding;
5210         if (direction > 0) {
5211             final int lastBottom = getChildAt(childCount - 1).getBottom();
5212             final int lastPosition = firstPosition + childCount;
5213             return lastPosition < mItemCount || lastBottom > getHeight() - listPadding.bottom;
5214         } else {
5215             final int firstTop = getChildAt(0).getTop();
5216             return firstPosition > 0 || firstTop < listPadding.top;
5217         }
5218     }
5219 
5220     /**
5221      * Track a motion scroll
5222      *
5223      * @param deltaY Amount to offset mMotionView. This is the accumulated delta since the motion
5224      *        began. Positive numbers mean the user's finger is moving down the screen.
5225      * @param incrementalDeltaY Change in deltaY from the previous event.
5226      * @return true if we're already at the beginning/end of the list and have nothing to do.
5227      */
5228     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124051739)
5229     boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
5230         final int childCount = getChildCount();
5231         if (childCount == 0) {
5232             return true;
5233         }
5234 
5235         final int firstTop = getChildAt(0).getTop();
5236         final int lastBottom = getChildAt(childCount - 1).getBottom();
5237 
5238         final Rect listPadding = mListPadding;
5239 
5240         // "effective padding" In this case is the amount of padding that affects
5241         // how much space should not be filled by items. If we don't clip to padding
5242         // there is no effective padding.
5243         int effectivePaddingTop = 0;
5244         int effectivePaddingBottom = 0;
5245         if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
5246             effectivePaddingTop = listPadding.top;
5247             effectivePaddingBottom = listPadding.bottom;
5248         }
5249 
5250          // FIXME account for grid vertical spacing too?
5251         final int spaceAbove = effectivePaddingTop - firstTop;
5252         final int end = getHeight() - effectivePaddingBottom;
5253         final int spaceBelow = lastBottom - end;
5254 
5255         final int height = getHeight() - mPaddingBottom - mPaddingTop;
5256         if (deltaY < 0) {
5257             deltaY = Math.max(-(height - 1), deltaY);
5258         } else {
5259             deltaY = Math.min(height - 1, deltaY);
5260         }
5261 
5262         if (incrementalDeltaY < 0) {
5263             incrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY);
5264         } else {
5265             incrementalDeltaY = Math.min(height - 1, incrementalDeltaY);
5266         }
5267 
5268         final int firstPosition = mFirstPosition;
5269 
5270         // Update our guesses for where the first and last views are
5271         if (firstPosition == 0) {
5272             mFirstPositionDistanceGuess = firstTop - listPadding.top;
5273         } else {
5274             mFirstPositionDistanceGuess += incrementalDeltaY;
5275         }
5276         if (firstPosition + childCount == mItemCount) {
5277             mLastPositionDistanceGuess = lastBottom + listPadding.bottom;
5278         } else {
5279             mLastPositionDistanceGuess += incrementalDeltaY;
5280         }
5281 
5282         final boolean cannotScrollDown = (firstPosition == 0 &&
5283                 firstTop >= listPadding.top && incrementalDeltaY >= 0);
5284         final boolean cannotScrollUp = (firstPosition + childCount == mItemCount &&
5285                 lastBottom <= getHeight() - listPadding.bottom && incrementalDeltaY <= 0);
5286 
5287         if (cannotScrollDown || cannotScrollUp) {
5288             return incrementalDeltaY != 0;
5289         }
5290 
5291         final boolean down = incrementalDeltaY < 0;
5292 
5293         final boolean inTouchMode = isInTouchMode();
5294         if (inTouchMode) {
5295             hideSelector();
5296         }
5297 
5298         final int headerViewsCount = getHeaderViewsCount();
5299         final int footerViewsStart = mItemCount - getFooterViewsCount();
5300 
5301         int start = 0;
5302         int count = 0;
5303 
5304         if (down) {
5305             int top = -incrementalDeltaY;
5306             if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
5307                 top += listPadding.top;
5308             }
5309             for (int i = 0; i < childCount; i++) {
5310                 final View child = getChildAt(i);
5311                 if (child.getBottom() >= top) {
5312                     break;
5313                 } else {
5314                     count++;
5315                     int position = firstPosition + i;
5316                     if (position >= headerViewsCount && position < footerViewsStart) {
5317                         // The view will be rebound to new data, clear any
5318                         // system-managed transient state.
5319                         child.clearAccessibilityFocus();
5320                         mRecycler.addScrapView(child, position);
5321                     }
5322                 }
5323             }
5324         } else {
5325             int bottom = getHeight() - incrementalDeltaY;
5326             if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
5327                 bottom -= listPadding.bottom;
5328             }
5329             for (int i = childCount - 1; i >= 0; i--) {
5330                 final View child = getChildAt(i);
5331                 if (child.getTop() <= bottom) {
5332                     break;
5333                 } else {
5334                     start = i;
5335                     count++;
5336                     int position = firstPosition + i;
5337                     if (position >= headerViewsCount && position < footerViewsStart) {
5338                         // The view will be rebound to new data, clear any
5339                         // system-managed transient state.
5340                         child.clearAccessibilityFocus();
5341                         mRecycler.addScrapView(child, position);
5342                     }
5343                 }
5344             }
5345         }
5346 
5347         mMotionViewNewTop = mMotionViewOriginalTop + deltaY;
5348 
5349         mBlockLayoutRequests = true;
5350 
5351         if (count > 0) {
5352             detachViewsFromParent(start, count);
5353             mRecycler.removeSkippedScrap();
5354         }
5355 
5356         // invalidate before moving the children to avoid unnecessary invalidate
5357         // calls to bubble up from the children all the way to the top
5358         if (!awakenScrollBars()) {
5359            invalidate();
5360         }
5361 
5362         offsetChildrenTopAndBottom(incrementalDeltaY);
5363 
5364         if (down) {
5365             mFirstPosition += count;
5366         }
5367 
5368         final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
5369         if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
5370             fillGap(down);
5371         }
5372 
5373         mRecycler.fullyDetachScrapViews();
5374         boolean selectorOnScreen = false;
5375         if (!inTouchMode && mSelectedPosition != INVALID_POSITION) {
5376             final int childIndex = mSelectedPosition - mFirstPosition;
5377             if (childIndex >= 0 && childIndex < getChildCount()) {
5378                 positionSelector(mSelectedPosition, getChildAt(childIndex));
5379                 selectorOnScreen = true;
5380             }
5381         } else if (mSelectorPosition != INVALID_POSITION) {
5382             final int childIndex = mSelectorPosition - mFirstPosition;
5383             if (childIndex >= 0 && childIndex < getChildCount()) {
5384                 positionSelector(mSelectorPosition, getChildAt(childIndex));
5385                 selectorOnScreen = true;
5386             }
5387         }
5388         if (!selectorOnScreen) {
5389             mSelectorRect.setEmpty();
5390         }
5391 
5392         mBlockLayoutRequests = false;
5393 
5394         invokeOnItemScrollListener();
5395 
5396         return false;
5397     }
5398 
5399     /**
5400      * Returns the number of header views in the list. Header views are special views
5401      * at the top of the list that should not be recycled during a layout.
5402      *
5403      * @return The number of header views, 0 in the default implementation.
5404      */
5405     int getHeaderViewsCount() {
5406         return 0;
5407     }
5408 
5409     /**
5410      * Returns the number of footer views in the list. Footer views are special views
5411      * at the bottom of the list that should not be recycled during a layout.
5412      *
5413      * @return The number of footer views, 0 in the default implementation.
5414      */
5415     int getFooterViewsCount() {
5416         return 0;
5417     }
5418 
5419     /**
5420      * Fills the gap left open by a touch-scroll. During a touch scroll, children that
5421      * remain on screen are shifted and the other ones are discarded. The role of this
5422      * method is to fill the gap thus created by performing a partial layout in the
5423      * empty space.
5424      *
5425      * @param down true if the scroll is going down, false if it is going up
5426      */
5427     @SuppressWarnings("HiddenAbstractMethod")
5428     abstract void fillGap(boolean down);
5429 
hideSelector()5430     void hideSelector() {
5431         if (mSelectedPosition != INVALID_POSITION) {
5432             if (mLayoutMode != LAYOUT_SPECIFIC) {
5433                 mResurrectToPosition = mSelectedPosition;
5434             }
5435             if (mNextSelectedPosition >= 0 && mNextSelectedPosition != mSelectedPosition) {
5436                 mResurrectToPosition = mNextSelectedPosition;
5437             }
5438             setSelectedPositionInt(INVALID_POSITION);
5439             setNextSelectedPositionInt(INVALID_POSITION);
5440             mSelectedTop = 0;
5441         }
5442     }
5443 
5444     /**
5445      * @return A position to select. First we try mSelectedPosition. If that has been clobbered by
5446      * entering touch mode, we then try mResurrectToPosition. Values are pinned to the range
5447      * of items available in the adapter
5448      */
reconcileSelectedPosition()5449     int reconcileSelectedPosition() {
5450         int position = mSelectedPosition;
5451         if (position < 0) {
5452             position = mResurrectToPosition;
5453         }
5454         position = Math.max(0, position);
5455         position = Math.min(position, mItemCount - 1);
5456         return position;
5457     }
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 containing y
5464      */
5465     @SuppressWarnings("HiddenAbstractMethod")
5466     @UnsupportedAppUsage
5467     abstract int findMotionRow(int y);
5468 
5469     /**
5470      * Find the row closest to y. This row will be used as the motion row when scrolling.
5471      *
5472      * @param y Where the user touched
5473      * @return The position of the first (or only) item in the row closest to y
5474      */
findClosestMotionRow(int y)5475     int findClosestMotionRow(int y) {
5476         final int childCount = getChildCount();
5477         if (childCount == 0) {
5478             return INVALID_POSITION;
5479         }
5480 
5481         final int motionRow = findMotionRow(y);
5482         return motionRow != INVALID_POSITION ? motionRow : mFirstPosition + childCount - 1;
5483     }
5484 
5485     /**
5486      * Causes all the views to be rebuilt and redrawn.
5487      */
invalidateViews()5488     public void invalidateViews() {
5489         mDataChanged = true;
5490         rememberSyncState();
5491         requestLayout();
5492         invalidate();
5493     }
5494 
5495     /**
5496      * If there is a selection returns false.
5497      * Otherwise resurrects the selection and returns true if resurrected.
5498      */
5499     @UnsupportedAppUsage
resurrectSelectionIfNeeded()5500     boolean resurrectSelectionIfNeeded() {
5501         if (mSelectedPosition < 0 && resurrectSelection()) {
5502             updateSelectorState();
5503             return true;
5504         }
5505         return false;
5506     }
5507 
5508     /**
5509      * Makes the item at the supplied position selected.
5510      *
5511      * @param position the position of the new selection
5512      */
5513     @SuppressWarnings("HiddenAbstractMethod")
5514     abstract void setSelectionInt(int position);
5515 
5516     /**
5517      * Attempt to bring the selection back if the user is switching from touch
5518      * to trackball mode
5519      * @return Whether selection was set to something.
5520      */
resurrectSelection()5521     boolean resurrectSelection() {
5522         final int childCount = getChildCount();
5523 
5524         if (childCount <= 0) {
5525             return false;
5526         }
5527 
5528         int selectedTop = 0;
5529         int selectedPos;
5530         int childrenTop = mListPadding.top;
5531         int childrenBottom = mBottom - mTop - mListPadding.bottom;
5532         final int firstPosition = mFirstPosition;
5533         final int toPosition = mResurrectToPosition;
5534         boolean down = true;
5535 
5536         if (toPosition >= firstPosition && toPosition < firstPosition + childCount) {
5537             selectedPos = toPosition;
5538 
5539             final View selected = getChildAt(selectedPos - mFirstPosition);
5540             selectedTop = selected.getTop();
5541             int selectedBottom = selected.getBottom();
5542 
5543             // We are scrolled, don't get in the fade
5544             if (selectedTop < childrenTop) {
5545                 selectedTop = childrenTop + getVerticalFadingEdgeLength();
5546             } else if (selectedBottom > childrenBottom) {
5547                 selectedTop = childrenBottom - selected.getMeasuredHeight()
5548                         - getVerticalFadingEdgeLength();
5549             }
5550         } else {
5551             if (toPosition < firstPosition) {
5552                 // Default to selecting whatever is first
5553                 selectedPos = firstPosition;
5554                 for (int i = 0; i < childCount; i++) {
5555                     final View v = getChildAt(i);
5556                     final int top = v.getTop();
5557 
5558                     if (i == 0) {
5559                         // Remember the position of the first item
5560                         selectedTop = top;
5561                         // See if we are scrolled at all
5562                         if (firstPosition > 0 || top < childrenTop) {
5563                             // If we are scrolled, don't select anything that is
5564                             // in the fade region
5565                             childrenTop += getVerticalFadingEdgeLength();
5566                         }
5567                     }
5568                     if (top >= childrenTop) {
5569                         // Found a view whose top is fully visisble
5570                         selectedPos = firstPosition + i;
5571                         selectedTop = top;
5572                         break;
5573                     }
5574                 }
5575             } else {
5576                 final int itemCount = mItemCount;
5577                 down = false;
5578                 selectedPos = firstPosition + childCount - 1;
5579 
5580                 for (int i = childCount - 1; i >= 0; i--) {
5581                     final View v = getChildAt(i);
5582                     final int top = v.getTop();
5583                     final int bottom = v.getBottom();
5584 
5585                     if (i == childCount - 1) {
5586                         selectedTop = top;
5587                         if (firstPosition + childCount < itemCount || bottom > childrenBottom) {
5588                             childrenBottom -= getVerticalFadingEdgeLength();
5589                         }
5590                     }
5591 
5592                     if (bottom <= childrenBottom) {
5593                         selectedPos = firstPosition + i;
5594                         selectedTop = top;
5595                         break;
5596                     }
5597                 }
5598             }
5599         }
5600 
5601         mResurrectToPosition = INVALID_POSITION;
5602         removeCallbacks(mFlingRunnable);
5603         if (mPositionScroller != null) {
5604             mPositionScroller.stop();
5605         }
5606         mTouchMode = TOUCH_MODE_REST;
5607         clearScrollingCache();
5608         mSpecificTop = selectedTop;
5609         selectedPos = lookForSelectablePosition(selectedPos, down);
5610         if (selectedPos >= firstPosition && selectedPos <= getLastVisiblePosition()) {
5611             mLayoutMode = LAYOUT_SPECIFIC;
5612             updateSelectorState();
5613             setSelectionInt(selectedPos);
5614             invokeOnItemScrollListener();
5615         } else {
5616             selectedPos = INVALID_POSITION;
5617         }
5618         reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
5619 
5620         return selectedPos >= 0;
5621     }
5622 
confirmCheckedPositionsById()5623     void confirmCheckedPositionsById() {
5624         // Clear out the positional check states, we'll rebuild it below from IDs.
5625         mCheckStates.clear();
5626 
5627         boolean checkedCountChanged = false;
5628         for (int checkedIndex = 0; checkedIndex < mCheckedIdStates.size(); checkedIndex++) {
5629             final long id = mCheckedIdStates.keyAt(checkedIndex);
5630             final int lastPos = mCheckedIdStates.valueAt(checkedIndex);
5631 
5632             final long lastPosId = mAdapter.getItemId(lastPos);
5633             if (id != lastPosId) {
5634                 // Look around to see if the ID is nearby. If not, uncheck it.
5635                 final int start = Math.max(0, lastPos - CHECK_POSITION_SEARCH_DISTANCE);
5636                 final int end = Math.min(lastPos + CHECK_POSITION_SEARCH_DISTANCE, mItemCount);
5637                 boolean found = false;
5638                 for (int searchPos = start; searchPos < end; searchPos++) {
5639                     final long searchId = mAdapter.getItemId(searchPos);
5640                     if (id == searchId) {
5641                         found = true;
5642                         mCheckStates.put(searchPos, true);
5643                         mCheckedIdStates.setValueAt(checkedIndex, searchPos);
5644                         break;
5645                     }
5646                 }
5647 
5648                 if (!found) {
5649                     mCheckedIdStates.delete(id);
5650                     checkedIndex--;
5651                     mCheckedItemCount--;
5652                     checkedCountChanged = true;
5653                     if (mChoiceActionMode != null && mMultiChoiceModeCallback != null) {
5654                         mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
5655                                 lastPos, id, false);
5656                     }
5657                 }
5658             } else {
5659                 mCheckStates.put(lastPos, true);
5660             }
5661         }
5662 
5663         if (checkedCountChanged && mChoiceActionMode != null) {
5664             mChoiceActionMode.invalidate();
5665         }
5666     }
5667 
5668     @Override
handleDataChanged()5669     protected void handleDataChanged() {
5670         int count = mItemCount;
5671         int lastHandledItemCount = mLastHandledItemCount;
5672         mLastHandledItemCount = mItemCount;
5673 
5674         if (mChoiceMode != CHOICE_MODE_NONE && mAdapter != null && mAdapter.hasStableIds()) {
5675             confirmCheckedPositionsById();
5676         }
5677 
5678         // TODO: In the future we can recycle these views based on stable ID instead.
5679         mRecycler.clearTransientStateViews();
5680 
5681         if (count > 0) {
5682             int newPos;
5683             int selectablePos;
5684 
5685             // Find the row we are supposed to sync to
5686             if (mNeedSync) {
5687                 // Update this first, since setNextSelectedPositionInt inspects it
5688                 mNeedSync = false;
5689                 mPendingSync = null;
5690 
5691                 if (mTranscriptMode == TRANSCRIPT_MODE_ALWAYS_SCROLL) {
5692                     mLayoutMode = LAYOUT_FORCE_BOTTOM;
5693                     return;
5694                 } else if (mTranscriptMode == TRANSCRIPT_MODE_NORMAL) {
5695                     if (mForceTranscriptScroll) {
5696                         mForceTranscriptScroll = false;
5697                         mLayoutMode = LAYOUT_FORCE_BOTTOM;
5698                         return;
5699                     }
5700                     final int childCount = getChildCount();
5701                     final int listBottom = getHeight() - getPaddingBottom();
5702                     final View lastChild = getChildAt(childCount - 1);
5703                     final int lastBottom = lastChild != null ? lastChild.getBottom() : listBottom;
5704                     if (mFirstPosition + childCount >= lastHandledItemCount &&
5705                             lastBottom <= listBottom) {
5706                         mLayoutMode = LAYOUT_FORCE_BOTTOM;
5707                         return;
5708                     }
5709                     // Something new came in and we didn't scroll; give the user a clue that
5710                     // there's something new.
5711                     awakenScrollBars();
5712                 }
5713 
5714                 switch (mSyncMode) {
5715                 case SYNC_SELECTED_POSITION:
5716                     if (isInTouchMode()) {
5717                         // We saved our state when not in touch mode. (We know this because
5718                         // mSyncMode is SYNC_SELECTED_POSITION.) Now we are trying to
5719                         // restore in touch mode. Just leave mSyncPosition as it is (possibly
5720                         // adjusting if the available range changed) and return.
5721                         mLayoutMode = LAYOUT_SYNC;
5722                         mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
5723 
5724                         return;
5725                     } else {
5726                         // See if we can find a position in the new data with the same
5727                         // id as the old selection. This will change mSyncPosition.
5728                         newPos = findSyncPosition();
5729                         if (newPos >= 0) {
5730                             // Found it. Now verify that new selection is still selectable
5731                             selectablePos = lookForSelectablePosition(newPos, true);
5732                             if (selectablePos == newPos) {
5733                                 // Same row id is selected
5734                                 mSyncPosition = newPos;
5735 
5736                                 if (mSyncHeight == getHeight()) {
5737                                     // If we are at the same height as when we saved state, try
5738                                     // to restore the scroll position too.
5739                                     mLayoutMode = LAYOUT_SYNC;
5740                                 } else {
5741                                     // We are not the same height as when the selection was saved, so
5742                                     // don't try to restore the exact position
5743                                     mLayoutMode = LAYOUT_SET_SELECTION;
5744                                 }
5745 
5746                                 // Restore selection
5747                                 setNextSelectedPositionInt(newPos);
5748                                 return;
5749                             }
5750                         }
5751                     }
5752                     break;
5753                 case SYNC_FIRST_POSITION:
5754                     // Leave mSyncPosition as it is -- just pin to available range
5755                     mLayoutMode = LAYOUT_SYNC;
5756                     mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
5757 
5758                     return;
5759                 }
5760             }
5761 
5762             if (!isInTouchMode()) {
5763                 // We couldn't find matching data -- try to use the same position
5764                 newPos = getSelectedItemPosition();
5765 
5766                 // Pin position to the available range
5767                 if (newPos >= count) {
5768                     newPos = count - 1;
5769                 }
5770                 if (newPos < 0) {
5771                     newPos = 0;
5772                 }
5773 
5774                 // Make sure we select something selectable -- first look down
5775                 selectablePos = lookForSelectablePosition(newPos, true);
5776 
5777                 if (selectablePos >= 0) {
5778                     setNextSelectedPositionInt(selectablePos);
5779                     return;
5780                 } else {
5781                     // Looking down didn't work -- try looking up
5782                     selectablePos = lookForSelectablePosition(newPos, false);
5783                     if (selectablePos >= 0) {
5784                         setNextSelectedPositionInt(selectablePos);
5785                         return;
5786                     }
5787                 }
5788             } else {
5789 
5790                 // We already know where we want to resurrect the selection
5791                 if (mResurrectToPosition >= 0) {
5792                     return;
5793                 }
5794             }
5795 
5796         }
5797 
5798         // Nothing is selected. Give up and reset everything.
5799         mLayoutMode = mStackFromBottom ? LAYOUT_FORCE_BOTTOM : LAYOUT_FORCE_TOP;
5800         mSelectedPosition = INVALID_POSITION;
5801         mSelectedRowId = INVALID_ROW_ID;
5802         mNextSelectedPosition = INVALID_POSITION;
5803         mNextSelectedRowId = INVALID_ROW_ID;
5804         mNeedSync = false;
5805         mPendingSync = null;
5806         mSelectorPosition = INVALID_POSITION;
5807         checkSelectionChanged();
5808     }
5809 
5810     @Override
onDisplayHint(int hint)5811     protected void onDisplayHint(int hint) {
5812         super.onDisplayHint(hint);
5813         switch (hint) {
5814             case INVISIBLE:
5815                 if (mPopup != null && mPopup.isShowing()) {
5816                     dismissPopup();
5817                 }
5818                 break;
5819             case VISIBLE:
5820                 if (mFiltered && mPopup != null && !mPopup.isShowing()) {
5821                     showPopup();
5822                 }
5823                 break;
5824         }
5825         mPopupHidden = hint == INVISIBLE;
5826     }
5827 
5828     /**
5829      * Removes the filter window
5830      */
dismissPopup()5831     private void dismissPopup() {
5832         if (mPopup != null) {
5833             mPopup.dismiss();
5834         }
5835     }
5836 
5837     /**
5838      * Shows the filter window
5839      */
showPopup()5840     private void showPopup() {
5841         // Make sure we have a window before showing the popup
5842         if (getWindowVisibility() == View.VISIBLE) {
5843             createTextFilter(true);
5844             positionPopup();
5845             // Make sure we get focus if we are showing the popup
5846             checkFocus();
5847         }
5848     }
5849 
positionPopup()5850     private void positionPopup() {
5851         int screenHeight = getResources().getDisplayMetrics().heightPixels;
5852         final int[] xy = new int[2];
5853         getLocationOnScreen(xy);
5854         // TODO: The 20 below should come from the theme
5855         // TODO: And the gravity should be defined in the theme as well
5856         final int bottomGap = screenHeight - xy[1] - getHeight() + (int) (mDensityScale * 20);
5857         if (!mPopup.isShowing()) {
5858             mPopup.showAtLocation(this, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL,
5859                     xy[0], bottomGap);
5860         } else {
5861             mPopup.update(xy[0], bottomGap, -1, -1);
5862         }
5863     }
5864 
5865     /**
5866      * What is the distance between the source and destination rectangles given the direction of
5867      * focus navigation between them? The direction basically helps figure out more quickly what is
5868      * self evident by the relationship between the rects...
5869      *
5870      * @param source the source rectangle
5871      * @param dest the destination rectangle
5872      * @param direction the direction
5873      * @return the distance between the rectangles
5874      */
getDistance(Rect source, Rect dest, int direction)5875     static int getDistance(Rect source, Rect dest, int direction) {
5876         int sX, sY; // source x, y
5877         int dX, dY; // dest x, y
5878         switch (direction) {
5879         case View.FOCUS_RIGHT:
5880             sX = source.right;
5881             sY = source.top + source.height() / 2;
5882             dX = dest.left;
5883             dY = dest.top + dest.height() / 2;
5884             break;
5885         case View.FOCUS_DOWN:
5886             sX = source.left + source.width() / 2;
5887             sY = source.bottom;
5888             dX = dest.left + dest.width() / 2;
5889             dY = dest.top;
5890             break;
5891         case View.FOCUS_LEFT:
5892             sX = source.left;
5893             sY = source.top + source.height() / 2;
5894             dX = dest.right;
5895             dY = dest.top + dest.height() / 2;
5896             break;
5897         case View.FOCUS_UP:
5898             sX = source.left + source.width() / 2;
5899             sY = source.top;
5900             dX = dest.left + dest.width() / 2;
5901             dY = dest.bottom;
5902             break;
5903         case View.FOCUS_FORWARD:
5904         case View.FOCUS_BACKWARD:
5905             sX = source.right + source.width() / 2;
5906             sY = source.top + source.height() / 2;
5907             dX = dest.left + dest.width() / 2;
5908             dY = dest.top + dest.height() / 2;
5909             break;
5910         default:
5911             throw new IllegalArgumentException("direction must be one of "
5912                     + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, "
5913                     + "FOCUS_FORWARD, FOCUS_BACKWARD}.");
5914         }
5915         int deltaX = dX - sX;
5916         int deltaY = dY - sY;
5917         return deltaY * deltaY + deltaX * deltaX;
5918     }
5919 
5920     @Override
isInFilterMode()5921     protected boolean isInFilterMode() {
5922         return mFiltered;
5923     }
5924 
5925     /**
5926      * Sends a key to the text filter window
5927      *
5928      * @param keyCode The keycode for the event
5929      * @param event The actual key event
5930      *
5931      * @return True if the text filter handled the event, false otherwise.
5932      */
sendToTextFilter(int keyCode, int count, KeyEvent event)5933     boolean sendToTextFilter(int keyCode, int count, KeyEvent event) {
5934         if (!acceptFilter()) {
5935             return false;
5936         }
5937 
5938         boolean handled = false;
5939         boolean okToSend = true;
5940         switch (keyCode) {
5941         case KeyEvent.KEYCODE_DPAD_UP:
5942         case KeyEvent.KEYCODE_DPAD_DOWN:
5943         case KeyEvent.KEYCODE_DPAD_LEFT:
5944         case KeyEvent.KEYCODE_DPAD_RIGHT:
5945         case KeyEvent.KEYCODE_DPAD_CENTER:
5946         case KeyEvent.KEYCODE_ENTER:
5947         case KeyEvent.KEYCODE_NUMPAD_ENTER:
5948             okToSend = false;
5949             break;
5950         case KeyEvent.KEYCODE_BACK:
5951             if (mFiltered && mPopup != null && mPopup.isShowing()) {
5952                 if (event.getAction() == KeyEvent.ACTION_DOWN
5953                         && event.getRepeatCount() == 0) {
5954                     KeyEvent.DispatcherState state = getKeyDispatcherState();
5955                     if (state != null) {
5956                         state.startTracking(event, this);
5957                     }
5958                     handled = true;
5959                 } else if (event.getAction() == KeyEvent.ACTION_UP
5960                         && event.isTracking() && !event.isCanceled()) {
5961                     handled = true;
5962                     mTextFilter.setText("");
5963                 }
5964             }
5965             okToSend = false;
5966             break;
5967         case KeyEvent.KEYCODE_SPACE:
5968             // Only send spaces once we are filtered
5969             okToSend = mFiltered;
5970             break;
5971         }
5972 
5973         if (okToSend) {
5974             createTextFilter(true);
5975 
5976             KeyEvent forwardEvent = event;
5977             if (forwardEvent.getRepeatCount() > 0) {
5978                 forwardEvent = KeyEvent.changeTimeRepeat(event, event.getEventTime(), 0);
5979             }
5980 
5981             int action = event.getAction();
5982             switch (action) {
5983                 case KeyEvent.ACTION_DOWN:
5984                     handled = mTextFilter.onKeyDown(keyCode, forwardEvent);
5985                     break;
5986 
5987                 case KeyEvent.ACTION_UP:
5988                     handled = mTextFilter.onKeyUp(keyCode, forwardEvent);
5989                     break;
5990 
5991                 case KeyEvent.ACTION_MULTIPLE:
5992                     handled = mTextFilter.onKeyMultiple(keyCode, count, event);
5993                     break;
5994             }
5995         }
5996         return handled;
5997     }
5998 
5999     /**
6000      * Return an InputConnection for editing of the filter text.
6001      */
6002     @Override
onCreateInputConnection(EditorInfo outAttrs)6003     public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
6004         if (isTextFilterEnabled()) {
6005             if (mPublicInputConnection == null) {
6006                 mDefInputConnection = new BaseInputConnection(this, false);
6007                 mPublicInputConnection = new InputConnectionWrapper(outAttrs);
6008             }
6009             outAttrs.inputType = EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_FILTER;
6010             outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE;
6011             return mPublicInputConnection;
6012         }
6013         return null;
6014     }
6015 
6016     private class InputConnectionWrapper implements InputConnection {
6017         private final EditorInfo mOutAttrs;
6018         private InputConnection mTarget;
6019 
InputConnectionWrapper(EditorInfo outAttrs)6020         public InputConnectionWrapper(EditorInfo outAttrs) {
6021             mOutAttrs = outAttrs;
6022         }
6023 
getTarget()6024         private InputConnection getTarget() {
6025             if (mTarget == null) {
6026                 mTarget = getTextFilterInput().onCreateInputConnection(mOutAttrs);
6027             }
6028             return mTarget;
6029         }
6030 
6031         @Override
reportFullscreenMode(boolean enabled)6032         public boolean reportFullscreenMode(boolean enabled) {
6033             // Use our own input connection, since it is
6034             // the "real" one the IME is talking with.
6035             return mDefInputConnection.reportFullscreenMode(enabled);
6036         }
6037 
6038         @Override
performEditorAction(int editorAction)6039         public boolean performEditorAction(int editorAction) {
6040             // The editor is off in its own window; we need to be
6041             // the one that does this.
6042             if (editorAction == EditorInfo.IME_ACTION_DONE) {
6043                 InputMethodManager imm =
6044                         getContext().getSystemService(InputMethodManager.class);
6045                 if (imm != null) {
6046                     imm.hideSoftInputFromWindow(getWindowToken(), 0);
6047                 }
6048                 return true;
6049             }
6050             return false;
6051         }
6052 
6053         @Override
sendKeyEvent(KeyEvent event)6054         public boolean sendKeyEvent(KeyEvent event) {
6055             // Use our own input connection, since the filter
6056             // text view may not be shown in a window so has
6057             // no ViewAncestor to dispatch events with.
6058             return mDefInputConnection.sendKeyEvent(event);
6059         }
6060 
6061         @Override
getTextBeforeCursor(int n, int flags)6062         public CharSequence getTextBeforeCursor(int n, int flags) {
6063             if (mTarget == null) return "";
6064             return mTarget.getTextBeforeCursor(n, flags);
6065         }
6066 
6067         @Override
getTextAfterCursor(int n, int flags)6068         public CharSequence getTextAfterCursor(int n, int flags) {
6069             if (mTarget == null) return "";
6070             return mTarget.getTextAfterCursor(n, flags);
6071         }
6072 
6073         @Override
getSelectedText(int flags)6074         public CharSequence getSelectedText(int flags) {
6075             if (mTarget == null) return "";
6076             return mTarget.getSelectedText(flags);
6077         }
6078 
6079         @Override
getSurroundingText(int beforeLength, int afterLength, int flags)6080         public SurroundingText getSurroundingText(int beforeLength, int afterLength, int flags) {
6081             if (mTarget == null) return null;
6082             return mTarget.getSurroundingText(beforeLength, afterLength, flags);
6083         }
6084 
6085         @Override
getCursorCapsMode(int reqModes)6086         public int getCursorCapsMode(int reqModes) {
6087             if (mTarget == null) return InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
6088             return mTarget.getCursorCapsMode(reqModes);
6089         }
6090 
6091         @Override
getExtractedText(ExtractedTextRequest request, int flags)6092         public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
6093             return getTarget().getExtractedText(request, flags);
6094         }
6095 
6096         @Override
deleteSurroundingText(int beforeLength, int afterLength)6097         public boolean deleteSurroundingText(int beforeLength, int afterLength) {
6098             return getTarget().deleteSurroundingText(beforeLength, afterLength);
6099         }
6100 
6101         @Override
deleteSurroundingTextInCodePoints(int beforeLength, int afterLength)6102         public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
6103             return getTarget().deleteSurroundingTextInCodePoints(beforeLength, afterLength);
6104         }
6105 
6106         @Override
setComposingText(CharSequence text, int newCursorPosition)6107         public boolean setComposingText(CharSequence text, int newCursorPosition) {
6108             return getTarget().setComposingText(text, newCursorPosition);
6109         }
6110 
6111         @Override
setComposingRegion(int start, int end)6112         public boolean setComposingRegion(int start, int end) {
6113             return getTarget().setComposingRegion(start, end);
6114         }
6115 
6116         @Override
finishComposingText()6117         public boolean finishComposingText() {
6118             return mTarget == null || mTarget.finishComposingText();
6119         }
6120 
6121         @Override
commitText(CharSequence text, int newCursorPosition)6122         public boolean commitText(CharSequence text, int newCursorPosition) {
6123             return getTarget().commitText(text, newCursorPosition);
6124         }
6125 
6126         @Override
commitCompletion(CompletionInfo text)6127         public boolean commitCompletion(CompletionInfo text) {
6128             return getTarget().commitCompletion(text);
6129         }
6130 
6131         @Override
commitCorrection(CorrectionInfo correctionInfo)6132         public boolean commitCorrection(CorrectionInfo correctionInfo) {
6133             return getTarget().commitCorrection(correctionInfo);
6134         }
6135 
6136         @Override
setSelection(int start, int end)6137         public boolean setSelection(int start, int end) {
6138             return getTarget().setSelection(start, end);
6139         }
6140 
6141         @Override
performContextMenuAction(int id)6142         public boolean performContextMenuAction(int id) {
6143             return getTarget().performContextMenuAction(id);
6144         }
6145 
6146         @Override
beginBatchEdit()6147         public boolean beginBatchEdit() {
6148             return getTarget().beginBatchEdit();
6149         }
6150 
6151         @Override
endBatchEdit()6152         public boolean endBatchEdit() {
6153             return getTarget().endBatchEdit();
6154         }
6155 
6156         @Override
clearMetaKeyStates(int states)6157         public boolean clearMetaKeyStates(int states) {
6158             return getTarget().clearMetaKeyStates(states);
6159         }
6160 
6161         @Override
performPrivateCommand(String action, Bundle data)6162         public boolean performPrivateCommand(String action, Bundle data) {
6163             return getTarget().performPrivateCommand(action, data);
6164         }
6165 
6166         @Override
requestCursorUpdates(int cursorUpdateMode)6167         public boolean requestCursorUpdates(int cursorUpdateMode) {
6168             return getTarget().requestCursorUpdates(cursorUpdateMode);
6169         }
6170 
6171         @Override
getHandler()6172         public Handler getHandler() {
6173             return getTarget().getHandler();
6174         }
6175 
6176         @Override
closeConnection()6177         public void closeConnection() {
6178             getTarget().closeConnection();
6179         }
6180 
6181         @Override
commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts)6182         public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
6183             return getTarget().commitContent(inputContentInfo, flags, opts);
6184         }
6185     }
6186 
6187     /**
6188      * For filtering we proxy an input connection to an internal text editor,
6189      * and this allows the proxying to happen.
6190      */
6191     @Override
checkInputConnectionProxy(View view)6192     public boolean checkInputConnectionProxy(View view) {
6193         return view == mTextFilter;
6194     }
6195 
6196     /**
6197      * Creates the window for the text filter and populates it with an EditText field;
6198      *
6199      * @param animateEntrance true if the window should appear with an animation
6200      */
createTextFilter(boolean animateEntrance)6201     private void createTextFilter(boolean animateEntrance) {
6202         if (mPopup == null) {
6203             PopupWindow p = new PopupWindow(getContext());
6204             p.setFocusable(false);
6205             p.setTouchable(false);
6206             p.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
6207             p.setContentView(getTextFilterInput());
6208             p.setWidth(LayoutParams.WRAP_CONTENT);
6209             p.setHeight(LayoutParams.WRAP_CONTENT);
6210             p.setBackgroundDrawable(null);
6211             mPopup = p;
6212             getViewTreeObserver().addOnGlobalLayoutListener(this);
6213             mGlobalLayoutListenerAddedFilter = true;
6214         }
6215         if (animateEntrance) {
6216             mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilter);
6217         } else {
6218             mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilterRestore);
6219         }
6220     }
6221 
getTextFilterInput()6222     private EditText getTextFilterInput() {
6223         if (mTextFilter == null) {
6224             final LayoutInflater layoutInflater = LayoutInflater.from(getContext());
6225             mTextFilter = (EditText) layoutInflater.inflate(
6226                     com.android.internal.R.layout.typing_filter, null);
6227             // For some reason setting this as the "real" input type changes
6228             // the text view in some way that it doesn't work, and I don't
6229             // want to figure out why this is.
6230             mTextFilter.setRawInputType(EditorInfo.TYPE_CLASS_TEXT
6231                     | EditorInfo.TYPE_TEXT_VARIATION_FILTER);
6232             mTextFilter.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI);
6233             mTextFilter.addTextChangedListener(this);
6234         }
6235         return mTextFilter;
6236     }
6237 
6238     /**
6239      * Clear the text filter.
6240      */
clearTextFilter()6241     public void clearTextFilter() {
6242         if (mFiltered) {
6243             getTextFilterInput().setText("");
6244             mFiltered = false;
6245             if (mPopup != null && mPopup.isShowing()) {
6246                 dismissPopup();
6247             }
6248         }
6249     }
6250 
6251     /**
6252      * Returns if the ListView currently has a text filter.
6253      */
hasTextFilter()6254     public boolean hasTextFilter() {
6255         return mFiltered;
6256     }
6257 
6258     @Override
onGlobalLayout()6259     public void onGlobalLayout() {
6260         if (isShown()) {
6261             // Show the popup if we are filtered
6262             if (mFiltered && mPopup != null && !mPopup.isShowing() && !mPopupHidden) {
6263                 showPopup();
6264             }
6265         } else {
6266             // Hide the popup when we are no longer visible
6267             if (mPopup != null && mPopup.isShowing()) {
6268                 dismissPopup();
6269             }
6270         }
6271 
6272     }
6273 
6274     /**
6275      * For our text watcher that is associated with the text filter.  Does
6276      * nothing.
6277      */
6278     @Override
beforeTextChanged(CharSequence s, int start, int count, int after)6279     public void beforeTextChanged(CharSequence s, int start, int count, int after) {
6280     }
6281 
6282     /**
6283      * For our text watcher that is associated with the text filter. Performs
6284      * the actual filtering as the text changes, and takes care of hiding and
6285      * showing the popup displaying the currently entered filter text.
6286      */
6287     @Override
onTextChanged(CharSequence s, int start, int before, int count)6288     public void onTextChanged(CharSequence s, int start, int before, int count) {
6289         if (isTextFilterEnabled()) {
6290             createTextFilter(true);
6291             int length = s.length();
6292             boolean showing = mPopup.isShowing();
6293             if (!showing && length > 0) {
6294                 // Show the filter popup if necessary
6295                 showPopup();
6296                 mFiltered = true;
6297             } else if (showing && length == 0) {
6298                 // Remove the filter popup if the user has cleared all text
6299                 dismissPopup();
6300                 mFiltered = false;
6301             }
6302             if (mAdapter instanceof Filterable) {
6303                 Filter f = ((Filterable) mAdapter).getFilter();
6304                 // Filter should not be null when we reach this part
6305                 if (f != null) {
6306                     f.filter(s, this);
6307                 } else {
6308                     throw new IllegalStateException("You cannot call onTextChanged with a non "
6309                             + "filterable adapter");
6310                 }
6311             }
6312         }
6313     }
6314 
6315     /**
6316      * For our text watcher that is associated with the text filter.  Does
6317      * nothing.
6318      */
6319     @Override
afterTextChanged(Editable s)6320     public void afterTextChanged(Editable s) {
6321     }
6322 
6323     @Override
onFilterComplete(int count)6324     public void onFilterComplete(int count) {
6325         if (mSelectedPosition < 0 && count > 0) {
6326             mResurrectToPosition = INVALID_POSITION;
6327             resurrectSelection();
6328         }
6329     }
6330 
6331     @Override
generateDefaultLayoutParams()6332     protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
6333         return new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
6334                 ViewGroup.LayoutParams.WRAP_CONTENT, 0);
6335     }
6336 
6337     @Override
generateLayoutParams(ViewGroup.LayoutParams p)6338     protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
6339         return new LayoutParams(p);
6340     }
6341 
6342     @Override
generateLayoutParams(AttributeSet attrs)6343     public LayoutParams generateLayoutParams(AttributeSet attrs) {
6344         return new AbsListView.LayoutParams(getContext(), attrs);
6345     }
6346 
6347     @Override
checkLayoutParams(ViewGroup.LayoutParams p)6348     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
6349         return p instanceof AbsListView.LayoutParams;
6350     }
6351 
6352     /**
6353      * Puts the list or grid into transcript mode. In this mode the list or grid will always scroll
6354      * to the bottom to show new items.
6355      *
6356      * @param mode the transcript mode to set
6357      *
6358      * @see #TRANSCRIPT_MODE_DISABLED
6359      * @see #TRANSCRIPT_MODE_NORMAL
6360      * @see #TRANSCRIPT_MODE_ALWAYS_SCROLL
6361      */
setTranscriptMode(int mode)6362     public void setTranscriptMode(int mode) {
6363         mTranscriptMode = mode;
6364     }
6365 
6366     /**
6367      * Returns the current transcript mode.
6368      *
6369      * @return {@link #TRANSCRIPT_MODE_DISABLED}, {@link #TRANSCRIPT_MODE_NORMAL} or
6370      *         {@link #TRANSCRIPT_MODE_ALWAYS_SCROLL}
6371      */
6372     @InspectableProperty(enumMapping = {
6373             @EnumEntry(value = TRANSCRIPT_MODE_DISABLED, name = "disabled"),
6374             @EnumEntry(value = TRANSCRIPT_MODE_NORMAL, name = "normal"),
6375             @EnumEntry(value = TRANSCRIPT_MODE_ALWAYS_SCROLL, name = "alwaysScroll")
6376     })
getTranscriptMode()6377     public int getTranscriptMode() {
6378         return mTranscriptMode;
6379     }
6380 
6381     @Override
getSolidColor()6382     public int getSolidColor() {
6383         return mCacheColorHint;
6384     }
6385 
6386     /**
6387      * When set to a non-zero value, the cache color hint indicates that this list is always drawn
6388      * on top of a solid, single-color, opaque background.
6389      *
6390      * Zero means that what's behind this object is translucent (non solid) or is not made of a
6391      * single color. This hint will not affect any existing background drawable set on this view (
6392      * typically set via {@link #setBackgroundDrawable(Drawable)}).
6393      *
6394      * @param color The background color
6395      */
setCacheColorHint(@olorInt int color)6396     public void setCacheColorHint(@ColorInt int color) {
6397         if (color != mCacheColorHint) {
6398             mCacheColorHint = color;
6399             int count = getChildCount();
6400             for (int i = 0; i < count; i++) {
6401                 getChildAt(i).setDrawingCacheBackgroundColor(color);
6402             }
6403             mRecycler.setCacheColorHint(color);
6404         }
6405     }
6406 
6407     /**
6408      * When set to a non-zero value, the cache color hint indicates that this list is always drawn
6409      * on top of a solid, single-color, opaque background
6410      *
6411      * @return The cache color hint
6412      */
6413     @ViewDebug.ExportedProperty(category = "drawing")
6414     @InspectableProperty
6415     @ColorInt
getCacheColorHint()6416     public int getCacheColorHint() {
6417         return mCacheColorHint;
6418     }
6419 
6420     /**
6421      * Move all views (excluding headers and footers) held by this AbsListView into the supplied
6422      * List. This includes views displayed on the screen as well as views stored in AbsListView's
6423      * internal view recycler.
6424      *
6425      * @param views A list into which to put the reclaimed views
6426      */
reclaimViews(List<View> views)6427     public void reclaimViews(List<View> views) {
6428         int childCount = getChildCount();
6429         RecyclerListener listener = mRecycler.mRecyclerListener;
6430 
6431         // Reclaim views on screen
6432         for (int i = 0; i < childCount; i++) {
6433             View child = getChildAt(i);
6434             AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
6435             // Don't reclaim header or footer views, or views that should be ignored
6436             if (lp != null && mRecycler.shouldRecycleViewType(lp.viewType)) {
6437                 views.add(child);
6438                 child.setAccessibilityDelegate(null);
6439                 child.resetSubtreeAutofillIds();
6440                 if (listener != null) {
6441                     // Pretend they went through the scrap heap
6442                     listener.onMovedToScrapHeap(child);
6443                 }
6444             }
6445         }
6446         mRecycler.reclaimScrapViews(views);
6447         removeAllViewsInLayout();
6448     }
6449 
finishGlows()6450     private void finishGlows() {
6451         if (shouldDisplayEdgeEffects()) {
6452             mEdgeGlowTop.finish();
6453             mEdgeGlowBottom.finish();
6454         }
6455     }
6456 
6457     /**
6458      * Sets up this AbsListView to use a remote views adapter which connects to a RemoteViewsService
6459      * through the specified intent.
6460      * @param intent the intent used to identify the RemoteViewsService for the adapter to connect to.
6461      */
setRemoteViewsAdapter(Intent intent)6462     public void setRemoteViewsAdapter(Intent intent) {
6463         setRemoteViewsAdapter(intent, false);
6464     }
6465 
6466     /** @hide **/
setRemoteViewsAdapterAsync(final Intent intent)6467     public Runnable setRemoteViewsAdapterAsync(final Intent intent) {
6468         return new RemoteViewsAdapter.AsyncRemoteAdapterAction(this, intent);
6469     }
6470 
6471     /** @hide **/
6472     @Override
setRemoteViewsAdapter(Intent intent, boolean isAsync)6473     public void setRemoteViewsAdapter(Intent intent, boolean isAsync) {
6474         // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
6475         // service handling the specified intent.
6476         if (mRemoteAdapter != null) {
6477             Intent.FilterComparison fcNew = new Intent.FilterComparison(intent);
6478             Intent.FilterComparison fcOld = new Intent.FilterComparison(
6479                     mRemoteAdapter.getRemoteViewsServiceIntent());
6480             if (fcNew.equals(fcOld)) {
6481                 return;
6482             }
6483         }
6484         mDeferNotifyDataSetChanged = false;
6485         // Otherwise, create a new RemoteViewsAdapter for binding
6486         mRemoteAdapter = new RemoteViewsAdapter(getContext(), intent, this, isAsync);
6487         if (mRemoteAdapter.isDataReady()) {
6488             setAdapter(mRemoteAdapter);
6489         }
6490     }
6491 
6492     /**
6493      * Sets up the onClickHandler to be used by the RemoteViewsAdapter when inflating RemoteViews
6494      *
6495      * @param handler The OnClickHandler to use when inflating RemoteViews.
6496      *
6497      * @hide
6498      */
setRemoteViewsInteractionHandler(InteractionHandler handler)6499     public void setRemoteViewsInteractionHandler(InteractionHandler handler) {
6500         // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
6501         // service handling the specified intent.
6502         if (mRemoteAdapter != null) {
6503             mRemoteAdapter.setRemoteViewsInteractionHandler(handler);
6504         }
6505     }
6506 
6507     /**
6508      * This defers a notifyDataSetChanged on the pending RemoteViewsAdapter if it has not
6509      * connected yet.
6510      */
6511     @Override
deferNotifyDataSetChanged()6512     public void deferNotifyDataSetChanged() {
6513         mDeferNotifyDataSetChanged = true;
6514     }
6515 
6516     /**
6517      * Called back when the adapter connects to the RemoteViewsService.
6518      */
6519     @Override
onRemoteAdapterConnected()6520     public boolean onRemoteAdapterConnected() {
6521         if (mRemoteAdapter != mAdapter) {
6522             setAdapter(mRemoteAdapter);
6523             if (mDeferNotifyDataSetChanged) {
6524                 mRemoteAdapter.notifyDataSetChanged();
6525                 mDeferNotifyDataSetChanged = false;
6526             }
6527             return false;
6528         } else if (mRemoteAdapter != null) {
6529             mRemoteAdapter.superNotifyDataSetChanged();
6530             return true;
6531         }
6532         return false;
6533     }
6534 
6535     /**
6536      * Called back when the adapter disconnects from the RemoteViewsService.
6537      */
6538     @Override
onRemoteAdapterDisconnected()6539     public void onRemoteAdapterDisconnected() {
6540         // If the remote adapter disconnects, we keep it around
6541         // since the currently displayed items are still cached.
6542         // Further, we want the service to eventually reconnect
6543         // when necessary, as triggered by this view requesting
6544         // items from the Adapter.
6545     }
6546 
6547     /**
6548      * Hints the RemoteViewsAdapter, if it exists, about which views are currently
6549      * being displayed by the AbsListView.
6550      */
setVisibleRangeHint(int start, int end)6551     void setVisibleRangeHint(int start, int end) {
6552         if (mRemoteAdapter != null) {
6553             mRemoteAdapter.setVisibleRangeHint(start, end);
6554         }
6555     }
6556 
6557     /**
6558      * Sets the edge effect color for both top and bottom edge effects.
6559      *
6560      * @param color The color for the edge effects.
6561      * @see #setTopEdgeEffectColor(int)
6562      * @see #setBottomEdgeEffectColor(int)
6563      * @see #getTopEdgeEffectColor()
6564      * @see #getBottomEdgeEffectColor()
6565      */
setEdgeEffectColor(@olorInt int color)6566     public void setEdgeEffectColor(@ColorInt int color) {
6567         setTopEdgeEffectColor(color);
6568         setBottomEdgeEffectColor(color);
6569     }
6570 
6571     /**
6572      * Sets the bottom edge effect color.
6573      *
6574      * @param color The color for the bottom edge effect.
6575      * @see #setTopEdgeEffectColor(int)
6576      * @see #setEdgeEffectColor(int)
6577      * @see #getTopEdgeEffectColor()
6578      * @see #getBottomEdgeEffectColor()
6579      */
setBottomEdgeEffectColor(@olorInt int color)6580     public void setBottomEdgeEffectColor(@ColorInt int color) {
6581         mEdgeGlowBottom.setColor(color);
6582         invalidateBottomGlow();
6583     }
6584 
6585     /**
6586      * Sets the top edge effect color.
6587      *
6588      * @param color The color for the top edge effect.
6589      * @see #setBottomEdgeEffectColor(int)
6590      * @see #setEdgeEffectColor(int)
6591      * @see #getTopEdgeEffectColor()
6592      * @see #getBottomEdgeEffectColor()
6593      */
setTopEdgeEffectColor(@olorInt int color)6594     public void setTopEdgeEffectColor(@ColorInt int color) {
6595         mEdgeGlowTop.setColor(color);
6596         invalidateTopGlow();
6597     }
6598 
6599     /**
6600      * Returns the top edge effect color.
6601      *
6602      * @return The top edge effect color.
6603      * @see #setEdgeEffectColor(int)
6604      * @see #setTopEdgeEffectColor(int)
6605      * @see #setBottomEdgeEffectColor(int)
6606      * @see #getBottomEdgeEffectColor()
6607      */
6608     @ColorInt
getTopEdgeEffectColor()6609     public int getTopEdgeEffectColor() {
6610         return mEdgeGlowTop.getColor();
6611     }
6612 
6613     /**
6614      * Returns the bottom edge effect color.
6615      *
6616      * @return The bottom edge effect color.
6617      * @see #setEdgeEffectColor(int)
6618      * @see #setTopEdgeEffectColor(int)
6619      * @see #setBottomEdgeEffectColor(int)
6620      * @see #getTopEdgeEffectColor()
6621      */
6622     @ColorInt
getBottomEdgeEffectColor()6623     public int getBottomEdgeEffectColor() {
6624         return mEdgeGlowBottom.getColor();
6625     }
6626 
6627     /**
6628      * Sets the recycler listener to be notified whenever a View is set aside in
6629      * the recycler for later reuse. This listener can be used to free resources
6630      * associated to the View.
6631      *
6632      * @param listener The recycler listener to be notified of views set aside
6633      *        in the recycler.
6634      *
6635      * @see android.widget.AbsListView.RecyclerListener
6636      */
setRecyclerListener(RecyclerListener listener)6637     public void setRecyclerListener(RecyclerListener listener) {
6638         mRecycler.mRecyclerListener = listener;
6639     }
6640 
6641     class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver {
6642         @Override
onChanged()6643         public void onChanged() {
6644             super.onChanged();
6645             if (mFastScroll != null) {
6646                 mFastScroll.onSectionsChanged();
6647             }
6648         }
6649 
6650         @Override
onInvalidated()6651         public void onInvalidated() {
6652             super.onInvalidated();
6653             if (mFastScroll != null) {
6654                 mFastScroll.onSectionsChanged();
6655             }
6656         }
6657     }
6658 
6659     /**
6660      * A MultiChoiceModeListener receives events for {@link AbsListView#CHOICE_MODE_MULTIPLE_MODAL}.
6661      * It acts as the {@link ActionMode.Callback} for the selection mode and also receives
6662      * {@link #onItemCheckedStateChanged(ActionMode, int, long, boolean)} events when the user
6663      * selects and deselects list items.
6664      */
6665     public interface MultiChoiceModeListener extends ActionMode.Callback {
6666         /**
6667          * Called when an item is checked or unchecked during selection mode.
6668          *
6669          * @param mode The {@link ActionMode} providing the selection mode
6670          * @param position Adapter position of the item that was checked or unchecked
6671          * @param id Adapter ID of the item that was checked or unchecked
6672          * @param checked <code>true</code> if the item is now checked, <code>false</code>
6673          *                if the item is now unchecked.
6674          */
6675         public void onItemCheckedStateChanged(ActionMode mode,
6676                 int position, long id, boolean checked);
6677     }
6678 
6679     class MultiChoiceModeWrapper implements MultiChoiceModeListener {
6680         private MultiChoiceModeListener mWrapped;
6681 
setWrapped(MultiChoiceModeListener wrapped)6682         public void setWrapped(MultiChoiceModeListener wrapped) {
6683             mWrapped = wrapped;
6684         }
6685 
hasWrappedCallback()6686         public boolean hasWrappedCallback() {
6687             return mWrapped != null;
6688         }
6689 
6690         @Override
onCreateActionMode(ActionMode mode, Menu menu)6691         public boolean onCreateActionMode(ActionMode mode, Menu menu) {
6692             if (mWrapped.onCreateActionMode(mode, menu)) {
6693                 // Initialize checked graphic state?
6694                 setLongClickable(false);
6695                 return true;
6696             }
6697             return false;
6698         }
6699 
6700         @Override
onPrepareActionMode(ActionMode mode, Menu menu)6701         public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
6702             return mWrapped.onPrepareActionMode(mode, menu);
6703         }
6704 
6705         @Override
onActionItemClicked(ActionMode mode, MenuItem item)6706         public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
6707             return mWrapped.onActionItemClicked(mode, item);
6708         }
6709 
6710         @Override
onDestroyActionMode(ActionMode mode)6711         public void onDestroyActionMode(ActionMode mode) {
6712             mWrapped.onDestroyActionMode(mode);
6713             mChoiceActionMode = null;
6714 
6715             // Ending selection mode means deselecting everything.
6716             clearChoices();
6717 
6718             mDataChanged = true;
6719             rememberSyncState();
6720             requestLayout();
6721 
6722             setLongClickable(true);
6723         }
6724 
6725         @Override
onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked)6726         public void onItemCheckedStateChanged(ActionMode mode,
6727                 int position, long id, boolean checked) {
6728             mWrapped.onItemCheckedStateChanged(mode, position, id, checked);
6729 
6730             // If there are no items selected we no longer need the selection mode.
6731             if (getCheckedItemCount() == 0) {
6732                 mode.finish();
6733             }
6734         }
6735     }
6736 
6737     /**
6738      * AbsListView extends LayoutParams to provide a place to hold the view type.
6739      */
6740     public static class LayoutParams extends ViewGroup.LayoutParams {
6741         /**
6742          * View type for this view, as returned by
6743          * {@link android.widget.Adapter#getItemViewType(int) }
6744          */
6745         @ViewDebug.ExportedProperty(category = "list", mapping = {
6746             @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_IGNORE, to = "ITEM_VIEW_TYPE_IGNORE"),
6747             @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_HEADER_OR_FOOTER, to = "ITEM_VIEW_TYPE_HEADER_OR_FOOTER")
6748         })
6749         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
6750         int viewType;
6751 
6752         /**
6753          * When this boolean is set, the view has been added to the AbsListView
6754          * at least once. It is used to know whether headers/footers have already
6755          * been added to the list view and whether they should be treated as
6756          * recycled views or not.
6757          */
6758         @ViewDebug.ExportedProperty(category = "list")
6759         boolean recycledHeaderFooter;
6760 
6761         /**
6762          * When an AbsListView is measured with an AT_MOST measure spec, it needs
6763          * to obtain children views to measure itself. When doing so, the children
6764          * are not attached to the window, but put in the recycler which assumes
6765          * they've been attached before. Setting this flag will force the reused
6766          * view to be attached to the window rather than just attached to the
6767          * parent.
6768          */
6769         @ViewDebug.ExportedProperty(category = "list")
6770         boolean forceAdd;
6771 
6772         /**
6773          * The position the view was removed from when pulled out of the
6774          * scrap heap.
6775          * @hide
6776          */
6777         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
6778         int scrappedFromPosition;
6779 
6780         /**
6781          * The ID the view represents
6782          */
6783         long itemId = -1;
6784 
6785         /** Whether the adapter considers the item enabled. */
6786         boolean isEnabled;
6787 
LayoutParams(Context c, AttributeSet attrs)6788         public LayoutParams(Context c, AttributeSet attrs) {
6789             super(c, attrs);
6790         }
6791 
LayoutParams(int w, int h)6792         public LayoutParams(int w, int h) {
6793             super(w, h);
6794         }
6795 
LayoutParams(int w, int h, int viewType)6796         public LayoutParams(int w, int h, int viewType) {
6797             super(w, h);
6798             this.viewType = viewType;
6799         }
6800 
LayoutParams(ViewGroup.LayoutParams source)6801         public LayoutParams(ViewGroup.LayoutParams source) {
6802             super(source);
6803         }
6804 
6805         /** @hide */
6806         @Override
encodeProperties(@onNull ViewHierarchyEncoder encoder)6807         protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
6808             super.encodeProperties(encoder);
6809 
6810             encoder.addProperty("list:viewType", viewType);
6811             encoder.addProperty("list:recycledHeaderFooter", recycledHeaderFooter);
6812             encoder.addProperty("list:forceAdd", forceAdd);
6813             encoder.addProperty("list:isEnabled", isEnabled);
6814         }
6815     }
6816 
6817     /**
6818      * A RecyclerListener is used to receive a notification whenever a View is placed
6819      * inside the RecycleBin's scrap heap. This listener is used to free resources
6820      * associated to Views placed in the RecycleBin.
6821      *
6822      * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
6823      */
6824     public static interface RecyclerListener {
6825         /**
6826          * Indicates that the specified View was moved into the recycler's scrap heap.
6827          * The view is not displayed on screen any more and any expensive resource
6828          * associated with the view should be discarded.
6829          *
6830          * @param view
6831          */
6832         void onMovedToScrapHeap(View view);
6833     }
6834 
6835     /**
6836      * The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of
6837      * storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the
6838      * start of a layout. By construction, they are displaying current information. At the end of
6839      * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that
6840      * could potentially be used by the adapter to avoid allocating views unnecessarily.
6841      *
6842      * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
6843      * @see android.widget.AbsListView.RecyclerListener
6844      */
6845     class RecycleBin {
6846         @UnsupportedAppUsage
6847         private RecyclerListener mRecyclerListener;
6848 
6849         /**
6850          * The position of the first view stored in mActiveViews.
6851          */
6852         private int mFirstActivePosition;
6853 
6854         /**
6855          * Views that were on screen at the start of layout. This array is populated at the start of
6856          * layout, and at the end of layout all view in mActiveViews are moved to mScrapViews.
6857          * Views in mActiveViews represent a contiguous range of Views, with position of the first
6858          * view store in mFirstActivePosition.
6859          */
6860         private View[] mActiveViews = new View[0];
6861 
6862         /**
6863          * Unsorted views that can be used by the adapter as a convert view.
6864          */
6865         private ArrayList<View>[] mScrapViews;
6866 
6867         private int mViewTypeCount;
6868 
6869         private ArrayList<View> mCurrentScrap;
6870 
6871         private ArrayList<View> mSkippedScrap;
6872 
6873         private SparseArray<View> mTransientStateViews;
6874         private LongSparseArray<View> mTransientStateViewsById;
6875 
setViewTypeCount(int viewTypeCount)6876         public void setViewTypeCount(int viewTypeCount) {
6877             if (viewTypeCount < 1) {
6878                 throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
6879             }
6880             //noinspection unchecked
6881             ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
6882             for (int i = 0; i < viewTypeCount; i++) {
6883                 scrapViews[i] = new ArrayList<View>();
6884             }
6885             mViewTypeCount = viewTypeCount;
6886             mCurrentScrap = scrapViews[0];
6887             mScrapViews = scrapViews;
6888         }
6889 
markChildrenDirty()6890         public void markChildrenDirty() {
6891             if (mViewTypeCount == 1) {
6892                 final ArrayList<View> scrap = mCurrentScrap;
6893                 final int scrapCount = scrap.size();
6894                 for (int i = 0; i < scrapCount; i++) {
6895                     scrap.get(i).forceLayout();
6896                 }
6897             } else {
6898                 final int typeCount = mViewTypeCount;
6899                 for (int i = 0; i < typeCount; i++) {
6900                     final ArrayList<View> scrap = mScrapViews[i];
6901                     final int scrapCount = scrap.size();
6902                     for (int j = 0; j < scrapCount; j++) {
6903                         scrap.get(j).forceLayout();
6904                     }
6905                 }
6906             }
6907             if (mTransientStateViews != null) {
6908                 final int count = mTransientStateViews.size();
6909                 for (int i = 0; i < count; i++) {
6910                     mTransientStateViews.valueAt(i).forceLayout();
6911                 }
6912             }
6913             if (mTransientStateViewsById != null) {
6914                 final int count = mTransientStateViewsById.size();
6915                 for (int i = 0; i < count; i++) {
6916                     mTransientStateViewsById.valueAt(i).forceLayout();
6917                 }
6918             }
6919         }
6920 
shouldRecycleViewType(int viewType)6921         public boolean shouldRecycleViewType(int viewType) {
6922             return viewType >= 0;
6923         }
6924 
6925         /**
6926          * Clears the scrap heap.
6927          */
6928         @UnsupportedAppUsage
clear()6929         void clear() {
6930             if (mViewTypeCount == 1) {
6931                 final ArrayList<View> scrap = mCurrentScrap;
6932                 clearScrap(scrap);
6933             } else {
6934                 final int typeCount = mViewTypeCount;
6935                 for (int i = 0; i < typeCount; i++) {
6936                     final ArrayList<View> scrap = mScrapViews[i];
6937                     clearScrap(scrap);
6938                 }
6939             }
6940 
6941             clearTransientStateViews();
6942         }
6943 
6944         /**
6945          * Fill ActiveViews with all of the children of the AbsListView.
6946          *
6947          * @param childCount The minimum number of views mActiveViews should hold
6948          * @param firstActivePosition The position of the first view that will be stored in
6949          *        mActiveViews
6950          */
fillActiveViews(int childCount, int firstActivePosition)6951         void fillActiveViews(int childCount, int firstActivePosition) {
6952             if (mActiveViews.length < childCount) {
6953                 mActiveViews = new View[childCount];
6954             }
6955             mFirstActivePosition = firstActivePosition;
6956 
6957             //noinspection MismatchedReadAndWriteOfArray
6958             final View[] activeViews = mActiveViews;
6959             for (int i = 0; i < childCount; i++) {
6960                 View child = getChildAt(i);
6961                 AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
6962                 // Don't put header or footer views into the scrap heap
6963                 if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
6964                     // Note:  We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views.
6965                     //        However, we will NOT place them into scrap views.
6966                     activeViews[i] = child;
6967                     // Remember the position so that setupChild() doesn't reset state.
6968                     lp.scrappedFromPosition = firstActivePosition + i;
6969                 }
6970             }
6971         }
6972 
6973         /**
6974          * Get the view corresponding to the specified position. The view will be removed from
6975          * mActiveViews if it is found.
6976          *
6977          * @param position The position to look up in mActiveViews
6978          * @return The view if it is found, null otherwise
6979          */
getActiveView(int position)6980         View getActiveView(int position) {
6981             int index = position - mFirstActivePosition;
6982             final View[] activeViews = mActiveViews;
6983             if (index >=0 && index < activeViews.length) {
6984                 final View match = activeViews[index];
6985                 activeViews[index] = null;
6986                 return match;
6987             }
6988             return null;
6989         }
6990 
getTransientStateView(int position)6991         View getTransientStateView(int position) {
6992             if (mAdapter != null && mAdapterHasStableIds && mTransientStateViewsById != null) {
6993                 long id = mAdapter.getItemId(position);
6994                 View result = mTransientStateViewsById.get(id);
6995                 mTransientStateViewsById.remove(id);
6996                 return result;
6997             }
6998             if (mTransientStateViews != null) {
6999                 final int index = mTransientStateViews.indexOfKey(position);
7000                 if (index >= 0) {
7001                     View result = mTransientStateViews.valueAt(index);
7002                     mTransientStateViews.removeAt(index);
7003                     return result;
7004                 }
7005             }
7006             return null;
7007         }
7008 
7009         /**
7010          * Dumps and fully detaches any currently saved views with transient
7011          * state.
7012          */
clearTransientStateViews()7013         void clearTransientStateViews() {
7014             final SparseArray<View> viewsByPos = mTransientStateViews;
7015             if (viewsByPos != null) {
7016                 final int N = viewsByPos.size();
7017                 for (int i = 0; i < N; i++) {
7018                     removeDetachedView(viewsByPos.valueAt(i), false);
7019                 }
7020                 viewsByPos.clear();
7021             }
7022 
7023             final LongSparseArray<View> viewsById = mTransientStateViewsById;
7024             if (viewsById != null) {
7025                 final int N = viewsById.size();
7026                 for (int i = 0; i < N; i++) {
7027                     removeDetachedView(viewsById.valueAt(i), false);
7028                 }
7029                 viewsById.clear();
7030             }
7031         }
7032 
7033         /**
7034          * @return A view from the ScrapViews collection. These are unordered.
7035          */
getScrapView(int position)7036         View getScrapView(int position) {
7037             final int whichScrap = mAdapter.getItemViewType(position);
7038             if (whichScrap < 0) {
7039                 return null;
7040             }
7041             if (mViewTypeCount == 1) {
7042                 return retrieveFromScrap(mCurrentScrap, position);
7043             } else if (whichScrap < mScrapViews.length) {
7044                 return retrieveFromScrap(mScrapViews[whichScrap], position);
7045             }
7046             return null;
7047         }
7048 
7049         /**
7050          * Puts a view into the list of scrap views.
7051          * <p>
7052          * If the list data hasn't changed or the adapter has stable IDs, views
7053          * with transient state will be preserved for later retrieval.
7054          *
7055          * @param scrap The view to add
7056          * @param position The view's position within its parent
7057          */
addScrapView(View scrap, int position)7058         void addScrapView(View scrap, int position) {
7059             final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
7060             if (lp == null) {
7061                 // Can't recycle, but we don't know anything about the view.
7062                 // Ignore it completely.
7063                 return;
7064             }
7065 
7066             lp.scrappedFromPosition = position;
7067 
7068             // Remove but don't scrap header or footer views, or views that
7069             // should otherwise not be recycled.
7070             final int viewType = lp.viewType;
7071             if (!shouldRecycleViewType(viewType)) {
7072                 // Can't recycle. If it's not a header or footer, which have
7073                 // special handling and should be ignored, then skip the scrap
7074                 // heap and we'll fully detach the view later.
7075                 if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
7076                     getSkippedScrap().add(scrap);
7077                 }
7078                 return;
7079             }
7080 
7081             scrap.dispatchStartTemporaryDetach();
7082 
7083             // The the accessibility state of the view may change while temporary
7084             // detached and we do not allow detached views to fire accessibility
7085             // events. So we are announcing that the subtree changed giving a chance
7086             // to clients holding on to a view in this subtree to refresh it.
7087             notifyViewAccessibilityStateChangedIfNeeded(
7088                     AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
7089 
7090             // Don't scrap views that have transient state.
7091             final boolean scrapHasTransientState = scrap.hasTransientState();
7092             if (scrapHasTransientState) {
7093                 if (mAdapter != null && mAdapterHasStableIds) {
7094                     // If the adapter has stable IDs, we can reuse the view for
7095                     // the same data.
7096                     if (mTransientStateViewsById == null) {
7097                         mTransientStateViewsById = new LongSparseArray<>();
7098                     }
7099                     mTransientStateViewsById.put(lp.itemId, scrap);
7100                 } else if (!mDataChanged) {
7101                     // If the data hasn't changed, we can reuse the views at
7102                     // their old positions.
7103                     if (mTransientStateViews == null) {
7104                         mTransientStateViews = new SparseArray<>();
7105                     }
7106                     mTransientStateViews.put(position, scrap);
7107                 } else {
7108                     // Otherwise, we'll have to remove the view and start over.
7109                     clearScrapForRebind(scrap);
7110                     getSkippedScrap().add(scrap);
7111                 }
7112             } else {
7113                 clearScrapForRebind(scrap);
7114                 if (mViewTypeCount == 1) {
7115                     mCurrentScrap.add(scrap);
7116                 } else {
7117                     mScrapViews[viewType].add(scrap);
7118                 }
7119 
7120                 if (mRecyclerListener != null) {
7121                     mRecyclerListener.onMovedToScrapHeap(scrap);
7122                 }
7123             }
7124         }
7125 
getSkippedScrap()7126         private ArrayList<View> getSkippedScrap() {
7127             if (mSkippedScrap == null) {
7128                 mSkippedScrap = new ArrayList<>();
7129             }
7130             return mSkippedScrap;
7131         }
7132 
7133         /**
7134          * Finish the removal of any views that skipped the scrap heap.
7135          */
removeSkippedScrap()7136         void removeSkippedScrap() {
7137             if (mSkippedScrap == null) {
7138                 return;
7139             }
7140             final int count = mSkippedScrap.size();
7141             for (int i = 0; i < count; i++) {
7142                 removeDetachedView(mSkippedScrap.get(i), false);
7143             }
7144             mSkippedScrap.clear();
7145         }
7146 
7147         /**
7148          * Move all views remaining in mActiveViews to mScrapViews.
7149          */
scrapActiveViews()7150         void scrapActiveViews() {
7151             final View[] activeViews = mActiveViews;
7152             final boolean hasListener = mRecyclerListener != null;
7153             final boolean multipleScraps = mViewTypeCount > 1;
7154 
7155             ArrayList<View> scrapViews = mCurrentScrap;
7156             final int count = activeViews.length;
7157             for (int i = count - 1; i >= 0; i--) {
7158                 final View victim = activeViews[i];
7159                 if (victim != null) {
7160                     final AbsListView.LayoutParams lp
7161                             = (AbsListView.LayoutParams) victim.getLayoutParams();
7162                     final int whichScrap = lp.viewType;
7163 
7164                     activeViews[i] = null;
7165 
7166                     if (victim.hasTransientState()) {
7167                         // Store views with transient state for later use.
7168                         victim.dispatchStartTemporaryDetach();
7169 
7170                         if (mAdapter != null && mAdapterHasStableIds) {
7171                             if (mTransientStateViewsById == null) {
7172                                 mTransientStateViewsById = new LongSparseArray<View>();
7173                             }
7174                             long id = mAdapter.getItemId(mFirstActivePosition + i);
7175                             mTransientStateViewsById.put(id, victim);
7176                         } else if (!mDataChanged) {
7177                             if (mTransientStateViews == null) {
7178                                 mTransientStateViews = new SparseArray<View>();
7179                             }
7180                             mTransientStateViews.put(mFirstActivePosition + i, victim);
7181                         } else if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
7182                             // The data has changed, we can't keep this view.
7183                             removeDetachedView(victim, false);
7184                         }
7185                     } else if (!shouldRecycleViewType(whichScrap)) {
7186                         // Discard non-recyclable views except headers/footers.
7187                         if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
7188                             removeDetachedView(victim, false);
7189                         }
7190                     } else {
7191                         // Store everything else on the appropriate scrap heap.
7192                         if (multipleScraps) {
7193                             scrapViews = mScrapViews[whichScrap];
7194                         }
7195 
7196                         lp.scrappedFromPosition = mFirstActivePosition + i;
7197                         removeDetachedView(victim, false);
7198                         scrapViews.add(victim);
7199 
7200                         if (hasListener) {
7201                             mRecyclerListener.onMovedToScrapHeap(victim);
7202                         }
7203                     }
7204                 }
7205             }
7206             pruneScrapViews();
7207         }
7208 
7209         /**
7210          * At the end of a layout pass, all temp detached views should either be re-attached or
7211          * completely detached. This method ensures that any remaining view in the scrap list is
7212          * fully detached.
7213          */
fullyDetachScrapViews()7214         void fullyDetachScrapViews() {
7215             final int viewTypeCount = mViewTypeCount;
7216             final ArrayList<View>[] scrapViews = mScrapViews;
7217             for (int i = 0; i < viewTypeCount; ++i) {
7218                 final ArrayList<View> scrapPile = scrapViews[i];
7219                 for (int j = scrapPile.size() - 1; j >= 0; j--) {
7220                     final View view = scrapPile.get(j);
7221                     if (view.isTemporarilyDetached()) {
7222                         removeDetachedView(view, false);
7223                     }
7224                 }
7225             }
7226         }
7227 
7228         /**
7229          * Makes sure that the size of mScrapViews does not exceed the size of
7230          * mActiveViews, which can happen if an adapter does not recycle its
7231          * views. Removes cached transient state views that no longer have
7232          * transient state.
7233          */
pruneScrapViews()7234         private void pruneScrapViews() {
7235             final int maxViews = mActiveViews.length;
7236             final int viewTypeCount = mViewTypeCount;
7237             final ArrayList<View>[] scrapViews = mScrapViews;
7238             for (int i = 0; i < viewTypeCount; ++i) {
7239                 final ArrayList<View> scrapPile = scrapViews[i];
7240                 int size = scrapPile.size();
7241                 while (size > maxViews) {
7242                     scrapPile.remove(--size);
7243                 }
7244             }
7245 
7246             final SparseArray<View> transViewsByPos = mTransientStateViews;
7247             if (transViewsByPos != null) {
7248                 for (int i = 0; i < transViewsByPos.size(); i++) {
7249                     final View v = transViewsByPos.valueAt(i);
7250                     if (!v.hasTransientState()) {
7251                         removeDetachedView(v, false);
7252                         transViewsByPos.removeAt(i);
7253                         i--;
7254                     }
7255                 }
7256             }
7257 
7258             final LongSparseArray<View> transViewsById = mTransientStateViewsById;
7259             if (transViewsById != null) {
7260                 for (int i = 0; i < transViewsById.size(); i++) {
7261                     final View v = transViewsById.valueAt(i);
7262                     if (!v.hasTransientState()) {
7263                         removeDetachedView(v, false);
7264                         transViewsById.removeAt(i);
7265                         i--;
7266                     }
7267                 }
7268             }
7269         }
7270 
7271         /**
7272          * Puts all views in the scrap heap into the supplied list.
7273          */
reclaimScrapViews(List<View> views)7274         void reclaimScrapViews(List<View> views) {
7275             if (mViewTypeCount == 1) {
7276                 views.addAll(mCurrentScrap);
7277             } else {
7278                 final int viewTypeCount = mViewTypeCount;
7279                 final ArrayList<View>[] scrapViews = mScrapViews;
7280                 for (int i = 0; i < viewTypeCount; ++i) {
7281                     final ArrayList<View> scrapPile = scrapViews[i];
7282                     views.addAll(scrapPile);
7283                 }
7284             }
7285         }
7286 
7287         /**
7288          * Updates the cache color hint of all known views.
7289          *
7290          * @param color The new cache color hint.
7291          */
setCacheColorHint(int color)7292         void setCacheColorHint(int color) {
7293             if (mViewTypeCount == 1) {
7294                 final ArrayList<View> scrap = mCurrentScrap;
7295                 final int scrapCount = scrap.size();
7296                 for (int i = 0; i < scrapCount; i++) {
7297                     scrap.get(i).setDrawingCacheBackgroundColor(color);
7298                 }
7299             } else {
7300                 final int typeCount = mViewTypeCount;
7301                 for (int i = 0; i < typeCount; i++) {
7302                     final ArrayList<View> scrap = mScrapViews[i];
7303                     final int scrapCount = scrap.size();
7304                     for (int j = 0; j < scrapCount; j++) {
7305                         scrap.get(j).setDrawingCacheBackgroundColor(color);
7306                     }
7307                 }
7308             }
7309             // Just in case this is called during a layout pass
7310             final View[] activeViews = mActiveViews;
7311             final int count = activeViews.length;
7312             for (int i = 0; i < count; ++i) {
7313                 final View victim = activeViews[i];
7314                 if (victim != null) {
7315                     victim.setDrawingCacheBackgroundColor(color);
7316                 }
7317             }
7318         }
7319 
retrieveFromScrap(ArrayList<View> scrapViews, int position)7320         private View retrieveFromScrap(ArrayList<View> scrapViews, int position) {
7321             final int size = scrapViews.size();
7322             if (size > 0) {
7323                 // See if we still have a view for this position or ID.
7324                 // Traverse backwards to find the most recently used scrap view
7325                 for (int i = size - 1; i >= 0; i--) {
7326                     final View view = scrapViews.get(i);
7327                     final AbsListView.LayoutParams params =
7328                             (AbsListView.LayoutParams) view.getLayoutParams();
7329 
7330                     if (mAdapterHasStableIds) {
7331                         final long id = mAdapter.getItemId(position);
7332                         if (id == params.itemId) {
7333                             return scrapViews.remove(i);
7334                         }
7335                     } else if (params.scrappedFromPosition == position) {
7336                         final View scrap = scrapViews.remove(i);
7337                         clearScrapForRebind(scrap);
7338                         return scrap;
7339                     }
7340                 }
7341                 final View scrap = scrapViews.remove(size - 1);
7342                 clearScrapForRebind(scrap);
7343                 return scrap;
7344             } else {
7345                 return null;
7346             }
7347         }
7348 
clearScrap(final ArrayList<View> scrap)7349         private void clearScrap(final ArrayList<View> scrap) {
7350             final int scrapCount = scrap.size();
7351             for (int j = 0; j < scrapCount; j++) {
7352                 removeDetachedView(scrap.remove(scrapCount - 1 - j), false);
7353             }
7354         }
7355 
clearScrapForRebind(View view)7356         private void clearScrapForRebind(View view) {
7357             view.clearAccessibilityFocus();
7358             view.setAccessibilityDelegate(null);
7359             view.resetSubtreeAutofillIds();
7360         }
7361 
removeDetachedView(View child, boolean animate)7362         private void removeDetachedView(View child, boolean animate) {
7363             child.setAccessibilityDelegate(null);
7364             child.resetSubtreeAutofillIds();
7365             AbsListView.this.removeDetachedView(child, animate);
7366         }
7367     }
7368 
7369     /**
7370      * Returns the height of the view for the specified position.
7371      *
7372      * @param position the item position
7373      * @return view height in pixels
7374      */
getHeightForPosition(int position)7375     int getHeightForPosition(int position) {
7376         final int firstVisiblePosition = getFirstVisiblePosition();
7377         final int childCount = getChildCount();
7378         final int index = position - firstVisiblePosition;
7379         if (index >= 0 && index < childCount) {
7380             // Position is on-screen, use existing view.
7381             final View view = getChildAt(index);
7382             return view.getHeight();
7383         } else {
7384             // Position is off-screen, obtain & recycle view.
7385             final View view = obtainView(position, mIsScrap);
7386             view.measure(mWidthMeasureSpec, MeasureSpec.UNSPECIFIED);
7387             final int height = view.getMeasuredHeight();
7388             mRecycler.addScrapView(view, position);
7389             return height;
7390         }
7391     }
7392 
7393     /**
7394      * Sets the selected item and positions the selection y pixels from the top edge
7395      * of the ListView. (If in touch mode, the item will not be selected but it will
7396      * still be positioned appropriately.)
7397      *
7398      * @param position Index (starting at 0) of the data item to be selected.
7399      * @param y The distance from the top edge of the ListView (plus padding) that the
7400      *        item will be positioned.
7401      */
setSelectionFromTop(int position, int y)7402     public void setSelectionFromTop(int position, int y) {
7403         if (mAdapter == null) {
7404             return;
7405         }
7406 
7407         if (!isInTouchMode()) {
7408             position = lookForSelectablePosition(position, true);
7409             if (position >= 0) {
7410                 setNextSelectedPositionInt(position);
7411             }
7412         } else {
7413             mResurrectToPosition = position;
7414         }
7415 
7416         if (position >= 0) {
7417             mLayoutMode = LAYOUT_SPECIFIC;
7418             mSpecificTop = mListPadding.top + y;
7419 
7420             if (mNeedSync) {
7421                 mSyncPosition = position;
7422                 mSyncRowId = mAdapter.getItemId(position);
7423             }
7424 
7425             if (mPositionScroller != null) {
7426                 mPositionScroller.stop();
7427             }
7428             requestLayout();
7429         }
7430     }
7431 
7432     /** @hide */
7433     @Override
encodeProperties(@onNull ViewHierarchyEncoder encoder)7434     protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
7435         super.encodeProperties(encoder);
7436 
7437         encoder.addProperty("drawing:cacheColorHint", getCacheColorHint());
7438         encoder.addProperty("list:fastScrollEnabled", isFastScrollEnabled());
7439         encoder.addProperty("list:scrollingCacheEnabled", isScrollingCacheEnabled());
7440         encoder.addProperty("list:smoothScrollbarEnabled", isSmoothScrollbarEnabled());
7441         encoder.addProperty("list:stackFromBottom", isStackFromBottom());
7442         encoder.addProperty("list:textFilterEnabled", isTextFilterEnabled());
7443 
7444         View selectedView = getSelectedView();
7445         if (selectedView != null) {
7446             encoder.addPropertyKey("selectedView");
7447             selectedView.encode(encoder);
7448         }
7449     }
7450 
7451     /**
7452      * Abstract positon scroller used to handle smooth scrolling.
7453      */
7454     static abstract class AbsPositionScroller {
7455         public abstract void start(int position);
7456         public abstract void start(int position, int boundPosition);
7457         public abstract void startWithOffset(int position, int offset);
7458         public abstract void startWithOffset(int position, int offset, int duration);
7459         public abstract void stop();
7460     }
7461 
7462     /**
7463      * Default position scroller that simulates a fling.
7464      */
7465     class PositionScroller extends AbsPositionScroller implements Runnable {
7466         private static final int SCROLL_DURATION = 200;
7467 
7468         private static final int MOVE_DOWN_POS = 1;
7469         private static final int MOVE_UP_POS = 2;
7470         private static final int MOVE_DOWN_BOUND = 3;
7471         private static final int MOVE_UP_BOUND = 4;
7472         private static final int MOVE_OFFSET = 5;
7473 
7474         private int mMode;
7475         private int mTargetPos;
7476         private int mBoundPos;
7477         private int mLastSeenPos;
7478         private int mScrollDuration;
7479         private final int mExtraScroll;
7480 
7481         private int mOffsetFromTop;
7482 
PositionScroller()7483         PositionScroller() {
7484             mExtraScroll = ViewConfiguration.get(mContext).getScaledFadingEdgeLength();
7485         }
7486 
7487         @Override
start(final int position)7488         public void start(final int position) {
7489             stop();
7490 
7491             if (mDataChanged) {
7492                 // Wait until we're back in a stable state to try this.
7493                 mPositionScrollAfterLayout = new Runnable() {
7494                     @Override public void run() {
7495                         start(position);
7496                     }
7497                 };
7498                 return;
7499             }
7500 
7501             final int childCount = getChildCount();
7502             if (childCount == 0) {
7503                 // Can't scroll without children.
7504                 return;
7505             }
7506 
7507             final int firstPos = mFirstPosition;
7508             final int lastPos = firstPos + childCount - 1;
7509 
7510             int viewTravelCount;
7511             int clampedPosition = Math.max(0, Math.min(getCount() - 1, position));
7512             if (clampedPosition < firstPos) {
7513                 viewTravelCount = firstPos - clampedPosition + 1;
7514                 mMode = MOVE_UP_POS;
7515             } else if (clampedPosition > lastPos) {
7516                 viewTravelCount = clampedPosition - lastPos + 1;
7517                 mMode = MOVE_DOWN_POS;
7518             } else {
7519                 scrollToVisible(clampedPosition, INVALID_POSITION, SCROLL_DURATION);
7520                 return;
7521             }
7522 
7523             if (viewTravelCount > 0) {
7524                 mScrollDuration = SCROLL_DURATION / viewTravelCount;
7525             } else {
7526                 mScrollDuration = SCROLL_DURATION;
7527             }
7528             mTargetPos = clampedPosition;
7529             mBoundPos = INVALID_POSITION;
7530             mLastSeenPos = INVALID_POSITION;
7531 
7532             postOnAnimation(this);
7533         }
7534 
7535         @Override
start(final int position, final int boundPosition)7536         public void start(final int position, final int boundPosition) {
7537             stop();
7538 
7539             if (boundPosition == INVALID_POSITION) {
7540                 start(position);
7541                 return;
7542             }
7543 
7544             if (mDataChanged) {
7545                 // Wait until we're back in a stable state to try this.
7546                 mPositionScrollAfterLayout = new Runnable() {
7547                     @Override public void run() {
7548                         start(position, boundPosition);
7549                     }
7550                 };
7551                 return;
7552             }
7553 
7554             final int childCount = getChildCount();
7555             if (childCount == 0) {
7556                 // Can't scroll without children.
7557                 return;
7558             }
7559 
7560             final int firstPos = mFirstPosition;
7561             final int lastPos = firstPos + childCount - 1;
7562 
7563             int viewTravelCount;
7564             int clampedPosition = Math.max(0, Math.min(getCount() - 1, position));
7565             if (clampedPosition < firstPos) {
7566                 final int boundPosFromLast = lastPos - boundPosition;
7567                 if (boundPosFromLast < 1) {
7568                     // Moving would shift our bound position off the screen. Abort.
7569                     return;
7570                 }
7571 
7572                 final int posTravel = firstPos - clampedPosition + 1;
7573                 final int boundTravel = boundPosFromLast - 1;
7574                 if (boundTravel < posTravel) {
7575                     viewTravelCount = boundTravel;
7576                     mMode = MOVE_UP_BOUND;
7577                 } else {
7578                     viewTravelCount = posTravel;
7579                     mMode = MOVE_UP_POS;
7580                 }
7581             } else if (clampedPosition > lastPos) {
7582                 final int boundPosFromFirst = boundPosition - firstPos;
7583                 if (boundPosFromFirst < 1) {
7584                     // Moving would shift our bound position off the screen. Abort.
7585                     return;
7586                 }
7587 
7588                 final int posTravel = clampedPosition - lastPos + 1;
7589                 final int boundTravel = boundPosFromFirst - 1;
7590                 if (boundTravel < posTravel) {
7591                     viewTravelCount = boundTravel;
7592                     mMode = MOVE_DOWN_BOUND;
7593                 } else {
7594                     viewTravelCount = posTravel;
7595                     mMode = MOVE_DOWN_POS;
7596                 }
7597             } else {
7598                 scrollToVisible(clampedPosition, boundPosition, SCROLL_DURATION);
7599                 return;
7600             }
7601 
7602             if (viewTravelCount > 0) {
7603                 mScrollDuration = SCROLL_DURATION / viewTravelCount;
7604             } else {
7605                 mScrollDuration = SCROLL_DURATION;
7606             }
7607             mTargetPos = clampedPosition;
7608             mBoundPos = boundPosition;
7609             mLastSeenPos = INVALID_POSITION;
7610 
7611             postOnAnimation(this);
7612         }
7613 
7614         @Override
startWithOffset(int position, int offset)7615         public void startWithOffset(int position, int offset) {
7616             startWithOffset(position, offset, SCROLL_DURATION);
7617         }
7618 
7619         @Override
startWithOffset(final int position, int offset, final int duration)7620         public void startWithOffset(final int position, int offset, final int duration) {
7621             stop();
7622 
7623             if (mDataChanged) {
7624                 // Wait until we're back in a stable state to try this.
7625                 final int postOffset = offset;
7626                 mPositionScrollAfterLayout = new Runnable() {
7627                     @Override public void run() {
7628                         startWithOffset(position, postOffset, duration);
7629                     }
7630                 };
7631                 return;
7632             }
7633 
7634             final int childCount = getChildCount();
7635             if (childCount == 0) {
7636                 // Can't scroll without children.
7637                 return;
7638             }
7639 
7640             offset += getPaddingTop();
7641 
7642             mTargetPos = Math.max(0, Math.min(getCount() - 1, position));
7643             mOffsetFromTop = offset;
7644             mBoundPos = INVALID_POSITION;
7645             mLastSeenPos = INVALID_POSITION;
7646             mMode = MOVE_OFFSET;
7647 
7648             final int firstPos = mFirstPosition;
7649             final int lastPos = firstPos + childCount - 1;
7650 
7651             int viewTravelCount;
7652             if (mTargetPos < firstPos) {
7653                 viewTravelCount = firstPos - mTargetPos;
7654             } else if (mTargetPos > lastPos) {
7655                 viewTravelCount = mTargetPos - lastPos;
7656             } else {
7657                 // On-screen, just scroll.
7658                 final int targetTop = getChildAt(mTargetPos - firstPos).getTop();
7659                 smoothScrollBy(targetTop - offset, duration, true, false);
7660                 return;
7661             }
7662 
7663             // Estimate how many screens we should travel
7664             final float screenTravelCount = (float) viewTravelCount / childCount;
7665             mScrollDuration = screenTravelCount < 1 ?
7666                     duration : (int) (duration / screenTravelCount);
7667             mLastSeenPos = INVALID_POSITION;
7668 
7669             postOnAnimation(this);
7670         }
7671 
7672         /**
7673          * Scroll such that targetPos is in the visible padded region without scrolling
7674          * boundPos out of view. Assumes targetPos is onscreen.
7675          */
7676         private void scrollToVisible(int targetPos, int boundPos, int duration) {
7677             final int firstPos = mFirstPosition;
7678             final int childCount = getChildCount();
7679             final int lastPos = firstPos + childCount - 1;
7680             final int paddedTop = mListPadding.top;
7681             final int paddedBottom = getHeight() - mListPadding.bottom;
7682 
7683             if (targetPos < firstPos || targetPos > lastPos) {
7684                 Log.w(TAG, "scrollToVisible called with targetPos " + targetPos +
7685                         " not visible [" + firstPos + ", " + lastPos + "]");
7686             }
7687             if (boundPos < firstPos || boundPos > lastPos) {
7688                 // boundPos doesn't matter, it's already offscreen.
7689                 boundPos = INVALID_POSITION;
7690             }
7691 
7692             final View targetChild = getChildAt(targetPos - firstPos);
7693             final int targetTop = targetChild.getTop();
7694             final int targetBottom = targetChild.getBottom();
7695             int scrollBy = 0;
7696 
7697             if (targetBottom > paddedBottom) {
7698                 scrollBy = targetBottom - paddedBottom;
7699             }
7700             if (targetTop < paddedTop) {
7701                 scrollBy = targetTop - paddedTop;
7702             }
7703 
7704             if (scrollBy == 0) {
7705                 return;
7706             }
7707 
7708             if (boundPos >= 0) {
7709                 final View boundChild = getChildAt(boundPos - firstPos);
7710                 final int boundTop = boundChild.getTop();
7711                 final int boundBottom = boundChild.getBottom();
7712                 final int absScroll = Math.abs(scrollBy);
7713 
7714                 if (scrollBy < 0 && boundBottom + absScroll > paddedBottom) {
7715                     // Don't scroll the bound view off the bottom of the screen.
7716                     scrollBy = Math.max(0, boundBottom - paddedBottom);
7717                 } else if (scrollBy > 0 && boundTop - absScroll < paddedTop) {
7718                     // Don't scroll the bound view off the top of the screen.
7719                     scrollBy = Math.min(0, boundTop - paddedTop);
7720                 }
7721             }
7722 
7723             smoothScrollBy(scrollBy, duration);
7724         }
7725 
7726         @Override
stop()7727         public void stop() {
7728             removeCallbacks(this);
7729         }
7730 
7731         @Override
run()7732         public void run() {
7733             final int listHeight = getHeight();
7734             final int firstPos = mFirstPosition;
7735 
7736             switch (mMode) {
7737             case MOVE_DOWN_POS: {
7738                 final int lastViewIndex = getChildCount() - 1;
7739                 final int lastPos = firstPos + lastViewIndex;
7740 
7741                 if (lastViewIndex < 0) {
7742                     return;
7743                 }
7744 
7745                 if (lastPos == mLastSeenPos) {
7746                     // No new views, let things keep going.
7747                     postOnAnimation(this);
7748                     return;
7749                 }
7750 
7751                 final View lastView = getChildAt(lastViewIndex);
7752                 final int lastViewHeight = lastView.getHeight();
7753                 final int lastViewTop = lastView.getTop();
7754                 final int lastViewPixelsShowing = listHeight - lastViewTop;
7755                 final int extraScroll = lastPos < mItemCount - 1 ?
7756                         Math.max(mListPadding.bottom, mExtraScroll) : mListPadding.bottom;
7757 
7758                 final int scrollBy = lastViewHeight - lastViewPixelsShowing + extraScroll;
7759                 smoothScrollBy(scrollBy, mScrollDuration, true, lastPos < mTargetPos);
7760 
7761                 mLastSeenPos = lastPos;
7762                 if (lastPos < mTargetPos) {
7763                     postOnAnimation(this);
7764                 }
7765                 break;
7766             }
7767 
7768             case MOVE_DOWN_BOUND: {
7769                 final int nextViewIndex = 1;
7770                 final int childCount = getChildCount();
7771 
7772                 if (firstPos == mBoundPos || childCount <= nextViewIndex
7773                         || firstPos + childCount >= mItemCount) {
7774                     reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
7775                     return;
7776                 }
7777                 final int nextPos = firstPos + nextViewIndex;
7778 
7779                 if (nextPos == mLastSeenPos) {
7780                     // No new views, let things keep going.
7781                     postOnAnimation(this);
7782                     return;
7783                 }
7784 
7785                 final View nextView = getChildAt(nextViewIndex);
7786                 final int nextViewHeight = nextView.getHeight();
7787                 final int nextViewTop = nextView.getTop();
7788                 final int extraScroll = Math.max(mListPadding.bottom, mExtraScroll);
7789                 if (nextPos < mBoundPos) {
7790                     smoothScrollBy(Math.max(0, nextViewHeight + nextViewTop - extraScroll),
7791                             mScrollDuration, true, true);
7792 
7793                     mLastSeenPos = nextPos;
7794 
7795                     postOnAnimation(this);
7796                 } else  {
7797                     if (nextViewTop > extraScroll) {
7798                         smoothScrollBy(nextViewTop - extraScroll, mScrollDuration, true, false);
7799                     } else {
7800                         reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
7801                     }
7802                 }
7803                 break;
7804             }
7805 
7806             case MOVE_UP_POS: {
7807                 if (firstPos == mLastSeenPos) {
7808                     // No new views, let things keep going.
7809                     postOnAnimation(this);
7810                     return;
7811                 }
7812 
7813                 final View firstView = getChildAt(0);
7814                 if (firstView == null) {
7815                     return;
7816                 }
7817                 final int firstViewTop = firstView.getTop();
7818                 final int extraScroll = firstPos > 0 ?
7819                         Math.max(mExtraScroll, mListPadding.top) : mListPadding.top;
7820 
7821                 smoothScrollBy(firstViewTop - extraScroll, mScrollDuration, true,
7822                         firstPos > mTargetPos);
7823 
7824                 mLastSeenPos = firstPos;
7825 
7826                 if (firstPos > mTargetPos) {
7827                     postOnAnimation(this);
7828                 }
7829                 break;
7830             }
7831 
7832             case MOVE_UP_BOUND: {
7833                 final int lastViewIndex = getChildCount() - 2;
7834                 if (lastViewIndex < 0) {
7835                     return;
7836                 }
7837                 final int lastPos = firstPos + lastViewIndex;
7838 
7839                 if (lastPos == mLastSeenPos) {
7840                     // No new views, let things keep going.
7841                     postOnAnimation(this);
7842                     return;
7843                 }
7844 
7845                 final View lastView = getChildAt(lastViewIndex);
7846                 final int lastViewHeight = lastView.getHeight();
7847                 final int lastViewTop = lastView.getTop();
7848                 final int lastViewPixelsShowing = listHeight - lastViewTop;
7849                 final int extraScroll = Math.max(mListPadding.top, mExtraScroll);
7850                 mLastSeenPos = lastPos;
7851                 if (lastPos > mBoundPos) {
7852                     smoothScrollBy(-(lastViewPixelsShowing - extraScroll), mScrollDuration, true,
7853                             true);
7854                     postOnAnimation(this);
7855                 } else {
7856                     final int bottom = listHeight - extraScroll;
7857                     final int lastViewBottom = lastViewTop + lastViewHeight;
7858                     if (bottom > lastViewBottom) {
7859                         smoothScrollBy(-(bottom - lastViewBottom), mScrollDuration, true, false);
7860                     } else {
7861                         reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
7862                     }
7863                 }
7864                 break;
7865             }
7866 
7867             case MOVE_OFFSET: {
7868                 if (mLastSeenPos == firstPos) {
7869                     // No new views, let things keep going.
7870                     postOnAnimation(this);
7871                     return;
7872                 }
7873 
7874                 mLastSeenPos = firstPos;
7875 
7876                 final int childCount = getChildCount();
7877 
7878                 if (childCount <= 0) {
7879                     return;
7880                 }
7881 
7882                 final int position = mTargetPos;
7883                 final int lastPos = firstPos + childCount - 1;
7884 
7885                 // Account for the visible "portion" of the first / last child when we estimate
7886                 // how many screens we should travel to reach our target
7887                 final View firstChild = getChildAt(0);
7888                 final int firstChildHeight = firstChild.getHeight();
7889                 final View lastChild = getChildAt(childCount - 1);
7890                 final int lastChildHeight = lastChild.getHeight();
7891                 final float firstPositionVisiblePart = (firstChildHeight == 0.0f) ? 1.0f
7892                         : (float) (firstChildHeight + firstChild.getTop()) / firstChildHeight;
7893                 final float lastPositionVisiblePart = (lastChildHeight == 0.0f) ? 1.0f
7894                         : (float) (lastChildHeight + getHeight() - lastChild.getBottom())
7895                                 / lastChildHeight;
7896 
7897                 float viewTravelCount = 0;
7898                 if (position < firstPos) {
7899                     viewTravelCount = firstPos - position + (1.0f - firstPositionVisiblePart) + 1;
7900                 } else if (position > lastPos) {
7901                     viewTravelCount = position - lastPos + (1.0f - lastPositionVisiblePart);
7902                 }
7903 
7904                 // Estimate how many screens we should travel
7905                 final float screenTravelCount = viewTravelCount / childCount;
7906 
7907                 final float modifier = Math.min(Math.abs(screenTravelCount), 1.f);
7908                 if (position < firstPos) {
7909                     final int distance = (int) (-getHeight() * modifier);
7910                     final int duration = (int) (mScrollDuration * modifier);
7911                     smoothScrollBy(distance, duration, true, true);
7912                     postOnAnimation(this);
7913                 } else if (position > lastPos) {
7914                     final int distance = (int) (getHeight() * modifier);
7915                     final int duration = (int) (mScrollDuration * modifier);
7916                     smoothScrollBy(distance, duration, true, true);
7917                     postOnAnimation(this);
7918                 } else {
7919                     // On-screen, just scroll.
7920                     final int targetTop = getChildAt(position - firstPos).getTop();
7921                     final int distance = targetTop - mOffsetFromTop;
7922                     final int duration = (int) (mScrollDuration *
7923                             ((float) Math.abs(distance) / getHeight()));
7924                     smoothScrollBy(distance, duration, true, false);
7925                 }
7926                 break;
7927             }
7928 
7929             default:
7930                 break;
7931             }
7932         }
7933     }
7934 }
7935