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