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