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