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