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