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