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