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