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