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