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