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