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