AbsListView.java revision 5b2081dc41cccd76780a2cb4e9a973505c13446c
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    /**
2847     * A base class for Runnables that will check that their view is still attached to
2848     * the original window as when the Runnable was created.
2849     *
2850     */
2851    private class WindowRunnnable {
2852        private int mOriginalAttachCount;
2853
2854        public void rememberWindowAttachCount() {
2855            mOriginalAttachCount = getWindowAttachCount();
2856        }
2857
2858        public boolean sameWindow() {
2859            return getWindowAttachCount() == mOriginalAttachCount;
2860        }
2861    }
2862
2863    private class PerformClick extends WindowRunnnable implements Runnable {
2864        int mClickMotionPosition;
2865
2866        @Override
2867        public void run() {
2868            // The data has changed since we posted this action in the event queue,
2869            // bail out before bad things happen
2870            if (mDataChanged) return;
2871
2872            final ListAdapter adapter = mAdapter;
2873            final int motionPosition = mClickMotionPosition;
2874            if (adapter != null && mItemCount > 0 &&
2875                    motionPosition != INVALID_POSITION &&
2876                    motionPosition < adapter.getCount() && sameWindow()) {
2877                final View view = getChildAt(motionPosition - mFirstPosition);
2878                // If there is no view, something bad happened (the view scrolled off the
2879                // screen, etc.) and we should cancel the click
2880                if (view != null) {
2881                    performItemClick(view, motionPosition, adapter.getItemId(motionPosition));
2882                }
2883            }
2884        }
2885    }
2886
2887    private class CheckForLongPress extends WindowRunnnable implements Runnable {
2888        @Override
2889        public void run() {
2890            final int motionPosition = mMotionPosition;
2891            final View child = getChildAt(motionPosition - mFirstPosition);
2892            if (child != null) {
2893                final int longPressPosition = mMotionPosition;
2894                final long longPressId = mAdapter.getItemId(mMotionPosition);
2895
2896                boolean handled = false;
2897                if (sameWindow() && !mDataChanged) {
2898                    handled = performLongPress(child, longPressPosition, longPressId);
2899                }
2900                if (handled) {
2901                    mTouchMode = TOUCH_MODE_REST;
2902                    setPressed(false);
2903                    child.setPressed(false);
2904                } else {
2905                    mTouchMode = TOUCH_MODE_DONE_WAITING;
2906                }
2907            }
2908        }
2909    }
2910
2911    private class CheckForKeyLongPress extends WindowRunnnable implements Runnable {
2912        @Override
2913        public void run() {
2914            if (isPressed() && mSelectedPosition >= 0) {
2915                int index = mSelectedPosition - mFirstPosition;
2916                View v = getChildAt(index);
2917
2918                if (!mDataChanged) {
2919                    boolean handled = false;
2920                    if (sameWindow()) {
2921                        handled = performLongPress(v, mSelectedPosition, mSelectedRowId);
2922                    }
2923                    if (handled) {
2924                        setPressed(false);
2925                        v.setPressed(false);
2926                    }
2927                } else {
2928                    setPressed(false);
2929                    if (v != null) v.setPressed(false);
2930                }
2931            }
2932        }
2933    }
2934
2935    boolean performLongPress(final View child,
2936            final int longPressPosition, final long longPressId) {
2937        // CHOICE_MODE_MULTIPLE_MODAL takes over long press.
2938        if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
2939            if (mChoiceActionMode == null &&
2940                    (mChoiceActionMode = startActionMode(mMultiChoiceModeCallback)) != null) {
2941                setItemChecked(longPressPosition, true);
2942                performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
2943            }
2944            return true;
2945        }
2946
2947        boolean handled = false;
2948        if (mOnItemLongClickListener != null) {
2949            handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, child,
2950                    longPressPosition, longPressId);
2951        }
2952        if (!handled) {
2953            mContextMenuInfo = createContextMenuInfo(child, longPressPosition, longPressId);
2954            handled = super.showContextMenuForChild(AbsListView.this);
2955        }
2956        if (handled) {
2957            performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
2958        }
2959        return handled;
2960    }
2961
2962    @Override
2963    protected ContextMenuInfo getContextMenuInfo() {
2964        return mContextMenuInfo;
2965    }
2966
2967    /** @hide */
2968    @Override
2969    public boolean showContextMenu(float x, float y, int metaState) {
2970        final int position = pointToPosition((int)x, (int)y);
2971        if (position != INVALID_POSITION) {
2972            final long id = mAdapter.getItemId(position);
2973            View child = getChildAt(position - mFirstPosition);
2974            if (child != null) {
2975                mContextMenuInfo = createContextMenuInfo(child, position, id);
2976                return super.showContextMenuForChild(AbsListView.this);
2977            }
2978        }
2979        return super.showContextMenu(x, y, metaState);
2980    }
2981
2982    @Override
2983    public boolean showContextMenuForChild(View originalView) {
2984        final int longPressPosition = getPositionForView(originalView);
2985        if (longPressPosition >= 0) {
2986            final long longPressId = mAdapter.getItemId(longPressPosition);
2987            boolean handled = false;
2988
2989            if (mOnItemLongClickListener != null) {
2990                handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, originalView,
2991                        longPressPosition, longPressId);
2992            }
2993            if (!handled) {
2994                mContextMenuInfo = createContextMenuInfo(
2995                        getChildAt(longPressPosition - mFirstPosition),
2996                        longPressPosition, longPressId);
2997                handled = super.showContextMenuForChild(originalView);
2998            }
2999
3000            return handled;
3001        }
3002        return false;
3003    }
3004
3005    @Override
3006    public boolean onKeyDown(int keyCode, KeyEvent event) {
3007        return false;
3008    }
3009
3010    @Override
3011    public boolean onKeyUp(int keyCode, KeyEvent event) {
3012        if (KeyEvent.isConfirmKey(keyCode)) {
3013            if (!isEnabled()) {
3014                return true;
3015            }
3016            if (isClickable() && isPressed() &&
3017                    mSelectedPosition >= 0 && mAdapter != null &&
3018                    mSelectedPosition < mAdapter.getCount()) {
3019
3020                final View view = getChildAt(mSelectedPosition - mFirstPosition);
3021                if (view != null) {
3022                    performItemClick(view, mSelectedPosition, mSelectedRowId);
3023                    view.setPressed(false);
3024                }
3025                setPressed(false);
3026                return true;
3027            }
3028        }
3029        return super.onKeyUp(keyCode, event);
3030    }
3031
3032    @Override
3033    protected void dispatchSetPressed(boolean pressed) {
3034        // Don't dispatch setPressed to our children. We call setPressed on ourselves to
3035        // get the selector in the right state, but we don't want to press each child.
3036    }
3037
3038    /**
3039     * Maps a point to a position in the list.
3040     *
3041     * @param x X in local coordinate
3042     * @param y Y in local coordinate
3043     * @return The position of the item which contains the specified point, or
3044     *         {@link #INVALID_POSITION} if the point does not intersect an item.
3045     */
3046    public int pointToPosition(int x, int y) {
3047        Rect frame = mTouchFrame;
3048        if (frame == null) {
3049            mTouchFrame = new Rect();
3050            frame = mTouchFrame;
3051        }
3052
3053        final int count = getChildCount();
3054        for (int i = count - 1; i >= 0; i--) {
3055            final View child = getChildAt(i);
3056            if (child.getVisibility() == View.VISIBLE) {
3057                child.getHitRect(frame);
3058                if (frame.contains(x, y)) {
3059                    return mFirstPosition + i;
3060                }
3061            }
3062        }
3063        return INVALID_POSITION;
3064    }
3065
3066
3067    /**
3068     * Maps a point to a the rowId of the item which intersects that point.
3069     *
3070     * @param x X in local coordinate
3071     * @param y Y in local coordinate
3072     * @return The rowId of the item which contains the specified point, or {@link #INVALID_ROW_ID}
3073     *         if the point does not intersect an item.
3074     */
3075    public long pointToRowId(int x, int y) {
3076        int position = pointToPosition(x, y);
3077        if (position >= 0) {
3078            return mAdapter.getItemId(position);
3079        }
3080        return INVALID_ROW_ID;
3081    }
3082
3083    final class CheckForTap implements Runnable {
3084        @Override
3085        public void run() {
3086            if (mTouchMode == TOUCH_MODE_DOWN) {
3087                mTouchMode = TOUCH_MODE_TAP;
3088                final View child = getChildAt(mMotionPosition - mFirstPosition);
3089                if (child != null && !child.hasFocusable()) {
3090                    mLayoutMode = LAYOUT_NORMAL;
3091
3092                    if (!mDataChanged) {
3093                        child.setPressed(true);
3094                        setPressed(true);
3095                        layoutChildren();
3096                        positionSelector(mMotionPosition, child);
3097                        refreshDrawableState();
3098
3099                        final int longPressTimeout = ViewConfiguration.getLongPressTimeout();
3100                        final boolean longClickable = isLongClickable();
3101
3102                        if (mSelector != null) {
3103                            Drawable d = mSelector.getCurrent();
3104                            if (d != null && d instanceof TransitionDrawable) {
3105                                if (longClickable) {
3106                                    ((TransitionDrawable) d).startTransition(longPressTimeout);
3107                                } else {
3108                                    ((TransitionDrawable) d).resetTransition();
3109                                }
3110                            }
3111                        }
3112
3113                        if (longClickable) {
3114                            if (mPendingCheckForLongPress == null) {
3115                                mPendingCheckForLongPress = new CheckForLongPress();
3116                            }
3117                            mPendingCheckForLongPress.rememberWindowAttachCount();
3118                            postDelayed(mPendingCheckForLongPress, longPressTimeout);
3119                        } else {
3120                            mTouchMode = TOUCH_MODE_DONE_WAITING;
3121                        }
3122                    } else {
3123                        mTouchMode = TOUCH_MODE_DONE_WAITING;
3124                    }
3125                }
3126            }
3127        }
3128    }
3129
3130    private boolean startScrollIfNeeded(int y) {
3131        // Check if we have moved far enough that it looks more like a
3132        // scroll than a tap
3133        final int deltaY = y - mMotionY;
3134        final int distance = Math.abs(deltaY);
3135        final boolean overscroll = mScrollY != 0;
3136        if (overscroll || distance > mTouchSlop) {
3137            createScrollingCache();
3138            if (overscroll) {
3139                mTouchMode = TOUCH_MODE_OVERSCROLL;
3140                mMotionCorrection = 0;
3141            } else {
3142                mTouchMode = TOUCH_MODE_SCROLL;
3143                mMotionCorrection = deltaY > 0 ? mTouchSlop : -mTouchSlop;
3144            }
3145            removeCallbacks(mPendingCheckForLongPress);
3146            setPressed(false);
3147            final View motionView = getChildAt(mMotionPosition - mFirstPosition);
3148            if (motionView != null) {
3149                motionView.setPressed(false);
3150            }
3151            reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
3152            // Time to start stealing events! Once we've stolen them, don't let anyone
3153            // steal from us
3154            final ViewParent parent = getParent();
3155            if (parent != null) {
3156                parent.requestDisallowInterceptTouchEvent(true);
3157            }
3158            scrollIfNeeded(y);
3159            return true;
3160        }
3161
3162        return false;
3163    }
3164
3165    private void scrollIfNeeded(int y) {
3166        final int rawDeltaY = y - mMotionY;
3167        final int deltaY = rawDeltaY - mMotionCorrection;
3168        int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY;
3169
3170        if (mTouchMode == TOUCH_MODE_SCROLL) {
3171            if (PROFILE_SCROLLING) {
3172                if (!mScrollProfilingStarted) {
3173                    Debug.startMethodTracing("AbsListViewScroll");
3174                    mScrollProfilingStarted = true;
3175                }
3176            }
3177
3178            if (mScrollStrictSpan == null) {
3179                // If it's non-null, we're already in a scroll.
3180                mScrollStrictSpan = StrictMode.enterCriticalSpan("AbsListView-scroll");
3181            }
3182
3183            if (y != mLastY) {
3184                // We may be here after stopping a fling and continuing to scroll.
3185                // If so, we haven't disallowed intercepting touch events yet.
3186                // Make sure that we do so in case we're in a parent that can intercept.
3187                if ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) == 0 &&
3188                        Math.abs(rawDeltaY) > mTouchSlop) {
3189                    final ViewParent parent = getParent();
3190                    if (parent != null) {
3191                        parent.requestDisallowInterceptTouchEvent(true);
3192                    }
3193                }
3194
3195                final int motionIndex;
3196                if (mMotionPosition >= 0) {
3197                    motionIndex = mMotionPosition - mFirstPosition;
3198                } else {
3199                    // If we don't have a motion position that we can reliably track,
3200                    // pick something in the middle to make a best guess at things below.
3201                    motionIndex = getChildCount() / 2;
3202                }
3203
3204                int motionViewPrevTop = 0;
3205                View motionView = this.getChildAt(motionIndex);
3206                if (motionView != null) {
3207                    motionViewPrevTop = motionView.getTop();
3208                }
3209
3210                // No need to do all this work if we're not going to move anyway
3211                boolean atEdge = false;
3212                if (incrementalDeltaY != 0) {
3213                    atEdge = trackMotionScroll(deltaY, incrementalDeltaY);
3214                }
3215
3216                // Check to see if we have bumped into the scroll limit
3217                motionView = this.getChildAt(motionIndex);
3218                if (motionView != null) {
3219                    // Check if the top of the motion view is where it is
3220                    // supposed to be
3221                    final int motionViewRealTop = motionView.getTop();
3222                    if (atEdge) {
3223                        // Apply overscroll
3224
3225                        int overscroll = -incrementalDeltaY -
3226                                (motionViewRealTop - motionViewPrevTop);
3227                        overScrollBy(0, overscroll, 0, mScrollY, 0, 0,
3228                                0, mOverscrollDistance, true);
3229                        if (Math.abs(mOverscrollDistance) == Math.abs(mScrollY)) {
3230                            // Don't allow overfling if we're at the edge.
3231                            if (mVelocityTracker != null) {
3232                                mVelocityTracker.clear();
3233                            }
3234                        }
3235
3236                        final int overscrollMode = getOverScrollMode();
3237                        if (overscrollMode == OVER_SCROLL_ALWAYS ||
3238                                (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
3239                                        !contentFits())) {
3240                            mDirection = 0; // Reset when entering overscroll.
3241                            mTouchMode = TOUCH_MODE_OVERSCROLL;
3242                            if (rawDeltaY > 0) {
3243                                mEdgeGlowTop.onPull((float) overscroll / getHeight());
3244                                if (!mEdgeGlowBottom.isFinished()) {
3245                                    mEdgeGlowBottom.onRelease();
3246                                }
3247                                invalidate(mEdgeGlowTop.getBounds(false));
3248                            } else if (rawDeltaY < 0) {
3249                                mEdgeGlowBottom.onPull((float) overscroll / getHeight());
3250                                if (!mEdgeGlowTop.isFinished()) {
3251                                    mEdgeGlowTop.onRelease();
3252                                }
3253                                invalidate(mEdgeGlowBottom.getBounds(true));
3254                            }
3255                        }
3256                    }
3257                    mMotionY = y;
3258                }
3259                mLastY = y;
3260            }
3261        } else if (mTouchMode == TOUCH_MODE_OVERSCROLL) {
3262            if (y != mLastY) {
3263                final int oldScroll = mScrollY;
3264                final int newScroll = oldScroll - incrementalDeltaY;
3265                int newDirection = y > mLastY ? 1 : -1;
3266
3267                if (mDirection == 0) {
3268                    mDirection = newDirection;
3269                }
3270
3271                int overScrollDistance = -incrementalDeltaY;
3272                if ((newScroll < 0 && oldScroll >= 0) || (newScroll > 0 && oldScroll <= 0)) {
3273                    overScrollDistance = -oldScroll;
3274                    incrementalDeltaY += overScrollDistance;
3275                } else {
3276                    incrementalDeltaY = 0;
3277                }
3278
3279                if (overScrollDistance != 0) {
3280                    overScrollBy(0, overScrollDistance, 0, mScrollY, 0, 0,
3281                            0, mOverscrollDistance, true);
3282                    final int overscrollMode = getOverScrollMode();
3283                    if (overscrollMode == OVER_SCROLL_ALWAYS ||
3284                            (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
3285                                    !contentFits())) {
3286                        if (rawDeltaY > 0) {
3287                            mEdgeGlowTop.onPull((float) overScrollDistance / getHeight());
3288                            if (!mEdgeGlowBottom.isFinished()) {
3289                                mEdgeGlowBottom.onRelease();
3290                            }
3291                            invalidate(mEdgeGlowTop.getBounds(false));
3292                        } else if (rawDeltaY < 0) {
3293                            mEdgeGlowBottom.onPull((float) overScrollDistance / getHeight());
3294                            if (!mEdgeGlowTop.isFinished()) {
3295                                mEdgeGlowTop.onRelease();
3296                            }
3297                            invalidate(mEdgeGlowBottom.getBounds(true));
3298                        }
3299                    }
3300                }
3301
3302                if (incrementalDeltaY != 0) {
3303                    // Coming back to 'real' list scrolling
3304                    if (mScrollY != 0) {
3305                        mScrollY = 0;
3306                        invalidateParentIfNeeded();
3307                    }
3308
3309                    trackMotionScroll(incrementalDeltaY, incrementalDeltaY);
3310
3311                    mTouchMode = TOUCH_MODE_SCROLL;
3312
3313                    // We did not scroll the full amount. Treat this essentially like the
3314                    // start of a new touch scroll
3315                    final int motionPosition = findClosestMotionRow(y);
3316
3317                    mMotionCorrection = 0;
3318                    View motionView = getChildAt(motionPosition - mFirstPosition);
3319                    mMotionViewOriginalTop = motionView != null ? motionView.getTop() : 0;
3320                    mMotionY = y;
3321                    mMotionPosition = motionPosition;
3322                }
3323                mLastY = y;
3324                mDirection = newDirection;
3325            }
3326        }
3327    }
3328
3329    @Override
3330    public void onTouchModeChanged(boolean isInTouchMode) {
3331        if (isInTouchMode) {
3332            // Get rid of the selection when we enter touch mode
3333            hideSelector();
3334            // Layout, but only if we already have done so previously.
3335            // (Otherwise may clobber a LAYOUT_SYNC layout that was requested to restore
3336            // state.)
3337            if (getHeight() > 0 && getChildCount() > 0) {
3338                // We do not lose focus initiating a touch (since AbsListView is focusable in
3339                // touch mode). Force an initial layout to get rid of the selection.
3340                layoutChildren();
3341            }
3342            updateSelectorState();
3343        } else {
3344            int touchMode = mTouchMode;
3345            if (touchMode == TOUCH_MODE_OVERSCROLL || touchMode == TOUCH_MODE_OVERFLING) {
3346                if (mFlingRunnable != null) {
3347                    mFlingRunnable.endFling();
3348                }
3349                if (mPositionScroller != null) {
3350                    mPositionScroller.stop();
3351                }
3352
3353                if (mScrollY != 0) {
3354                    mScrollY = 0;
3355                    invalidateParentCaches();
3356                    finishGlows();
3357                    invalidate();
3358                }
3359            }
3360        }
3361    }
3362
3363    @Override
3364    public boolean onTouchEvent(MotionEvent ev) {
3365        if (!isEnabled()) {
3366            // A disabled view that is clickable still consumes the touch
3367            // events, it just doesn't respond to them.
3368            return isClickable() || isLongClickable();
3369        }
3370
3371        if (mPositionScroller != null) {
3372            mPositionScroller.stop();
3373        }
3374
3375        if (!mIsAttached) {
3376            // Something isn't right.
3377            // Since we rely on being attached to get data set change notifications,
3378            // don't risk doing anything where we might try to resync and find things
3379            // in a bogus state.
3380            return false;
3381        }
3382
3383        if (mFastScroller != null) {
3384            boolean intercepted = mFastScroller.onTouchEvent(ev);
3385            if (intercepted) {
3386                return true;
3387            }
3388        }
3389
3390        initVelocityTrackerIfNotExists();
3391        mVelocityTracker.addMovement(ev);
3392
3393        final int actionMasked = ev.getActionMasked();
3394        switch (actionMasked) {
3395            case MotionEvent.ACTION_DOWN: {
3396                onTouchDown(ev);
3397                break;
3398            }
3399
3400            case MotionEvent.ACTION_MOVE: {
3401                onTouchMove(ev);
3402                break;
3403            }
3404
3405            case MotionEvent.ACTION_UP: {
3406                onTouchUp(ev);
3407                break;
3408            }
3409
3410            case MotionEvent.ACTION_CANCEL: {
3411                onTouchCancel();
3412                break;
3413            }
3414
3415            case MotionEvent.ACTION_POINTER_UP: {
3416                onSecondaryPointerUp(ev);
3417                final int x = mMotionX;
3418                final int y = mMotionY;
3419                final int motionPosition = pointToPosition(x, y);
3420                if (motionPosition >= 0) {
3421                    // Remember where the motion event started
3422                    final View child = getChildAt(motionPosition - mFirstPosition);
3423                    mMotionViewOriginalTop = child.getTop();
3424                    mMotionPosition = motionPosition;
3425                }
3426                mLastY = y;
3427                break;
3428            }
3429
3430            case MotionEvent.ACTION_POINTER_DOWN: {
3431                // New pointers take over dragging duties
3432                final int index = ev.getActionIndex();
3433                final int id = ev.getPointerId(index);
3434                final int x = (int) ev.getX(index);
3435                final int y = (int) ev.getY(index);
3436                mMotionCorrection = 0;
3437                mActivePointerId = id;
3438                mMotionX = x;
3439                mMotionY = y;
3440                final int motionPosition = pointToPosition(x, y);
3441                if (motionPosition >= 0) {
3442                    // Remember where the motion event started
3443                    final View child = getChildAt(motionPosition - mFirstPosition);
3444                    mMotionViewOriginalTop = child.getTop();
3445                    mMotionPosition = motionPosition;
3446                }
3447                mLastY = y;
3448                break;
3449            }
3450        }
3451
3452        return true;
3453    }
3454
3455    private void onTouchDown(MotionEvent ev) {
3456        mActivePointerId = ev.getPointerId(0);
3457
3458        if (mTouchMode == TOUCH_MODE_OVERFLING) {
3459            // Stopped the fling. It is a scroll.
3460            mFlingRunnable.endFling();
3461            if (mPositionScroller != null) {
3462                mPositionScroller.stop();
3463            }
3464            mTouchMode = TOUCH_MODE_OVERSCROLL;
3465            mMotionX = (int) ev.getX();
3466            mMotionY = (int) ev.getY();
3467            mLastY = mMotionY;
3468            mMotionCorrection = 0;
3469            mDirection = 0;
3470        } else {
3471            final int x = (int) ev.getX();
3472            final int y = (int) ev.getY();
3473            int motionPosition = pointToPosition(x, y);
3474
3475            if (!mDataChanged) {
3476                if (mTouchMode == TOUCH_MODE_FLING) {
3477                    // Stopped a fling. It is a scroll.
3478                    createScrollingCache();
3479                    mTouchMode = TOUCH_MODE_SCROLL;
3480                    mMotionCorrection = 0;
3481                    motionPosition = findMotionRow(y);
3482                    mFlingRunnable.flywheelTouch();
3483                } else if ((motionPosition >= 0) && getAdapter().isEnabled(motionPosition)) {
3484                    // User clicked on an actual view (and was not stopping a
3485                    // fling). It might be a click or a scroll. Assume it is a
3486                    // click until proven otherwise.
3487                    mTouchMode = TOUCH_MODE_DOWN;
3488
3489                    // FIXME Debounce
3490                    if (mPendingCheckForTap == null) {
3491                        mPendingCheckForTap = new CheckForTap();
3492                    }
3493
3494                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
3495                }
3496            }
3497
3498            if (motionPosition >= 0) {
3499                // Remember where the motion event started
3500                final View v = getChildAt(motionPosition - mFirstPosition);
3501                mMotionViewOriginalTop = v.getTop();
3502            }
3503
3504            mMotionX = x;
3505            mMotionY = y;
3506            mMotionPosition = motionPosition;
3507            mLastY = Integer.MIN_VALUE;
3508        }
3509
3510        if (mTouchMode == TOUCH_MODE_DOWN && mMotionPosition != INVALID_POSITION
3511                && performButtonActionOnTouchDown(ev)) {
3512            removeCallbacks(mPendingCheckForTap);
3513        }
3514    }
3515
3516    private void onTouchMove(MotionEvent ev) {
3517        int pointerIndex = ev.findPointerIndex(mActivePointerId);
3518        if (pointerIndex == -1) {
3519            pointerIndex = 0;
3520            mActivePointerId = ev.getPointerId(pointerIndex);
3521        }
3522
3523        if (mDataChanged) {
3524            // Re-sync everything if data has been changed
3525            // since the scroll operation can query the adapter.
3526            layoutChildren();
3527        }
3528
3529        final int y = (int) ev.getY(pointerIndex);
3530
3531        switch (mTouchMode) {
3532            case TOUCH_MODE_DOWN:
3533            case TOUCH_MODE_TAP:
3534            case TOUCH_MODE_DONE_WAITING:
3535                // Check if we have moved far enough that it looks more like a
3536                // scroll than a tap. If so, we'll enter scrolling mode.
3537                if (startScrollIfNeeded(y)) {
3538                    break;
3539                }
3540                // Otherwise, check containment within list bounds. If we're
3541                // outside bounds, cancel any active presses.
3542                final float x = ev.getX(pointerIndex);
3543                if (!pointInView(x, y, mTouchSlop)) {
3544                    setPressed(false);
3545                    final View motionView = getChildAt(mMotionPosition - mFirstPosition);
3546                    if (motionView != null) {
3547                        motionView.setPressed(false);
3548                    }
3549                    removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
3550                            mPendingCheckForTap : mPendingCheckForLongPress);
3551                    mTouchMode = TOUCH_MODE_DONE_WAITING;
3552                    updateSelectorState();
3553                }
3554                break;
3555            case TOUCH_MODE_SCROLL:
3556            case TOUCH_MODE_OVERSCROLL:
3557                scrollIfNeeded(y);
3558                break;
3559        }
3560    }
3561
3562    private void onTouchUp(MotionEvent ev) {
3563        switch (mTouchMode) {
3564        case TOUCH_MODE_DOWN:
3565        case TOUCH_MODE_TAP:
3566        case TOUCH_MODE_DONE_WAITING:
3567            final int motionPosition = mMotionPosition;
3568            final View child = getChildAt(motionPosition - mFirstPosition);
3569            if (child != null) {
3570                if (mTouchMode != TOUCH_MODE_DOWN) {
3571                    child.setPressed(false);
3572                }
3573
3574                final float x = ev.getX();
3575                final boolean inList = x > mListPadding.left && x < getWidth() - mListPadding.right;
3576                if (inList && !child.hasFocusable()) {
3577                    if (mPerformClick == null) {
3578                        mPerformClick = new PerformClick();
3579                    }
3580
3581                    final AbsListView.PerformClick performClick = mPerformClick;
3582                    performClick.mClickMotionPosition = motionPosition;
3583                    performClick.rememberWindowAttachCount();
3584
3585                    mResurrectToPosition = motionPosition;
3586
3587                    if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) {
3588                        removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
3589                                mPendingCheckForTap : mPendingCheckForLongPress);
3590                        mLayoutMode = LAYOUT_NORMAL;
3591                        if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
3592                            mTouchMode = TOUCH_MODE_TAP;
3593                            setSelectedPositionInt(mMotionPosition);
3594                            layoutChildren();
3595                            child.setPressed(true);
3596                            positionSelector(mMotionPosition, child);
3597                            setPressed(true);
3598                            if (mSelector != null) {
3599                                Drawable d = mSelector.getCurrent();
3600                                if (d != null && d instanceof TransitionDrawable) {
3601                                    ((TransitionDrawable) d).resetTransition();
3602                                }
3603                            }
3604                            if (mTouchModeReset != null) {
3605                                removeCallbacks(mTouchModeReset);
3606                            }
3607                            mTouchModeReset = new Runnable() {
3608                                @Override
3609                                public void run() {
3610                                    mTouchModeReset = null;
3611                                    mTouchMode = TOUCH_MODE_REST;
3612                                    child.setPressed(false);
3613                                    setPressed(false);
3614                                    if (!mDataChanged) {
3615                                        performClick.run();
3616                                    }
3617                                }
3618                            };
3619                            postDelayed(mTouchModeReset,
3620                                    ViewConfiguration.getPressedStateDuration());
3621                        } else {
3622                            mTouchMode = TOUCH_MODE_REST;
3623                            updateSelectorState();
3624                        }
3625                        return;
3626                    } else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
3627                        performClick.run();
3628                    }
3629                }
3630            }
3631            mTouchMode = TOUCH_MODE_REST;
3632            updateSelectorState();
3633            break;
3634        case TOUCH_MODE_SCROLL:
3635            final int childCount = getChildCount();
3636            if (childCount > 0) {
3637                final int firstChildTop = getChildAt(0).getTop();
3638                final int lastChildBottom = getChildAt(childCount - 1).getBottom();
3639                final int contentTop = mListPadding.top;
3640                final int contentBottom = getHeight() - mListPadding.bottom;
3641                if (mFirstPosition == 0 && firstChildTop >= contentTop &&
3642                        mFirstPosition + childCount < mItemCount &&
3643                        lastChildBottom <= getHeight() - contentBottom) {
3644                    mTouchMode = TOUCH_MODE_REST;
3645                    reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
3646                } else {
3647                    final VelocityTracker velocityTracker = mVelocityTracker;
3648                    velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
3649
3650                    final int initialVelocity = (int)
3651                            (velocityTracker.getYVelocity(mActivePointerId) * mVelocityScale);
3652                    // Fling if we have enough velocity and we aren't at a boundary.
3653                    // Since we can potentially overfling more than we can overscroll, don't
3654                    // allow the weird behavior where you can scroll to a boundary then
3655                    // fling further.
3656                    if (Math.abs(initialVelocity) > mMinimumVelocity &&
3657                            !((mFirstPosition == 0 &&
3658                                    firstChildTop == contentTop - mOverscrollDistance) ||
3659                              (mFirstPosition + childCount == mItemCount &&
3660                                    lastChildBottom == contentBottom + mOverscrollDistance))) {
3661                        if (mFlingRunnable == null) {
3662                            mFlingRunnable = new FlingRunnable();
3663                        }
3664                        reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
3665
3666                        mFlingRunnable.start(-initialVelocity);
3667                    } else {
3668                        mTouchMode = TOUCH_MODE_REST;
3669                        reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
3670                        if (mFlingRunnable != null) {
3671                            mFlingRunnable.endFling();
3672                        }
3673                        if (mPositionScroller != null) {
3674                            mPositionScroller.stop();
3675                        }
3676                    }
3677                }
3678            } else {
3679                mTouchMode = TOUCH_MODE_REST;
3680                reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
3681            }
3682            break;
3683
3684        case TOUCH_MODE_OVERSCROLL:
3685            if (mFlingRunnable == null) {
3686                mFlingRunnable = new FlingRunnable();
3687            }
3688            final VelocityTracker velocityTracker = mVelocityTracker;
3689            velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
3690            final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
3691
3692            reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
3693            if (Math.abs(initialVelocity) > mMinimumVelocity) {
3694                mFlingRunnable.startOverfling(-initialVelocity);
3695            } else {
3696                mFlingRunnable.startSpringback();
3697            }
3698
3699            break;
3700        }
3701
3702        setPressed(false);
3703
3704        if (mEdgeGlowTop != null) {
3705            mEdgeGlowTop.onRelease();
3706            mEdgeGlowBottom.onRelease();
3707        }
3708
3709        // Need to redraw since we probably aren't drawing the selector anymore
3710        invalidate();
3711        removeCallbacks(mPendingCheckForLongPress);
3712        recycleVelocityTracker();
3713
3714        mActivePointerId = INVALID_POINTER;
3715
3716        if (PROFILE_SCROLLING) {
3717            if (mScrollProfilingStarted) {
3718                Debug.stopMethodTracing();
3719                mScrollProfilingStarted = false;
3720            }
3721        }
3722
3723        if (mScrollStrictSpan != null) {
3724            mScrollStrictSpan.finish();
3725            mScrollStrictSpan = null;
3726        }
3727    }
3728
3729    private void onTouchCancel() {
3730        switch (mTouchMode) {
3731        case TOUCH_MODE_OVERSCROLL:
3732            if (mFlingRunnable == null) {
3733                mFlingRunnable = new FlingRunnable();
3734            }
3735            mFlingRunnable.startSpringback();
3736            break;
3737
3738        case TOUCH_MODE_OVERFLING:
3739            // Do nothing - let it play out.
3740            break;
3741
3742        default:
3743            mTouchMode = TOUCH_MODE_REST;
3744            setPressed(false);
3745            final View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
3746            if (motionView != null) {
3747                motionView.setPressed(false);
3748            }
3749            clearScrollingCache();
3750            removeCallbacks(mPendingCheckForLongPress);
3751            recycleVelocityTracker();
3752        }
3753
3754        if (mEdgeGlowTop != null) {
3755            mEdgeGlowTop.onRelease();
3756            mEdgeGlowBottom.onRelease();
3757        }
3758        mActivePointerId = INVALID_POINTER;
3759    }
3760
3761    @Override
3762    protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
3763        if (mScrollY != scrollY) {
3764            onScrollChanged(mScrollX, scrollY, mScrollX, mScrollY);
3765            mScrollY = scrollY;
3766            invalidateParentIfNeeded();
3767
3768            awakenScrollBars();
3769        }
3770    }
3771
3772    @Override
3773    public boolean onGenericMotionEvent(MotionEvent event) {
3774        if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
3775            switch (event.getAction()) {
3776                case MotionEvent.ACTION_SCROLL: {
3777                    if (mTouchMode == TOUCH_MODE_REST) {
3778                        final float vscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
3779                        if (vscroll != 0) {
3780                            final int delta = (int) (vscroll * getVerticalScrollFactor());
3781                            if (!trackMotionScroll(delta, delta)) {
3782                                return true;
3783                            }
3784                        }
3785                    }
3786                }
3787            }
3788        }
3789        return super.onGenericMotionEvent(event);
3790    }
3791
3792    @Override
3793    public void draw(Canvas canvas) {
3794        super.draw(canvas);
3795        if (mEdgeGlowTop != null) {
3796            final int scrollY = mScrollY;
3797            if (!mEdgeGlowTop.isFinished()) {
3798                final int restoreCount = canvas.save();
3799                final int leftPadding = mListPadding.left + mGlowPaddingLeft;
3800                final int rightPadding = mListPadding.right + mGlowPaddingRight;
3801                final int width = getWidth() - leftPadding - rightPadding;
3802
3803                int edgeY = Math.min(0, scrollY + mFirstPositionDistanceGuess);
3804                canvas.translate(leftPadding, edgeY);
3805                mEdgeGlowTop.setSize(width, getHeight());
3806                if (mEdgeGlowTop.draw(canvas)) {
3807                    mEdgeGlowTop.setPosition(leftPadding, edgeY);
3808                    invalidate(mEdgeGlowTop.getBounds(false));
3809                }
3810                canvas.restoreToCount(restoreCount);
3811            }
3812            if (!mEdgeGlowBottom.isFinished()) {
3813                final int restoreCount = canvas.save();
3814                final int leftPadding = mListPadding.left + mGlowPaddingLeft;
3815                final int rightPadding = mListPadding.right + mGlowPaddingRight;
3816                final int width = getWidth() - leftPadding - rightPadding;
3817                final int height = getHeight();
3818
3819                int edgeX = -width + leftPadding;
3820                int edgeY = Math.max(height, scrollY + mLastPositionDistanceGuess);
3821                canvas.translate(edgeX, edgeY);
3822                canvas.rotate(180, width, 0);
3823                mEdgeGlowBottom.setSize(width, height);
3824                if (mEdgeGlowBottom.draw(canvas)) {
3825                    // Account for the rotation
3826                    mEdgeGlowBottom.setPosition(edgeX + width, edgeY);
3827                    invalidate(mEdgeGlowBottom.getBounds(true));
3828                }
3829                canvas.restoreToCount(restoreCount);
3830            }
3831        }
3832    }
3833
3834    /**
3835     * @hide
3836     */
3837    public void setOverScrollEffectPadding(int leftPadding, int rightPadding) {
3838        mGlowPaddingLeft = leftPadding;
3839        mGlowPaddingRight = rightPadding;
3840    }
3841
3842    private void initOrResetVelocityTracker() {
3843        if (mVelocityTracker == null) {
3844            mVelocityTracker = VelocityTracker.obtain();
3845        } else {
3846            mVelocityTracker.clear();
3847        }
3848    }
3849
3850    private void initVelocityTrackerIfNotExists() {
3851        if (mVelocityTracker == null) {
3852            mVelocityTracker = VelocityTracker.obtain();
3853        }
3854    }
3855
3856    private void recycleVelocityTracker() {
3857        if (mVelocityTracker != null) {
3858            mVelocityTracker.recycle();
3859            mVelocityTracker = null;
3860        }
3861    }
3862
3863    @Override
3864    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
3865        if (disallowIntercept) {
3866            recycleVelocityTracker();
3867        }
3868        super.requestDisallowInterceptTouchEvent(disallowIntercept);
3869    }
3870
3871    @Override
3872    public boolean onInterceptHoverEvent(MotionEvent event) {
3873        if (mFastScroller != null && mFastScroller.onInterceptHoverEvent(event)) {
3874            return true;
3875        }
3876
3877        return super.onInterceptHoverEvent(event);
3878    }
3879
3880    @Override
3881    public boolean onInterceptTouchEvent(MotionEvent ev) {
3882        int action = ev.getAction();
3883        View v;
3884
3885        if (mPositionScroller != null) {
3886            mPositionScroller.stop();
3887        }
3888
3889        if (!mIsAttached) {
3890            // Something isn't right.
3891            // Since we rely on being attached to get data set change notifications,
3892            // don't risk doing anything where we might try to resync and find things
3893            // in a bogus state.
3894            return false;
3895        }
3896
3897        if (mFastScroller != null && mFastScroller.onInterceptTouchEvent(ev)) {
3898            return true;
3899        }
3900
3901        switch (action & MotionEvent.ACTION_MASK) {
3902        case MotionEvent.ACTION_DOWN: {
3903            int touchMode = mTouchMode;
3904            if (touchMode == TOUCH_MODE_OVERFLING || touchMode == TOUCH_MODE_OVERSCROLL) {
3905                mMotionCorrection = 0;
3906                return true;
3907            }
3908
3909            final int x = (int) ev.getX();
3910            final int y = (int) ev.getY();
3911            mActivePointerId = ev.getPointerId(0);
3912
3913            int motionPosition = findMotionRow(y);
3914            if (touchMode != TOUCH_MODE_FLING && motionPosition >= 0) {
3915                // User clicked on an actual view (and was not stopping a fling).
3916                // Remember where the motion event started
3917                v = getChildAt(motionPosition - mFirstPosition);
3918                mMotionViewOriginalTop = v.getTop();
3919                mMotionX = x;
3920                mMotionY = y;
3921                mMotionPosition = motionPosition;
3922                mTouchMode = TOUCH_MODE_DOWN;
3923                clearScrollingCache();
3924            }
3925            mLastY = Integer.MIN_VALUE;
3926            initOrResetVelocityTracker();
3927            mVelocityTracker.addMovement(ev);
3928            if (touchMode == TOUCH_MODE_FLING) {
3929                return true;
3930            }
3931            break;
3932        }
3933
3934        case MotionEvent.ACTION_MOVE: {
3935            switch (mTouchMode) {
3936            case TOUCH_MODE_DOWN:
3937                int pointerIndex = ev.findPointerIndex(mActivePointerId);
3938                if (pointerIndex == -1) {
3939                    pointerIndex = 0;
3940                    mActivePointerId = ev.getPointerId(pointerIndex);
3941                }
3942                final int y = (int) ev.getY(pointerIndex);
3943                initVelocityTrackerIfNotExists();
3944                mVelocityTracker.addMovement(ev);
3945                if (startScrollIfNeeded(y)) {
3946                    return true;
3947                }
3948                break;
3949            }
3950            break;
3951        }
3952
3953        case MotionEvent.ACTION_CANCEL:
3954        case MotionEvent.ACTION_UP: {
3955            mTouchMode = TOUCH_MODE_REST;
3956            mActivePointerId = INVALID_POINTER;
3957            recycleVelocityTracker();
3958            reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
3959            break;
3960        }
3961
3962        case MotionEvent.ACTION_POINTER_UP: {
3963            onSecondaryPointerUp(ev);
3964            break;
3965        }
3966        }
3967
3968        return false;
3969    }
3970
3971    private void onSecondaryPointerUp(MotionEvent ev) {
3972        final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
3973                MotionEvent.ACTION_POINTER_INDEX_SHIFT;
3974        final int pointerId = ev.getPointerId(pointerIndex);
3975        if (pointerId == mActivePointerId) {
3976            // This was our active pointer going up. Choose a new
3977            // active pointer and adjust accordingly.
3978            // TODO: Make this decision more intelligent.
3979            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
3980            mMotionX = (int) ev.getX(newPointerIndex);
3981            mMotionY = (int) ev.getY(newPointerIndex);
3982            mMotionCorrection = 0;
3983            mActivePointerId = ev.getPointerId(newPointerIndex);
3984        }
3985    }
3986
3987    /**
3988     * {@inheritDoc}
3989     */
3990    @Override
3991    public void addTouchables(ArrayList<View> views) {
3992        final int count = getChildCount();
3993        final int firstPosition = mFirstPosition;
3994        final ListAdapter adapter = mAdapter;
3995
3996        if (adapter == null) {
3997            return;
3998        }
3999
4000        for (int i = 0; i < count; i++) {
4001            final View child = getChildAt(i);
4002            if (adapter.isEnabled(firstPosition + i)) {
4003                views.add(child);
4004            }
4005            child.addTouchables(views);
4006        }
4007    }
4008
4009    /**
4010     * Fires an "on scroll state changed" event to the registered
4011     * {@link android.widget.AbsListView.OnScrollListener}, if any. The state change
4012     * is fired only if the specified state is different from the previously known state.
4013     *
4014     * @param newState The new scroll state.
4015     */
4016    void reportScrollStateChange(int newState) {
4017        if (newState != mLastScrollState) {
4018            if (mOnScrollListener != null) {
4019                mLastScrollState = newState;
4020                mOnScrollListener.onScrollStateChanged(this, newState);
4021            }
4022        }
4023    }
4024
4025    /**
4026     * Responsible for fling behavior. Use {@link #start(int)} to
4027     * initiate a fling. Each frame of the fling is handled in {@link #run()}.
4028     * A FlingRunnable will keep re-posting itself until the fling is done.
4029     *
4030     */
4031    private class FlingRunnable implements Runnable {
4032        /**
4033         * Tracks the decay of a fling scroll
4034         */
4035        private final OverScroller mScroller;
4036
4037        /**
4038         * Y value reported by mScroller on the previous fling
4039         */
4040        private int mLastFlingY;
4041
4042        private final Runnable mCheckFlywheel = new Runnable() {
4043            @Override
4044            public void run() {
4045                final int activeId = mActivePointerId;
4046                final VelocityTracker vt = mVelocityTracker;
4047                final OverScroller scroller = mScroller;
4048                if (vt == null || activeId == INVALID_POINTER) {
4049                    return;
4050                }
4051
4052                vt.computeCurrentVelocity(1000, mMaximumVelocity);
4053                final float yvel = -vt.getYVelocity(activeId);
4054
4055                if (Math.abs(yvel) >= mMinimumVelocity
4056                        && scroller.isScrollingInDirection(0, yvel)) {
4057                    // Keep the fling alive a little longer
4058                    postDelayed(this, FLYWHEEL_TIMEOUT);
4059                } else {
4060                    endFling();
4061                    mTouchMode = TOUCH_MODE_SCROLL;
4062                    reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
4063                }
4064            }
4065        };
4066
4067        private static final int FLYWHEEL_TIMEOUT = 40; // milliseconds
4068
4069        FlingRunnable() {
4070            mScroller = new OverScroller(getContext());
4071        }
4072
4073        void start(int initialVelocity) {
4074            int initialY = initialVelocity < 0 ? Integer.MAX_VALUE : 0;
4075            mLastFlingY = initialY;
4076            mScroller.setInterpolator(null);
4077            mScroller.fling(0, initialY, 0, initialVelocity,
4078                    0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE);
4079            mTouchMode = TOUCH_MODE_FLING;
4080            postOnAnimation(this);
4081
4082            if (PROFILE_FLINGING) {
4083                if (!mFlingProfilingStarted) {
4084                    Debug.startMethodTracing("AbsListViewFling");
4085                    mFlingProfilingStarted = true;
4086                }
4087            }
4088
4089            if (mFlingStrictSpan == null) {
4090                mFlingStrictSpan = StrictMode.enterCriticalSpan("AbsListView-fling");
4091            }
4092        }
4093
4094        void startSpringback() {
4095            if (mScroller.springBack(0, mScrollY, 0, 0, 0, 0)) {
4096                mTouchMode = TOUCH_MODE_OVERFLING;
4097                invalidate();
4098                postOnAnimation(this);
4099            } else {
4100                mTouchMode = TOUCH_MODE_REST;
4101                reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4102            }
4103        }
4104
4105        void startOverfling(int initialVelocity) {
4106            mScroller.setInterpolator(null);
4107            mScroller.fling(0, mScrollY, 0, initialVelocity, 0, 0,
4108                    Integer.MIN_VALUE, Integer.MAX_VALUE, 0, getHeight());
4109            mTouchMode = TOUCH_MODE_OVERFLING;
4110            invalidate();
4111            postOnAnimation(this);
4112        }
4113
4114        void edgeReached(int delta) {
4115            mScroller.notifyVerticalEdgeReached(mScrollY, 0, mOverflingDistance);
4116            final int overscrollMode = getOverScrollMode();
4117            if (overscrollMode == OVER_SCROLL_ALWAYS ||
4118                    (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && !contentFits())) {
4119                mTouchMode = TOUCH_MODE_OVERFLING;
4120                final int vel = (int) mScroller.getCurrVelocity();
4121                if (delta > 0) {
4122                    mEdgeGlowTop.onAbsorb(vel);
4123                } else {
4124                    mEdgeGlowBottom.onAbsorb(vel);
4125                }
4126            } else {
4127                mTouchMode = TOUCH_MODE_REST;
4128                if (mPositionScroller != null) {
4129                    mPositionScroller.stop();
4130                }
4131            }
4132            invalidate();
4133            postOnAnimation(this);
4134        }
4135
4136        void startScroll(int distance, int duration, boolean linear) {
4137            int initialY = distance < 0 ? Integer.MAX_VALUE : 0;
4138            mLastFlingY = initialY;
4139            mScroller.setInterpolator(linear ? sLinearInterpolator : null);
4140            mScroller.startScroll(0, initialY, 0, distance, duration);
4141            mTouchMode = TOUCH_MODE_FLING;
4142            postOnAnimation(this);
4143        }
4144
4145        void endFling() {
4146            mTouchMode = TOUCH_MODE_REST;
4147
4148            removeCallbacks(this);
4149            removeCallbacks(mCheckFlywheel);
4150
4151            reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4152            clearScrollingCache();
4153            mScroller.abortAnimation();
4154
4155            if (mFlingStrictSpan != null) {
4156                mFlingStrictSpan.finish();
4157                mFlingStrictSpan = null;
4158            }
4159        }
4160
4161        void flywheelTouch() {
4162            postDelayed(mCheckFlywheel, FLYWHEEL_TIMEOUT);
4163        }
4164
4165        @Override
4166        public void run() {
4167            switch (mTouchMode) {
4168            default:
4169                endFling();
4170                return;
4171
4172            case TOUCH_MODE_SCROLL:
4173                if (mScroller.isFinished()) {
4174                    return;
4175                }
4176                // Fall through
4177            case TOUCH_MODE_FLING: {
4178                if (mDataChanged) {
4179                    layoutChildren();
4180                }
4181
4182                if (mItemCount == 0 || getChildCount() == 0) {
4183                    endFling();
4184                    return;
4185                }
4186
4187                final OverScroller scroller = mScroller;
4188                boolean more = scroller.computeScrollOffset();
4189                final int y = scroller.getCurrY();
4190
4191                // Flip sign to convert finger direction to list items direction
4192                // (e.g. finger moving down means list is moving towards the top)
4193                int delta = mLastFlingY - y;
4194
4195                // Pretend that each frame of a fling scroll is a touch scroll
4196                if (delta > 0) {
4197                    // List is moving towards the top. Use first view as mMotionPosition
4198                    mMotionPosition = mFirstPosition;
4199                    final View firstView = getChildAt(0);
4200                    mMotionViewOriginalTop = firstView.getTop();
4201
4202                    // Don't fling more than 1 screen
4203                    delta = Math.min(getHeight() - mPaddingBottom - mPaddingTop - 1, delta);
4204                } else {
4205                    // List is moving towards the bottom. Use last view as mMotionPosition
4206                    int offsetToLast = getChildCount() - 1;
4207                    mMotionPosition = mFirstPosition + offsetToLast;
4208
4209                    final View lastView = getChildAt(offsetToLast);
4210                    mMotionViewOriginalTop = lastView.getTop();
4211
4212                    // Don't fling more than 1 screen
4213                    delta = Math.max(-(getHeight() - mPaddingBottom - mPaddingTop - 1), delta);
4214                }
4215
4216                // Check to see if we have bumped into the scroll limit
4217                View motionView = getChildAt(mMotionPosition - mFirstPosition);
4218                int oldTop = 0;
4219                if (motionView != null) {
4220                    oldTop = motionView.getTop();
4221                }
4222
4223                // Don't stop just because delta is zero (it could have been rounded)
4224                final boolean atEdge = trackMotionScroll(delta, delta);
4225                final boolean atEnd = atEdge && (delta != 0);
4226                if (atEnd) {
4227                    if (motionView != null) {
4228                        // Tweak the scroll for how far we overshot
4229                        int overshoot = -(delta - (motionView.getTop() - oldTop));
4230                        overScrollBy(0, overshoot, 0, mScrollY, 0, 0,
4231                                0, mOverflingDistance, false);
4232                    }
4233                    if (more) {
4234                        edgeReached(delta);
4235                    }
4236                    break;
4237                }
4238
4239                if (more && !atEnd) {
4240                    if (atEdge) invalidate();
4241                    mLastFlingY = y;
4242                    postOnAnimation(this);
4243                } else {
4244                    endFling();
4245
4246                    if (PROFILE_FLINGING) {
4247                        if (mFlingProfilingStarted) {
4248                            Debug.stopMethodTracing();
4249                            mFlingProfilingStarted = false;
4250                        }
4251
4252                        if (mFlingStrictSpan != null) {
4253                            mFlingStrictSpan.finish();
4254                            mFlingStrictSpan = null;
4255                        }
4256                    }
4257                }
4258                break;
4259            }
4260
4261            case TOUCH_MODE_OVERFLING: {
4262                final OverScroller scroller = mScroller;
4263                if (scroller.computeScrollOffset()) {
4264                    final int scrollY = mScrollY;
4265                    final int currY = scroller.getCurrY();
4266                    final int deltaY = currY - scrollY;
4267                    if (overScrollBy(0, deltaY, 0, scrollY, 0, 0,
4268                            0, mOverflingDistance, false)) {
4269                        final boolean crossDown = scrollY <= 0 && currY > 0;
4270                        final boolean crossUp = scrollY >= 0 && currY < 0;
4271                        if (crossDown || crossUp) {
4272                            int velocity = (int) scroller.getCurrVelocity();
4273                            if (crossUp) velocity = -velocity;
4274
4275                            // Don't flywheel from this; we're just continuing things.
4276                            scroller.abortAnimation();
4277                            start(velocity);
4278                        } else {
4279                            startSpringback();
4280                        }
4281                    } else {
4282                        invalidate();
4283                        postOnAnimation(this);
4284                    }
4285                } else {
4286                    endFling();
4287                }
4288                break;
4289            }
4290            }
4291        }
4292    }
4293
4294    class PositionScroller implements Runnable {
4295        private static final int SCROLL_DURATION = 200;
4296
4297        private static final int MOVE_DOWN_POS = 1;
4298        private static final int MOVE_UP_POS = 2;
4299        private static final int MOVE_DOWN_BOUND = 3;
4300        private static final int MOVE_UP_BOUND = 4;
4301        private static final int MOVE_OFFSET = 5;
4302
4303        private int mMode;
4304        private int mTargetPos;
4305        private int mBoundPos;
4306        private int mLastSeenPos;
4307        private int mScrollDuration;
4308        private final int mExtraScroll;
4309
4310        private int mOffsetFromTop;
4311
4312        PositionScroller() {
4313            mExtraScroll = ViewConfiguration.get(mContext).getScaledFadingEdgeLength();
4314        }
4315
4316        void start(final int position) {
4317            stop();
4318
4319            if (mDataChanged) {
4320                // Wait until we're back in a stable state to try this.
4321                mPositionScrollAfterLayout = new Runnable() {
4322                    @Override public void run() {
4323                        start(position);
4324                    }
4325                };
4326                return;
4327            }
4328
4329            final int childCount = getChildCount();
4330            if (childCount == 0) {
4331                // Can't scroll without children.
4332                return;
4333            }
4334
4335            final int firstPos = mFirstPosition;
4336            final int lastPos = firstPos + childCount - 1;
4337
4338            int viewTravelCount;
4339            int clampedPosition = Math.max(0, Math.min(getCount() - 1, position));
4340            if (clampedPosition < firstPos) {
4341                viewTravelCount = firstPos - clampedPosition + 1;
4342                mMode = MOVE_UP_POS;
4343            } else if (clampedPosition > lastPos) {
4344                viewTravelCount = clampedPosition - lastPos + 1;
4345                mMode = MOVE_DOWN_POS;
4346            } else {
4347                scrollToVisible(clampedPosition, INVALID_POSITION, SCROLL_DURATION);
4348                return;
4349            }
4350
4351            if (viewTravelCount > 0) {
4352                mScrollDuration = SCROLL_DURATION / viewTravelCount;
4353            } else {
4354                mScrollDuration = SCROLL_DURATION;
4355            }
4356            mTargetPos = clampedPosition;
4357            mBoundPos = INVALID_POSITION;
4358            mLastSeenPos = INVALID_POSITION;
4359
4360            postOnAnimation(this);
4361        }
4362
4363        void start(final int position, final int boundPosition) {
4364            stop();
4365
4366            if (boundPosition == INVALID_POSITION) {
4367                start(position);
4368                return;
4369            }
4370
4371            if (mDataChanged) {
4372                // Wait until we're back in a stable state to try this.
4373                mPositionScrollAfterLayout = new Runnable() {
4374                    @Override public void run() {
4375                        start(position, boundPosition);
4376                    }
4377                };
4378                return;
4379            }
4380
4381            final int childCount = getChildCount();
4382            if (childCount == 0) {
4383                // Can't scroll without children.
4384                return;
4385            }
4386
4387            final int firstPos = mFirstPosition;
4388            final int lastPos = firstPos + childCount - 1;
4389
4390            int viewTravelCount;
4391            int clampedPosition = Math.max(0, Math.min(getCount() - 1, position));
4392            if (clampedPosition < firstPos) {
4393                final int boundPosFromLast = lastPos - boundPosition;
4394                if (boundPosFromLast < 1) {
4395                    // Moving would shift our bound position off the screen. Abort.
4396                    return;
4397                }
4398
4399                final int posTravel = firstPos - clampedPosition + 1;
4400                final int boundTravel = boundPosFromLast - 1;
4401                if (boundTravel < posTravel) {
4402                    viewTravelCount = boundTravel;
4403                    mMode = MOVE_UP_BOUND;
4404                } else {
4405                    viewTravelCount = posTravel;
4406                    mMode = MOVE_UP_POS;
4407                }
4408            } else if (clampedPosition > lastPos) {
4409                final int boundPosFromFirst = boundPosition - firstPos;
4410                if (boundPosFromFirst < 1) {
4411                    // Moving would shift our bound position off the screen. Abort.
4412                    return;
4413                }
4414
4415                final int posTravel = clampedPosition - lastPos + 1;
4416                final int boundTravel = boundPosFromFirst - 1;
4417                if (boundTravel < posTravel) {
4418                    viewTravelCount = boundTravel;
4419                    mMode = MOVE_DOWN_BOUND;
4420                } else {
4421                    viewTravelCount = posTravel;
4422                    mMode = MOVE_DOWN_POS;
4423                }
4424            } else {
4425                scrollToVisible(clampedPosition, boundPosition, SCROLL_DURATION);
4426                return;
4427            }
4428
4429            if (viewTravelCount > 0) {
4430                mScrollDuration = SCROLL_DURATION / viewTravelCount;
4431            } else {
4432                mScrollDuration = SCROLL_DURATION;
4433            }
4434            mTargetPos = clampedPosition;
4435            mBoundPos = boundPosition;
4436            mLastSeenPos = INVALID_POSITION;
4437
4438            postOnAnimation(this);
4439        }
4440
4441        void startWithOffset(int position, int offset) {
4442            startWithOffset(position, offset, SCROLL_DURATION);
4443        }
4444
4445        void startWithOffset(final int position, int offset, final int duration) {
4446            stop();
4447
4448            if (mDataChanged) {
4449                // Wait until we're back in a stable state to try this.
4450                final int postOffset = offset;
4451                mPositionScrollAfterLayout = new Runnable() {
4452                    @Override public void run() {
4453                        startWithOffset(position, postOffset, duration);
4454                    }
4455                };
4456                return;
4457            }
4458
4459            final int childCount = getChildCount();
4460            if (childCount == 0) {
4461                // Can't scroll without children.
4462                return;
4463            }
4464
4465            offset += getPaddingTop();
4466
4467            mTargetPos = Math.max(0, Math.min(getCount() - 1, position));
4468            mOffsetFromTop = offset;
4469            mBoundPos = INVALID_POSITION;
4470            mLastSeenPos = INVALID_POSITION;
4471            mMode = MOVE_OFFSET;
4472
4473            final int firstPos = mFirstPosition;
4474            final int lastPos = firstPos + childCount - 1;
4475
4476            int viewTravelCount;
4477            if (mTargetPos < firstPos) {
4478                viewTravelCount = firstPos - mTargetPos;
4479            } else if (mTargetPos > lastPos) {
4480                viewTravelCount = mTargetPos - lastPos;
4481            } else {
4482                // On-screen, just scroll.
4483                final int targetTop = getChildAt(mTargetPos - firstPos).getTop();
4484                smoothScrollBy(targetTop - offset, duration, true);
4485                return;
4486            }
4487
4488            // Estimate how many screens we should travel
4489            final float screenTravelCount = (float) viewTravelCount / childCount;
4490            mScrollDuration = screenTravelCount < 1 ?
4491                    duration : (int) (duration / screenTravelCount);
4492            mLastSeenPos = INVALID_POSITION;
4493
4494            postOnAnimation(this);
4495        }
4496
4497        /**
4498         * Scroll such that targetPos is in the visible padded region without scrolling
4499         * boundPos out of view. Assumes targetPos is onscreen.
4500         */
4501        void scrollToVisible(int targetPos, int boundPos, int duration) {
4502            final int firstPos = mFirstPosition;
4503            final int childCount = getChildCount();
4504            final int lastPos = firstPos + childCount - 1;
4505            final int paddedTop = mListPadding.top;
4506            final int paddedBottom = getHeight() - mListPadding.bottom;
4507
4508            if (targetPos < firstPos || targetPos > lastPos) {
4509                Log.w(TAG, "scrollToVisible called with targetPos " + targetPos +
4510                        " not visible [" + firstPos + ", " + lastPos + "]");
4511            }
4512            if (boundPos < firstPos || boundPos > lastPos) {
4513                // boundPos doesn't matter, it's already offscreen.
4514                boundPos = INVALID_POSITION;
4515            }
4516
4517            final View targetChild = getChildAt(targetPos - firstPos);
4518            final int targetTop = targetChild.getTop();
4519            final int targetBottom = targetChild.getBottom();
4520            int scrollBy = 0;
4521
4522            if (targetBottom > paddedBottom) {
4523                scrollBy = targetBottom - paddedBottom;
4524            }
4525            if (targetTop < paddedTop) {
4526                scrollBy = targetTop - paddedTop;
4527            }
4528
4529            if (scrollBy == 0) {
4530                return;
4531            }
4532
4533            if (boundPos >= 0) {
4534                final View boundChild = getChildAt(boundPos - firstPos);
4535                final int boundTop = boundChild.getTop();
4536                final int boundBottom = boundChild.getBottom();
4537                final int absScroll = Math.abs(scrollBy);
4538
4539                if (scrollBy < 0 && boundBottom + absScroll > paddedBottom) {
4540                    // Don't scroll the bound view off the bottom of the screen.
4541                    scrollBy = Math.max(0, boundBottom - paddedBottom);
4542                } else if (scrollBy > 0 && boundTop - absScroll < paddedTop) {
4543                    // Don't scroll the bound view off the top of the screen.
4544                    scrollBy = Math.min(0, boundTop - paddedTop);
4545                }
4546            }
4547
4548            smoothScrollBy(scrollBy, duration);
4549        }
4550
4551        void stop() {
4552            removeCallbacks(this);
4553        }
4554
4555        @Override
4556        public void run() {
4557            final int listHeight = getHeight();
4558            final int firstPos = mFirstPosition;
4559
4560            switch (mMode) {
4561            case MOVE_DOWN_POS: {
4562                final int lastViewIndex = getChildCount() - 1;
4563                final int lastPos = firstPos + lastViewIndex;
4564
4565                if (lastViewIndex < 0) {
4566                    return;
4567                }
4568
4569                if (lastPos == mLastSeenPos) {
4570                    // No new views, let things keep going.
4571                    postOnAnimation(this);
4572                    return;
4573                }
4574
4575                final View lastView = getChildAt(lastViewIndex);
4576                final int lastViewHeight = lastView.getHeight();
4577                final int lastViewTop = lastView.getTop();
4578                final int lastViewPixelsShowing = listHeight - lastViewTop;
4579                final int extraScroll = lastPos < mItemCount - 1 ?
4580                        Math.max(mListPadding.bottom, mExtraScroll) : mListPadding.bottom;
4581
4582                final int scrollBy = lastViewHeight - lastViewPixelsShowing + extraScroll;
4583                smoothScrollBy(scrollBy, mScrollDuration, true);
4584
4585                mLastSeenPos = lastPos;
4586                if (lastPos < mTargetPos) {
4587                    postOnAnimation(this);
4588                }
4589                break;
4590            }
4591
4592            case MOVE_DOWN_BOUND: {
4593                final int nextViewIndex = 1;
4594                final int childCount = getChildCount();
4595
4596                if (firstPos == mBoundPos || childCount <= nextViewIndex
4597                        || firstPos + childCount >= mItemCount) {
4598                    return;
4599                }
4600                final int nextPos = firstPos + nextViewIndex;
4601
4602                if (nextPos == mLastSeenPos) {
4603                    // No new views, let things keep going.
4604                    postOnAnimation(this);
4605                    return;
4606                }
4607
4608                final View nextView = getChildAt(nextViewIndex);
4609                final int nextViewHeight = nextView.getHeight();
4610                final int nextViewTop = nextView.getTop();
4611                final int extraScroll = Math.max(mListPadding.bottom, mExtraScroll);
4612                if (nextPos < mBoundPos) {
4613                    smoothScrollBy(Math.max(0, nextViewHeight + nextViewTop - extraScroll),
4614                            mScrollDuration, true);
4615
4616                    mLastSeenPos = nextPos;
4617
4618                    postOnAnimation(this);
4619                } else  {
4620                    if (nextViewTop > extraScroll) {
4621                        smoothScrollBy(nextViewTop - extraScroll, mScrollDuration, true);
4622                    }
4623                }
4624                break;
4625            }
4626
4627            case MOVE_UP_POS: {
4628                if (firstPos == mLastSeenPos) {
4629                    // No new views, let things keep going.
4630                    postOnAnimation(this);
4631                    return;
4632                }
4633
4634                final View firstView = getChildAt(0);
4635                if (firstView == null) {
4636                    return;
4637                }
4638                final int firstViewTop = firstView.getTop();
4639                final int extraScroll = firstPos > 0 ?
4640                        Math.max(mExtraScroll, mListPadding.top) : mListPadding.top;
4641
4642                smoothScrollBy(firstViewTop - extraScroll, mScrollDuration, true);
4643
4644                mLastSeenPos = firstPos;
4645
4646                if (firstPos > mTargetPos) {
4647                    postOnAnimation(this);
4648                }
4649                break;
4650            }
4651
4652            case MOVE_UP_BOUND: {
4653                final int lastViewIndex = getChildCount() - 2;
4654                if (lastViewIndex < 0) {
4655                    return;
4656                }
4657                final int lastPos = firstPos + lastViewIndex;
4658
4659                if (lastPos == mLastSeenPos) {
4660                    // No new views, let things keep going.
4661                    postOnAnimation(this);
4662                    return;
4663                }
4664
4665                final View lastView = getChildAt(lastViewIndex);
4666                final int lastViewHeight = lastView.getHeight();
4667                final int lastViewTop = lastView.getTop();
4668                final int lastViewPixelsShowing = listHeight - lastViewTop;
4669                final int extraScroll = Math.max(mListPadding.top, mExtraScroll);
4670                mLastSeenPos = lastPos;
4671                if (lastPos > mBoundPos) {
4672                    smoothScrollBy(-(lastViewPixelsShowing - extraScroll), mScrollDuration, true);
4673                    postOnAnimation(this);
4674                } else {
4675                    final int bottom = listHeight - extraScroll;
4676                    final int lastViewBottom = lastViewTop + lastViewHeight;
4677                    if (bottom > lastViewBottom) {
4678                        smoothScrollBy(-(bottom - lastViewBottom), mScrollDuration, true);
4679                    }
4680                }
4681                break;
4682            }
4683
4684            case MOVE_OFFSET: {
4685                if (mLastSeenPos == firstPos) {
4686                    // No new views, let things keep going.
4687                    postOnAnimation(this);
4688                    return;
4689                }
4690
4691                mLastSeenPos = firstPos;
4692
4693                final int childCount = getChildCount();
4694                final int position = mTargetPos;
4695                final int lastPos = firstPos + childCount - 1;
4696
4697                int viewTravelCount = 0;
4698                if (position < firstPos) {
4699                    viewTravelCount = firstPos - position + 1;
4700                } else if (position > lastPos) {
4701                    viewTravelCount = position - lastPos;
4702                }
4703
4704                // Estimate how many screens we should travel
4705                final float screenTravelCount = (float) viewTravelCount / childCount;
4706
4707                final float modifier = Math.min(Math.abs(screenTravelCount), 1.f);
4708                if (position < firstPos) {
4709                    final int distance = (int) (-getHeight() * modifier);
4710                    final int duration = (int) (mScrollDuration * modifier);
4711                    smoothScrollBy(distance, duration, true);
4712                    postOnAnimation(this);
4713                } else if (position > lastPos) {
4714                    final int distance = (int) (getHeight() * modifier);
4715                    final int duration = (int) (mScrollDuration * modifier);
4716                    smoothScrollBy(distance, duration, true);
4717                    postOnAnimation(this);
4718                } else {
4719                    // On-screen, just scroll.
4720                    final int targetTop = getChildAt(position - firstPos).getTop();
4721                    final int distance = targetTop - mOffsetFromTop;
4722                    final int duration = (int) (mScrollDuration *
4723                            ((float) Math.abs(distance) / getHeight()));
4724                    smoothScrollBy(distance, duration, true);
4725                }
4726                break;
4727            }
4728
4729            default:
4730                break;
4731            }
4732        }
4733    }
4734
4735    /**
4736     * The amount of friction applied to flings. The default value
4737     * is {@link ViewConfiguration#getScrollFriction}.
4738     */
4739    public void setFriction(float friction) {
4740        if (mFlingRunnable == null) {
4741            mFlingRunnable = new FlingRunnable();
4742        }
4743        mFlingRunnable.mScroller.setFriction(friction);
4744    }
4745
4746    /**
4747     * Sets a scale factor for the fling velocity. The initial scale
4748     * factor is 1.0.
4749     *
4750     * @param scale The scale factor to multiply the velocity by.
4751     */
4752    public void setVelocityScale(float scale) {
4753        mVelocityScale = scale;
4754    }
4755
4756    /**
4757     * Smoothly scroll to the specified adapter position. The view will
4758     * scroll such that the indicated position is displayed.
4759     * @param position Scroll to this adapter position.
4760     */
4761    public void smoothScrollToPosition(int position) {
4762        if (mPositionScroller == null) {
4763            mPositionScroller = new PositionScroller();
4764        }
4765        mPositionScroller.start(position);
4766    }
4767
4768    /**
4769     * Smoothly scroll to the specified adapter position. The view will scroll
4770     * such that the indicated position is displayed <code>offset</code> pixels from
4771     * the top edge of the view. If this is impossible, (e.g. the offset would scroll
4772     * the first or last item beyond the boundaries of the list) it will get as close
4773     * as possible. The scroll will take <code>duration</code> milliseconds to complete.
4774     *
4775     * @param position Position to scroll to
4776     * @param offset Desired distance in pixels of <code>position</code> from the top
4777     *               of the view when scrolling is finished
4778     * @param duration Number of milliseconds to use for the scroll
4779     */
4780    public void smoothScrollToPositionFromTop(int position, int offset, int duration) {
4781        if (mPositionScroller == null) {
4782            mPositionScroller = new PositionScroller();
4783        }
4784        mPositionScroller.startWithOffset(position, offset, duration);
4785    }
4786
4787    /**
4788     * Smoothly scroll to the specified adapter position. The view will scroll
4789     * such that the indicated position is displayed <code>offset</code> pixels from
4790     * the top edge of the view. If this is impossible, (e.g. the offset would scroll
4791     * the first or last item beyond the boundaries of the list) it will get as close
4792     * as possible.
4793     *
4794     * @param position Position to scroll to
4795     * @param offset Desired distance in pixels of <code>position</code> from the top
4796     *               of the view when scrolling is finished
4797     */
4798    public void smoothScrollToPositionFromTop(int position, int offset) {
4799        if (mPositionScroller == null) {
4800            mPositionScroller = new PositionScroller();
4801        }
4802        mPositionScroller.startWithOffset(position, offset);
4803    }
4804
4805    /**
4806     * Smoothly scroll to the specified adapter position. The view will
4807     * scroll such that the indicated position is displayed, but it will
4808     * stop early if scrolling further would scroll boundPosition out of
4809     * view.
4810     * @param position Scroll to this adapter position.
4811     * @param boundPosition Do not scroll if it would move this adapter
4812     *          position out of view.
4813     */
4814    public void smoothScrollToPosition(int position, int boundPosition) {
4815        if (mPositionScroller == null) {
4816            mPositionScroller = new PositionScroller();
4817        }
4818        mPositionScroller.start(position, boundPosition);
4819    }
4820
4821    /**
4822     * Smoothly scroll by distance pixels over duration milliseconds.
4823     * @param distance Distance to scroll in pixels.
4824     * @param duration Duration of the scroll animation in milliseconds.
4825     */
4826    public void smoothScrollBy(int distance, int duration) {
4827        smoothScrollBy(distance, duration, false);
4828    }
4829
4830    void smoothScrollBy(int distance, int duration, boolean linear) {
4831        if (mFlingRunnable == null) {
4832            mFlingRunnable = new FlingRunnable();
4833        }
4834
4835        // No sense starting to scroll if we're not going anywhere
4836        final int firstPos = mFirstPosition;
4837        final int childCount = getChildCount();
4838        final int lastPos = firstPos + childCount;
4839        final int topLimit = getPaddingTop();
4840        final int bottomLimit = getHeight() - getPaddingBottom();
4841
4842        if (distance == 0 || mItemCount == 0 || childCount == 0 ||
4843                (firstPos == 0 && getChildAt(0).getTop() == topLimit && distance < 0) ||
4844                (lastPos == mItemCount &&
4845                        getChildAt(childCount - 1).getBottom() == bottomLimit && distance > 0)) {
4846            mFlingRunnable.endFling();
4847            if (mPositionScroller != null) {
4848                mPositionScroller.stop();
4849            }
4850        } else {
4851            reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
4852            mFlingRunnable.startScroll(distance, duration, linear);
4853        }
4854    }
4855
4856    /**
4857     * Allows RemoteViews to scroll relatively to a position.
4858     */
4859    void smoothScrollByOffset(int position) {
4860        int index = -1;
4861        if (position < 0) {
4862            index = getFirstVisiblePosition();
4863        } else if (position > 0) {
4864            index = getLastVisiblePosition();
4865        }
4866
4867        if (index > -1) {
4868            View child = getChildAt(index - getFirstVisiblePosition());
4869            if (child != null) {
4870                Rect visibleRect = new Rect();
4871                if (child.getGlobalVisibleRect(visibleRect)) {
4872                    // the child is partially visible
4873                    int childRectArea = child.getWidth() * child.getHeight();
4874                    int visibleRectArea = visibleRect.width() * visibleRect.height();
4875                    float visibleArea = (visibleRectArea / (float) childRectArea);
4876                    final float visibleThreshold = 0.75f;
4877                    if ((position < 0) && (visibleArea < visibleThreshold)) {
4878                        // the top index is not perceivably visible so offset
4879                        // to account for showing that top index as well
4880                        ++index;
4881                    } else if ((position > 0) && (visibleArea < visibleThreshold)) {
4882                        // the bottom index is not perceivably visible so offset
4883                        // to account for showing that bottom index as well
4884                        --index;
4885                    }
4886                }
4887                smoothScrollToPosition(Math.max(0, Math.min(getCount(), index + position)));
4888            }
4889        }
4890    }
4891
4892    private void createScrollingCache() {
4893        if (mScrollingCacheEnabled && !mCachingStarted && !isHardwareAccelerated()) {
4894            setChildrenDrawnWithCacheEnabled(true);
4895            setChildrenDrawingCacheEnabled(true);
4896            mCachingStarted = mCachingActive = true;
4897        }
4898    }
4899
4900    private void clearScrollingCache() {
4901        if (!isHardwareAccelerated()) {
4902            if (mClearScrollingCache == null) {
4903                mClearScrollingCache = new Runnable() {
4904                    @Override
4905                    public void run() {
4906                        if (mCachingStarted) {
4907                            mCachingStarted = mCachingActive = false;
4908                            setChildrenDrawnWithCacheEnabled(false);
4909                            if ((mPersistentDrawingCache & PERSISTENT_SCROLLING_CACHE) == 0) {
4910                                setChildrenDrawingCacheEnabled(false);
4911                            }
4912                            if (!isAlwaysDrawnWithCacheEnabled()) {
4913                                invalidate();
4914                            }
4915                        }
4916                    }
4917                };
4918            }
4919            post(mClearScrollingCache);
4920        }
4921    }
4922
4923    /**
4924     * Scrolls the list items within the view by a specified number of pixels.
4925     *
4926     * @param y the amount of pixels to scroll by vertically
4927     * @return true if the list is able to scroll, or false if the list is
4928     *         already at the beginning/end and unable to scroll any more.
4929     */
4930    public boolean scrollListBy(int y) {
4931        return !trackMotionScroll(-y, -y);
4932    }
4933
4934    /**
4935     * Track a motion scroll
4936     *
4937     * @param deltaY Amount to offset mMotionView. This is the accumulated delta since the motion
4938     *        began. Positive numbers mean the user's finger is moving down the screen.
4939     * @param incrementalDeltaY Change in deltaY from the previous event.
4940     * @return true if we're already at the beginning/end of the list and have nothing to do.
4941     */
4942    boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
4943        final int childCount = getChildCount();
4944        if (childCount == 0) {
4945            return true;
4946        }
4947
4948        final int firstTop = getChildAt(0).getTop();
4949        final int lastBottom = getChildAt(childCount - 1).getBottom();
4950
4951        final Rect listPadding = mListPadding;
4952
4953        // "effective padding" In this case is the amount of padding that affects
4954        // how much space should not be filled by items. If we don't clip to padding
4955        // there is no effective padding.
4956        int effectivePaddingTop = 0;
4957        int effectivePaddingBottom = 0;
4958        if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
4959            effectivePaddingTop = listPadding.top;
4960            effectivePaddingBottom = listPadding.bottom;
4961        }
4962
4963         // FIXME account for grid vertical spacing too?
4964        final int spaceAbove = effectivePaddingTop - firstTop;
4965        final int end = getHeight() - effectivePaddingBottom;
4966        final int spaceBelow = lastBottom - end;
4967
4968        final int height = getHeight() - mPaddingBottom - mPaddingTop;
4969        if (deltaY < 0) {
4970            deltaY = Math.max(-(height - 1), deltaY);
4971        } else {
4972            deltaY = Math.min(height - 1, deltaY);
4973        }
4974
4975        if (incrementalDeltaY < 0) {
4976            incrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY);
4977        } else {
4978            incrementalDeltaY = Math.min(height - 1, incrementalDeltaY);
4979        }
4980
4981        final int firstPosition = mFirstPosition;
4982
4983        // Update our guesses for where the first and last views are
4984        if (firstPosition == 0) {
4985            mFirstPositionDistanceGuess = firstTop - listPadding.top;
4986        } else {
4987            mFirstPositionDistanceGuess += incrementalDeltaY;
4988        }
4989        if (firstPosition + childCount == mItemCount) {
4990            mLastPositionDistanceGuess = lastBottom + listPadding.bottom;
4991        } else {
4992            mLastPositionDistanceGuess += incrementalDeltaY;
4993        }
4994
4995        final boolean cannotScrollDown = (firstPosition == 0 &&
4996                firstTop >= listPadding.top && incrementalDeltaY >= 0);
4997        final boolean cannotScrollUp = (firstPosition + childCount == mItemCount &&
4998                lastBottom <= getHeight() - listPadding.bottom && incrementalDeltaY <= 0);
4999
5000        if (cannotScrollDown || cannotScrollUp) {
5001            return incrementalDeltaY != 0;
5002        }
5003
5004        final boolean down = incrementalDeltaY < 0;
5005
5006        final boolean inTouchMode = isInTouchMode();
5007        if (inTouchMode) {
5008            hideSelector();
5009        }
5010
5011        final int headerViewsCount = getHeaderViewsCount();
5012        final int footerViewsStart = mItemCount - getFooterViewsCount();
5013
5014        int start = 0;
5015        int count = 0;
5016
5017        if (down) {
5018            int top = -incrementalDeltaY;
5019            if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
5020                top += listPadding.top;
5021            }
5022            for (int i = 0; i < childCount; i++) {
5023                final View child = getChildAt(i);
5024                if (child.getBottom() >= top) {
5025                    break;
5026                } else {
5027                    count++;
5028                    int position = firstPosition + i;
5029                    if (position >= headerViewsCount && position < footerViewsStart) {
5030                        mRecycler.addScrapView(child, position);
5031                    }
5032                }
5033            }
5034        } else {
5035            int bottom = getHeight() - incrementalDeltaY;
5036            if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
5037                bottom -= listPadding.bottom;
5038            }
5039            for (int i = childCount - 1; i >= 0; i--) {
5040                final View child = getChildAt(i);
5041                if (child.getTop() <= bottom) {
5042                    break;
5043                } else {
5044                    start = i;
5045                    count++;
5046                    int position = firstPosition + i;
5047                    if (position >= headerViewsCount && position < footerViewsStart) {
5048                        mRecycler.addScrapView(child, position);
5049                    }
5050                }
5051            }
5052        }
5053
5054        mMotionViewNewTop = mMotionViewOriginalTop + deltaY;
5055
5056        mBlockLayoutRequests = true;
5057
5058        if (count > 0) {
5059            detachViewsFromParent(start, count);
5060            mRecycler.removeSkippedScrap();
5061        }
5062
5063        // invalidate before moving the children to avoid unnecessary invalidate
5064        // calls to bubble up from the children all the way to the top
5065        if (!awakenScrollBars()) {
5066           invalidate();
5067        }
5068
5069        offsetChildrenTopAndBottom(incrementalDeltaY);
5070
5071        if (down) {
5072            mFirstPosition += count;
5073        }
5074
5075        final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
5076        if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
5077            fillGap(down);
5078        }
5079
5080        if (!inTouchMode && mSelectedPosition != INVALID_POSITION) {
5081            final int childIndex = mSelectedPosition - mFirstPosition;
5082            if (childIndex >= 0 && childIndex < getChildCount()) {
5083                positionSelector(mSelectedPosition, getChildAt(childIndex));
5084            }
5085        } else if (mSelectorPosition != INVALID_POSITION) {
5086            final int childIndex = mSelectorPosition - mFirstPosition;
5087            if (childIndex >= 0 && childIndex < getChildCount()) {
5088                positionSelector(INVALID_POSITION, getChildAt(childIndex));
5089            }
5090        } else {
5091            mSelectorRect.setEmpty();
5092        }
5093
5094        mBlockLayoutRequests = false;
5095
5096        invokeOnItemScrollListener();
5097
5098        return false;
5099    }
5100
5101    /**
5102     * Returns the number of header views in the list. Header views are special views
5103     * at the top of the list that should not be recycled during a layout.
5104     *
5105     * @return The number of header views, 0 in the default implementation.
5106     */
5107    int getHeaderViewsCount() {
5108        return 0;
5109    }
5110
5111    /**
5112     * Returns the number of footer views in the list. Footer views are special views
5113     * at the bottom of the list that should not be recycled during a layout.
5114     *
5115     * @return The number of footer views, 0 in the default implementation.
5116     */
5117    int getFooterViewsCount() {
5118        return 0;
5119    }
5120
5121    /**
5122     * Fills the gap left open by a touch-scroll. During a touch scroll, children that
5123     * remain on screen are shifted and the other ones are discarded. The role of this
5124     * method is to fill the gap thus created by performing a partial layout in the
5125     * empty space.
5126     *
5127     * @param down true if the scroll is going down, false if it is going up
5128     */
5129    abstract void fillGap(boolean down);
5130
5131    void hideSelector() {
5132        if (mSelectedPosition != INVALID_POSITION) {
5133            if (mLayoutMode != LAYOUT_SPECIFIC) {
5134                mResurrectToPosition = mSelectedPosition;
5135            }
5136            if (mNextSelectedPosition >= 0 && mNextSelectedPosition != mSelectedPosition) {
5137                mResurrectToPosition = mNextSelectedPosition;
5138            }
5139            setSelectedPositionInt(INVALID_POSITION);
5140            setNextSelectedPositionInt(INVALID_POSITION);
5141            mSelectedTop = 0;
5142        }
5143    }
5144
5145    /**
5146     * @return A position to select. First we try mSelectedPosition. If that has been clobbered by
5147     * entering touch mode, we then try mResurrectToPosition. Values are pinned to the range
5148     * of items available in the adapter
5149     */
5150    int reconcileSelectedPosition() {
5151        int position = mSelectedPosition;
5152        if (position < 0) {
5153            position = mResurrectToPosition;
5154        }
5155        position = Math.max(0, position);
5156        position = Math.min(position, mItemCount - 1);
5157        return position;
5158    }
5159
5160    /**
5161     * Find the row closest to y. This row will be used as the motion row when scrolling
5162     *
5163     * @param y Where the user touched
5164     * @return The position of the first (or only) item in the row containing y
5165     */
5166    abstract int findMotionRow(int y);
5167
5168    /**
5169     * Find the row closest to y. This row will be used as the motion row when scrolling.
5170     *
5171     * @param y Where the user touched
5172     * @return The position of the first (or only) item in the row closest to y
5173     */
5174    int findClosestMotionRow(int y) {
5175        final int childCount = getChildCount();
5176        if (childCount == 0) {
5177            return INVALID_POSITION;
5178        }
5179
5180        final int motionRow = findMotionRow(y);
5181        return motionRow != INVALID_POSITION ? motionRow : mFirstPosition + childCount - 1;
5182    }
5183
5184    /**
5185     * Causes all the views to be rebuilt and redrawn.
5186     */
5187    public void invalidateViews() {
5188        mDataChanged = true;
5189        rememberSyncState();
5190        requestLayout();
5191        invalidate();
5192    }
5193
5194    /**
5195     * If there is a selection returns false.
5196     * Otherwise resurrects the selection and returns true if resurrected.
5197     */
5198    boolean resurrectSelectionIfNeeded() {
5199        if (mSelectedPosition < 0 && resurrectSelection()) {
5200            updateSelectorState();
5201            return true;
5202        }
5203        return false;
5204    }
5205
5206    /**
5207     * Makes the item at the supplied position selected.
5208     *
5209     * @param position the position of the new selection
5210     */
5211    abstract void setSelectionInt(int position);
5212
5213    /**
5214     * Attempt to bring the selection back if the user is switching from touch
5215     * to trackball mode
5216     * @return Whether selection was set to something.
5217     */
5218    boolean resurrectSelection() {
5219        final int childCount = getChildCount();
5220
5221        if (childCount <= 0) {
5222            return false;
5223        }
5224
5225        int selectedTop = 0;
5226        int selectedPos;
5227        int childrenTop = mListPadding.top;
5228        int childrenBottom = mBottom - mTop - mListPadding.bottom;
5229        final int firstPosition = mFirstPosition;
5230        final int toPosition = mResurrectToPosition;
5231        boolean down = true;
5232
5233        if (toPosition >= firstPosition && toPosition < firstPosition + childCount) {
5234            selectedPos = toPosition;
5235
5236            final View selected = getChildAt(selectedPos - mFirstPosition);
5237            selectedTop = selected.getTop();
5238            int selectedBottom = selected.getBottom();
5239
5240            // We are scrolled, don't get in the fade
5241            if (selectedTop < childrenTop) {
5242                selectedTop = childrenTop + getVerticalFadingEdgeLength();
5243            } else if (selectedBottom > childrenBottom) {
5244                selectedTop = childrenBottom - selected.getMeasuredHeight()
5245                        - getVerticalFadingEdgeLength();
5246            }
5247        } else {
5248            if (toPosition < firstPosition) {
5249                // Default to selecting whatever is first
5250                selectedPos = firstPosition;
5251                for (int i = 0; i < childCount; i++) {
5252                    final View v = getChildAt(i);
5253                    final int top = v.getTop();
5254
5255                    if (i == 0) {
5256                        // Remember the position of the first item
5257                        selectedTop = top;
5258                        // See if we are scrolled at all
5259                        if (firstPosition > 0 || top < childrenTop) {
5260                            // If we are scrolled, don't select anything that is
5261                            // in the fade region
5262                            childrenTop += getVerticalFadingEdgeLength();
5263                        }
5264                    }
5265                    if (top >= childrenTop) {
5266                        // Found a view whose top is fully visisble
5267                        selectedPos = firstPosition + i;
5268                        selectedTop = top;
5269                        break;
5270                    }
5271                }
5272            } else {
5273                final int itemCount = mItemCount;
5274                down = false;
5275                selectedPos = firstPosition + childCount - 1;
5276
5277                for (int i = childCount - 1; i >= 0; i--) {
5278                    final View v = getChildAt(i);
5279                    final int top = v.getTop();
5280                    final int bottom = v.getBottom();
5281
5282                    if (i == childCount - 1) {
5283                        selectedTop = top;
5284                        if (firstPosition + childCount < itemCount || bottom > childrenBottom) {
5285                            childrenBottom -= getVerticalFadingEdgeLength();
5286                        }
5287                    }
5288
5289                    if (bottom <= childrenBottom) {
5290                        selectedPos = firstPosition + i;
5291                        selectedTop = top;
5292                        break;
5293                    }
5294                }
5295            }
5296        }
5297
5298        mResurrectToPosition = INVALID_POSITION;
5299        removeCallbacks(mFlingRunnable);
5300        if (mPositionScroller != null) {
5301            mPositionScroller.stop();
5302        }
5303        mTouchMode = TOUCH_MODE_REST;
5304        clearScrollingCache();
5305        mSpecificTop = selectedTop;
5306        selectedPos = lookForSelectablePosition(selectedPos, down);
5307        if (selectedPos >= firstPosition && selectedPos <= getLastVisiblePosition()) {
5308            mLayoutMode = LAYOUT_SPECIFIC;
5309            updateSelectorState();
5310            setSelectionInt(selectedPos);
5311            invokeOnItemScrollListener();
5312        } else {
5313            selectedPos = INVALID_POSITION;
5314        }
5315        reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
5316
5317        return selectedPos >= 0;
5318    }
5319
5320    void confirmCheckedPositionsById() {
5321        // Clear out the positional check states, we'll rebuild it below from IDs.
5322        mCheckStates.clear();
5323
5324        boolean checkedCountChanged = false;
5325        for (int checkedIndex = 0; checkedIndex < mCheckedIdStates.size(); checkedIndex++) {
5326            final long id = mCheckedIdStates.keyAt(checkedIndex);
5327            final int lastPos = mCheckedIdStates.valueAt(checkedIndex);
5328
5329            final long lastPosId = mAdapter.getItemId(lastPos);
5330            if (id != lastPosId) {
5331                // Look around to see if the ID is nearby. If not, uncheck it.
5332                final int start = Math.max(0, lastPos - CHECK_POSITION_SEARCH_DISTANCE);
5333                final int end = Math.min(lastPos + CHECK_POSITION_SEARCH_DISTANCE, mItemCount);
5334                boolean found = false;
5335                for (int searchPos = start; searchPos < end; searchPos++) {
5336                    final long searchId = mAdapter.getItemId(searchPos);
5337                    if (id == searchId) {
5338                        found = true;
5339                        mCheckStates.put(searchPos, true);
5340                        mCheckedIdStates.setValueAt(checkedIndex, searchPos);
5341                        break;
5342                    }
5343                }
5344
5345                if (!found) {
5346                    mCheckedIdStates.delete(id);
5347                    checkedIndex--;
5348                    mCheckedItemCount--;
5349                    checkedCountChanged = true;
5350                    if (mChoiceActionMode != null && mMultiChoiceModeCallback != null) {
5351                        mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
5352                                lastPos, id, false);
5353                    }
5354                }
5355            } else {
5356                mCheckStates.put(lastPos, true);
5357            }
5358        }
5359
5360        if (checkedCountChanged && mChoiceActionMode != null) {
5361            mChoiceActionMode.invalidate();
5362        }
5363    }
5364
5365    @Override
5366    protected void handleDataChanged() {
5367        int count = mItemCount;
5368        int lastHandledItemCount = mLastHandledItemCount;
5369        mLastHandledItemCount = mItemCount;
5370
5371        if (mChoiceMode != CHOICE_MODE_NONE && mAdapter != null && mAdapter.hasStableIds()) {
5372            confirmCheckedPositionsById();
5373        }
5374
5375        // TODO: In the future we can recycle these views based on stable ID instead.
5376        mRecycler.clearTransientStateViews();
5377
5378        if (count > 0) {
5379            int newPos;
5380            int selectablePos;
5381
5382            // Find the row we are supposed to sync to
5383            if (mNeedSync) {
5384                // Update this first, since setNextSelectedPositionInt inspects it
5385                mNeedSync = false;
5386                mPendingSync = null;
5387
5388                if (mTranscriptMode == TRANSCRIPT_MODE_ALWAYS_SCROLL) {
5389                    mLayoutMode = LAYOUT_FORCE_BOTTOM;
5390                    return;
5391                } else if (mTranscriptMode == TRANSCRIPT_MODE_NORMAL) {
5392                    if (mForceTranscriptScroll) {
5393                        mForceTranscriptScroll = false;
5394                        mLayoutMode = LAYOUT_FORCE_BOTTOM;
5395                        return;
5396                    }
5397                    final int childCount = getChildCount();
5398                    final int listBottom = getHeight() - getPaddingBottom();
5399                    final View lastChild = getChildAt(childCount - 1);
5400                    final int lastBottom = lastChild != null ? lastChild.getBottom() : listBottom;
5401                    if (mFirstPosition + childCount >= lastHandledItemCount &&
5402                            lastBottom <= listBottom) {
5403                        mLayoutMode = LAYOUT_FORCE_BOTTOM;
5404                        return;
5405                    }
5406                    // Something new came in and we didn't scroll; give the user a clue that
5407                    // there's something new.
5408                    awakenScrollBars();
5409                }
5410
5411                switch (mSyncMode) {
5412                case SYNC_SELECTED_POSITION:
5413                    if (isInTouchMode()) {
5414                        // We saved our state when not in touch mode. (We know this because
5415                        // mSyncMode is SYNC_SELECTED_POSITION.) Now we are trying to
5416                        // restore in touch mode. Just leave mSyncPosition as it is (possibly
5417                        // adjusting if the available range changed) and return.
5418                        mLayoutMode = LAYOUT_SYNC;
5419                        mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
5420
5421                        return;
5422                    } else {
5423                        // See if we can find a position in the new data with the same
5424                        // id as the old selection. This will change mSyncPosition.
5425                        newPos = findSyncPosition();
5426                        if (newPos >= 0) {
5427                            // Found it. Now verify that new selection is still selectable
5428                            selectablePos = lookForSelectablePosition(newPos, true);
5429                            if (selectablePos == newPos) {
5430                                // Same row id is selected
5431                                mSyncPosition = newPos;
5432
5433                                if (mSyncHeight == getHeight()) {
5434                                    // If we are at the same height as when we saved state, try
5435                                    // to restore the scroll position too.
5436                                    mLayoutMode = LAYOUT_SYNC;
5437                                } else {
5438                                    // We are not the same height as when the selection was saved, so
5439                                    // don't try to restore the exact position
5440                                    mLayoutMode = LAYOUT_SET_SELECTION;
5441                                }
5442
5443                                // Restore selection
5444                                setNextSelectedPositionInt(newPos);
5445                                return;
5446                            }
5447                        }
5448                    }
5449                    break;
5450                case SYNC_FIRST_POSITION:
5451                    // Leave mSyncPosition as it is -- just pin to available range
5452                    mLayoutMode = LAYOUT_SYNC;
5453                    mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
5454
5455                    return;
5456                }
5457            }
5458
5459            if (!isInTouchMode()) {
5460                // We couldn't find matching data -- try to use the same position
5461                newPos = getSelectedItemPosition();
5462
5463                // Pin position to the available range
5464                if (newPos >= count) {
5465                    newPos = count - 1;
5466                }
5467                if (newPos < 0) {
5468                    newPos = 0;
5469                }
5470
5471                // Make sure we select something selectable -- first look down
5472                selectablePos = lookForSelectablePosition(newPos, true);
5473
5474                if (selectablePos >= 0) {
5475                    setNextSelectedPositionInt(selectablePos);
5476                    return;
5477                } else {
5478                    // Looking down didn't work -- try looking up
5479                    selectablePos = lookForSelectablePosition(newPos, false);
5480                    if (selectablePos >= 0) {
5481                        setNextSelectedPositionInt(selectablePos);
5482                        return;
5483                    }
5484                }
5485            } else {
5486
5487                // We already know where we want to resurrect the selection
5488                if (mResurrectToPosition >= 0) {
5489                    return;
5490                }
5491            }
5492
5493        }
5494
5495        // Nothing is selected. Give up and reset everything.
5496        mLayoutMode = mStackFromBottom ? LAYOUT_FORCE_BOTTOM : LAYOUT_FORCE_TOP;
5497        mSelectedPosition = INVALID_POSITION;
5498        mSelectedRowId = INVALID_ROW_ID;
5499        mNextSelectedPosition = INVALID_POSITION;
5500        mNextSelectedRowId = INVALID_ROW_ID;
5501        mNeedSync = false;
5502        mPendingSync = null;
5503        mSelectorPosition = INVALID_POSITION;
5504        checkSelectionChanged();
5505    }
5506
5507    @Override
5508    protected void onDisplayHint(int hint) {
5509        super.onDisplayHint(hint);
5510        switch (hint) {
5511            case INVISIBLE:
5512                if (mPopup != null && mPopup.isShowing()) {
5513                    dismissPopup();
5514                }
5515                break;
5516            case VISIBLE:
5517                if (mFiltered && mPopup != null && !mPopup.isShowing()) {
5518                    showPopup();
5519                }
5520                break;
5521        }
5522        mPopupHidden = hint == INVISIBLE;
5523    }
5524
5525    /**
5526     * Removes the filter window
5527     */
5528    private void dismissPopup() {
5529        if (mPopup != null) {
5530            mPopup.dismiss();
5531        }
5532    }
5533
5534    /**
5535     * Shows the filter window
5536     */
5537    private void showPopup() {
5538        // Make sure we have a window before showing the popup
5539        if (getWindowVisibility() == View.VISIBLE) {
5540            createTextFilter(true);
5541            positionPopup();
5542            // Make sure we get focus if we are showing the popup
5543            checkFocus();
5544        }
5545    }
5546
5547    private void positionPopup() {
5548        int screenHeight = getResources().getDisplayMetrics().heightPixels;
5549        final int[] xy = new int[2];
5550        getLocationOnScreen(xy);
5551        // TODO: The 20 below should come from the theme
5552        // TODO: And the gravity should be defined in the theme as well
5553        final int bottomGap = screenHeight - xy[1] - getHeight() + (int) (mDensityScale * 20);
5554        if (!mPopup.isShowing()) {
5555            mPopup.showAtLocation(this, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL,
5556                    xy[0], bottomGap);
5557        } else {
5558            mPopup.update(xy[0], bottomGap, -1, -1);
5559        }
5560    }
5561
5562    /**
5563     * What is the distance between the source and destination rectangles given the direction of
5564     * focus navigation between them? The direction basically helps figure out more quickly what is
5565     * self evident by the relationship between the rects...
5566     *
5567     * @param source the source rectangle
5568     * @param dest the destination rectangle
5569     * @param direction the direction
5570     * @return the distance between the rectangles
5571     */
5572    static int getDistance(Rect source, Rect dest, int direction) {
5573        int sX, sY; // source x, y
5574        int dX, dY; // dest x, y
5575        switch (direction) {
5576        case View.FOCUS_RIGHT:
5577            sX = source.right;
5578            sY = source.top + source.height() / 2;
5579            dX = dest.left;
5580            dY = dest.top + dest.height() / 2;
5581            break;
5582        case View.FOCUS_DOWN:
5583            sX = source.left + source.width() / 2;
5584            sY = source.bottom;
5585            dX = dest.left + dest.width() / 2;
5586            dY = dest.top;
5587            break;
5588        case View.FOCUS_LEFT:
5589            sX = source.left;
5590            sY = source.top + source.height() / 2;
5591            dX = dest.right;
5592            dY = dest.top + dest.height() / 2;
5593            break;
5594        case View.FOCUS_UP:
5595            sX = source.left + source.width() / 2;
5596            sY = source.top;
5597            dX = dest.left + dest.width() / 2;
5598            dY = dest.bottom;
5599            break;
5600        case View.FOCUS_FORWARD:
5601        case View.FOCUS_BACKWARD:
5602            sX = source.right + source.width() / 2;
5603            sY = source.top + source.height() / 2;
5604            dX = dest.left + dest.width() / 2;
5605            dY = dest.top + dest.height() / 2;
5606            break;
5607        default:
5608            throw new IllegalArgumentException("direction must be one of "
5609                    + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, "
5610                    + "FOCUS_FORWARD, FOCUS_BACKWARD}.");
5611        }
5612        int deltaX = dX - sX;
5613        int deltaY = dY - sY;
5614        return deltaY * deltaY + deltaX * deltaX;
5615    }
5616
5617    @Override
5618    protected boolean isInFilterMode() {
5619        return mFiltered;
5620    }
5621
5622    /**
5623     * Sends a key to the text filter window
5624     *
5625     * @param keyCode The keycode for the event
5626     * @param event The actual key event
5627     *
5628     * @return True if the text filter handled the event, false otherwise.
5629     */
5630    boolean sendToTextFilter(int keyCode, int count, KeyEvent event) {
5631        if (!acceptFilter()) {
5632            return false;
5633        }
5634
5635        boolean handled = false;
5636        boolean okToSend = true;
5637        switch (keyCode) {
5638        case KeyEvent.KEYCODE_DPAD_UP:
5639        case KeyEvent.KEYCODE_DPAD_DOWN:
5640        case KeyEvent.KEYCODE_DPAD_LEFT:
5641        case KeyEvent.KEYCODE_DPAD_RIGHT:
5642        case KeyEvent.KEYCODE_DPAD_CENTER:
5643        case KeyEvent.KEYCODE_ENTER:
5644            okToSend = false;
5645            break;
5646        case KeyEvent.KEYCODE_BACK:
5647            if (mFiltered && mPopup != null && mPopup.isShowing()) {
5648                if (event.getAction() == KeyEvent.ACTION_DOWN
5649                        && event.getRepeatCount() == 0) {
5650                    KeyEvent.DispatcherState state = getKeyDispatcherState();
5651                    if (state != null) {
5652                        state.startTracking(event, this);
5653                    }
5654                    handled = true;
5655                } else if (event.getAction() == KeyEvent.ACTION_UP
5656                        && event.isTracking() && !event.isCanceled()) {
5657                    handled = true;
5658                    mTextFilter.setText("");
5659                }
5660            }
5661            okToSend = false;
5662            break;
5663        case KeyEvent.KEYCODE_SPACE:
5664            // Only send spaces once we are filtered
5665            okToSend = mFiltered;
5666            break;
5667        }
5668
5669        if (okToSend) {
5670            createTextFilter(true);
5671
5672            KeyEvent forwardEvent = event;
5673            if (forwardEvent.getRepeatCount() > 0) {
5674                forwardEvent = KeyEvent.changeTimeRepeat(event, event.getEventTime(), 0);
5675            }
5676
5677            int action = event.getAction();
5678            switch (action) {
5679                case KeyEvent.ACTION_DOWN:
5680                    handled = mTextFilter.onKeyDown(keyCode, forwardEvent);
5681                    break;
5682
5683                case KeyEvent.ACTION_UP:
5684                    handled = mTextFilter.onKeyUp(keyCode, forwardEvent);
5685                    break;
5686
5687                case KeyEvent.ACTION_MULTIPLE:
5688                    handled = mTextFilter.onKeyMultiple(keyCode, count, event);
5689                    break;
5690            }
5691        }
5692        return handled;
5693    }
5694
5695    /**
5696     * Return an InputConnection for editing of the filter text.
5697     */
5698    @Override
5699    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
5700        if (isTextFilterEnabled()) {
5701            if (mPublicInputConnection == null) {
5702                mDefInputConnection = new BaseInputConnection(this, false);
5703                mPublicInputConnection = new InputConnectionWrapper(outAttrs);
5704            }
5705            outAttrs.inputType = EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_FILTER;
5706            outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE;
5707            return mPublicInputConnection;
5708        }
5709        return null;
5710    }
5711
5712    private class InputConnectionWrapper implements InputConnection {
5713        private final EditorInfo mOutAttrs;
5714        private InputConnection mTarget;
5715
5716        public InputConnectionWrapper(EditorInfo outAttrs) {
5717            mOutAttrs = outAttrs;
5718        }
5719
5720        private InputConnection getTarget() {
5721            if (mTarget == null) {
5722                mTarget = getTextFilterInput().onCreateInputConnection(mOutAttrs);
5723            }
5724            return mTarget;
5725        }
5726
5727        @Override
5728        public boolean reportFullscreenMode(boolean enabled) {
5729            // Use our own input connection, since it is
5730            // the "real" one the IME is talking with.
5731            return mDefInputConnection.reportFullscreenMode(enabled);
5732        }
5733
5734        @Override
5735        public boolean performEditorAction(int editorAction) {
5736            // The editor is off in its own window; we need to be
5737            // the one that does this.
5738            if (editorAction == EditorInfo.IME_ACTION_DONE) {
5739                InputMethodManager imm = (InputMethodManager)
5740                        getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
5741                if (imm != null) {
5742                    imm.hideSoftInputFromWindow(getWindowToken(), 0);
5743                }
5744                return true;
5745            }
5746            return false;
5747        }
5748
5749        @Override
5750        public boolean sendKeyEvent(KeyEvent event) {
5751            // Use our own input connection, since the filter
5752            // text view may not be shown in a window so has
5753            // no ViewAncestor to dispatch events with.
5754            return mDefInputConnection.sendKeyEvent(event);
5755        }
5756
5757        @Override
5758        public CharSequence getTextBeforeCursor(int n, int flags) {
5759            if (mTarget == null) return "";
5760            return mTarget.getTextBeforeCursor(n, flags);
5761        }
5762
5763        @Override
5764        public CharSequence getTextAfterCursor(int n, int flags) {
5765            if (mTarget == null) return "";
5766            return mTarget.getTextAfterCursor(n, flags);
5767        }
5768
5769        @Override
5770        public CharSequence getSelectedText(int flags) {
5771            if (mTarget == null) return "";
5772            return mTarget.getSelectedText(flags);
5773        }
5774
5775        @Override
5776        public int getCursorCapsMode(int reqModes) {
5777            if (mTarget == null) return InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
5778            return mTarget.getCursorCapsMode(reqModes);
5779        }
5780
5781        @Override
5782        public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
5783            return getTarget().getExtractedText(request, flags);
5784        }
5785
5786        @Override
5787        public boolean deleteSurroundingText(int beforeLength, int afterLength) {
5788            return getTarget().deleteSurroundingText(beforeLength, afterLength);
5789        }
5790
5791        @Override
5792        public boolean setComposingText(CharSequence text, int newCursorPosition) {
5793            return getTarget().setComposingText(text, newCursorPosition);
5794        }
5795
5796        @Override
5797        public boolean setComposingRegion(int start, int end) {
5798            return getTarget().setComposingRegion(start, end);
5799        }
5800
5801        @Override
5802        public boolean finishComposingText() {
5803            return mTarget == null || mTarget.finishComposingText();
5804        }
5805
5806        @Override
5807        public boolean commitText(CharSequence text, int newCursorPosition) {
5808            return getTarget().commitText(text, newCursorPosition);
5809        }
5810
5811        @Override
5812        public boolean commitCompletion(CompletionInfo text) {
5813            return getTarget().commitCompletion(text);
5814        }
5815
5816        @Override
5817        public boolean commitCorrection(CorrectionInfo correctionInfo) {
5818            return getTarget().commitCorrection(correctionInfo);
5819        }
5820
5821        @Override
5822        public boolean setSelection(int start, int end) {
5823            return getTarget().setSelection(start, end);
5824        }
5825
5826        @Override
5827        public boolean performContextMenuAction(int id) {
5828            return getTarget().performContextMenuAction(id);
5829        }
5830
5831        @Override
5832        public boolean beginBatchEdit() {
5833            return getTarget().beginBatchEdit();
5834        }
5835
5836        @Override
5837        public boolean endBatchEdit() {
5838            return getTarget().endBatchEdit();
5839        }
5840
5841        @Override
5842        public boolean clearMetaKeyStates(int states) {
5843            return getTarget().clearMetaKeyStates(states);
5844        }
5845
5846        @Override
5847        public boolean performPrivateCommand(String action, Bundle data) {
5848            return getTarget().performPrivateCommand(action, data);
5849        }
5850    }
5851
5852    /**
5853     * For filtering we proxy an input connection to an internal text editor,
5854     * and this allows the proxying to happen.
5855     */
5856    @Override
5857    public boolean checkInputConnectionProxy(View view) {
5858        return view == mTextFilter;
5859    }
5860
5861    /**
5862     * Creates the window for the text filter and populates it with an EditText field;
5863     *
5864     * @param animateEntrance true if the window should appear with an animation
5865     */
5866    private void createTextFilter(boolean animateEntrance) {
5867        if (mPopup == null) {
5868            PopupWindow p = new PopupWindow(getContext());
5869            p.setFocusable(false);
5870            p.setTouchable(false);
5871            p.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
5872            p.setContentView(getTextFilterInput());
5873            p.setWidth(LayoutParams.WRAP_CONTENT);
5874            p.setHeight(LayoutParams.WRAP_CONTENT);
5875            p.setBackgroundDrawable(null);
5876            mPopup = p;
5877            getViewTreeObserver().addOnGlobalLayoutListener(this);
5878            mGlobalLayoutListenerAddedFilter = true;
5879        }
5880        if (animateEntrance) {
5881            mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilter);
5882        } else {
5883            mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilterRestore);
5884        }
5885    }
5886
5887    private EditText getTextFilterInput() {
5888        if (mTextFilter == null) {
5889            final LayoutInflater layoutInflater = LayoutInflater.from(getContext());
5890            mTextFilter = (EditText) layoutInflater.inflate(
5891                    com.android.internal.R.layout.typing_filter, null);
5892            // For some reason setting this as the "real" input type changes
5893            // the text view in some way that it doesn't work, and I don't
5894            // want to figure out why this is.
5895            mTextFilter.setRawInputType(EditorInfo.TYPE_CLASS_TEXT
5896                    | EditorInfo.TYPE_TEXT_VARIATION_FILTER);
5897            mTextFilter.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI);
5898            mTextFilter.addTextChangedListener(this);
5899        }
5900        return mTextFilter;
5901    }
5902
5903    /**
5904     * Clear the text filter.
5905     */
5906    public void clearTextFilter() {
5907        if (mFiltered) {
5908            getTextFilterInput().setText("");
5909            mFiltered = false;
5910            if (mPopup != null && mPopup.isShowing()) {
5911                dismissPopup();
5912            }
5913        }
5914    }
5915
5916    /**
5917     * Returns if the ListView currently has a text filter.
5918     */
5919    public boolean hasTextFilter() {
5920        return mFiltered;
5921    }
5922
5923    @Override
5924    public void onGlobalLayout() {
5925        if (isShown()) {
5926            // Show the popup if we are filtered
5927            if (mFiltered && mPopup != null && !mPopup.isShowing() && !mPopupHidden) {
5928                showPopup();
5929            }
5930        } else {
5931            // Hide the popup when we are no longer visible
5932            if (mPopup != null && mPopup.isShowing()) {
5933                dismissPopup();
5934            }
5935        }
5936
5937    }
5938
5939    /**
5940     * For our text watcher that is associated with the text filter.  Does
5941     * nothing.
5942     */
5943    @Override
5944    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
5945    }
5946
5947    /**
5948     * For our text watcher that is associated with the text filter. Performs
5949     * the actual filtering as the text changes, and takes care of hiding and
5950     * showing the popup displaying the currently entered filter text.
5951     */
5952    @Override
5953    public void onTextChanged(CharSequence s, int start, int before, int count) {
5954        if (isTextFilterEnabled()) {
5955            createTextFilter(true);
5956            int length = s.length();
5957            boolean showing = mPopup.isShowing();
5958            if (!showing && length > 0) {
5959                // Show the filter popup if necessary
5960                showPopup();
5961                mFiltered = true;
5962            } else if (showing && length == 0) {
5963                // Remove the filter popup if the user has cleared all text
5964                dismissPopup();
5965                mFiltered = false;
5966            }
5967            if (mAdapter instanceof Filterable) {
5968                Filter f = ((Filterable) mAdapter).getFilter();
5969                // Filter should not be null when we reach this part
5970                if (f != null) {
5971                    f.filter(s, this);
5972                } else {
5973                    throw new IllegalStateException("You cannot call onTextChanged with a non "
5974                            + "filterable adapter");
5975                }
5976            }
5977        }
5978    }
5979
5980    /**
5981     * For our text watcher that is associated with the text filter.  Does
5982     * nothing.
5983     */
5984    @Override
5985    public void afterTextChanged(Editable s) {
5986    }
5987
5988    @Override
5989    public void onFilterComplete(int count) {
5990        if (mSelectedPosition < 0 && count > 0) {
5991            mResurrectToPosition = INVALID_POSITION;
5992            resurrectSelection();
5993        }
5994    }
5995
5996    @Override
5997    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
5998        return new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
5999                ViewGroup.LayoutParams.WRAP_CONTENT, 0);
6000    }
6001
6002    @Override
6003    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
6004        return new LayoutParams(p);
6005    }
6006
6007    @Override
6008    public LayoutParams generateLayoutParams(AttributeSet attrs) {
6009        return new AbsListView.LayoutParams(getContext(), attrs);
6010    }
6011
6012    @Override
6013    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
6014        return p instanceof AbsListView.LayoutParams;
6015    }
6016
6017    /**
6018     * Puts the list or grid into transcript mode. In this mode the list or grid will always scroll
6019     * to the bottom to show new items.
6020     *
6021     * @param mode the transcript mode to set
6022     *
6023     * @see #TRANSCRIPT_MODE_DISABLED
6024     * @see #TRANSCRIPT_MODE_NORMAL
6025     * @see #TRANSCRIPT_MODE_ALWAYS_SCROLL
6026     */
6027    public void setTranscriptMode(int mode) {
6028        mTranscriptMode = mode;
6029    }
6030
6031    /**
6032     * Returns the current transcript mode.
6033     *
6034     * @return {@link #TRANSCRIPT_MODE_DISABLED}, {@link #TRANSCRIPT_MODE_NORMAL} or
6035     *         {@link #TRANSCRIPT_MODE_ALWAYS_SCROLL}
6036     */
6037    public int getTranscriptMode() {
6038        return mTranscriptMode;
6039    }
6040
6041    @Override
6042    public int getSolidColor() {
6043        return mCacheColorHint;
6044    }
6045
6046    /**
6047     * When set to a non-zero value, the cache color hint indicates that this list is always drawn
6048     * on top of a solid, single-color, opaque background.
6049     *
6050     * Zero means that what's behind this object is translucent (non solid) or is not made of a
6051     * single color. This hint will not affect any existing background drawable set on this view (
6052     * typically set via {@link #setBackgroundDrawable(Drawable)}).
6053     *
6054     * @param color The background color
6055     */
6056    public void setCacheColorHint(int color) {
6057        if (color != mCacheColorHint) {
6058            mCacheColorHint = color;
6059            int count = getChildCount();
6060            for (int i = 0; i < count; i++) {
6061                getChildAt(i).setDrawingCacheBackgroundColor(color);
6062            }
6063            mRecycler.setCacheColorHint(color);
6064        }
6065    }
6066
6067    /**
6068     * When set to a non-zero value, the cache color hint indicates that this list is always drawn
6069     * on top of a solid, single-color, opaque background
6070     *
6071     * @return The cache color hint
6072     */
6073    @ViewDebug.ExportedProperty(category = "drawing")
6074    public int getCacheColorHint() {
6075        return mCacheColorHint;
6076    }
6077
6078    /**
6079     * Move all views (excluding headers and footers) held by this AbsListView into the supplied
6080     * List. This includes views displayed on the screen as well as views stored in AbsListView's
6081     * internal view recycler.
6082     *
6083     * @param views A list into which to put the reclaimed views
6084     */
6085    public void reclaimViews(List<View> views) {
6086        int childCount = getChildCount();
6087        RecyclerListener listener = mRecycler.mRecyclerListener;
6088
6089        // Reclaim views on screen
6090        for (int i = 0; i < childCount; i++) {
6091            View child = getChildAt(i);
6092            AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
6093            // Don't reclaim header or footer views, or views that should be ignored
6094            if (lp != null && mRecycler.shouldRecycleViewType(lp.viewType)) {
6095                views.add(child);
6096                child.setAccessibilityDelegate(null);
6097                if (listener != null) {
6098                    // Pretend they went through the scrap heap
6099                    listener.onMovedToScrapHeap(child);
6100                }
6101            }
6102        }
6103        mRecycler.reclaimScrapViews(views);
6104        removeAllViewsInLayout();
6105    }
6106
6107    private void finishGlows() {
6108        if (mEdgeGlowTop != null) {
6109            mEdgeGlowTop.finish();
6110            mEdgeGlowBottom.finish();
6111        }
6112    }
6113
6114    /**
6115     * Sets up this AbsListView to use a remote views adapter which connects to a RemoteViewsService
6116     * through the specified intent.
6117     * @param intent the intent used to identify the RemoteViewsService for the adapter to connect to.
6118     */
6119    public void setRemoteViewsAdapter(Intent intent) {
6120        // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
6121        // service handling the specified intent.
6122        if (mRemoteAdapter != null) {
6123            Intent.FilterComparison fcNew = new Intent.FilterComparison(intent);
6124            Intent.FilterComparison fcOld = new Intent.FilterComparison(
6125                    mRemoteAdapter.getRemoteViewsServiceIntent());
6126            if (fcNew.equals(fcOld)) {
6127                return;
6128            }
6129        }
6130        mDeferNotifyDataSetChanged = false;
6131        // Otherwise, create a new RemoteViewsAdapter for binding
6132        mRemoteAdapter = new RemoteViewsAdapter(getContext(), intent, this);
6133        if (mRemoteAdapter.isDataReady()) {
6134            setAdapter(mRemoteAdapter);
6135        }
6136    }
6137
6138    /**
6139     * Sets up the onClickHandler to be used by the RemoteViewsAdapter when inflating RemoteViews
6140     *
6141     * @param handler The OnClickHandler to use when inflating RemoteViews.
6142     *
6143     * @hide
6144     */
6145    public void setRemoteViewsOnClickHandler(OnClickHandler handler) {
6146        // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
6147        // service handling the specified intent.
6148        if (mRemoteAdapter != null) {
6149            mRemoteAdapter.setRemoteViewsOnClickHandler(handler);
6150        }
6151    }
6152
6153    /**
6154     * This defers a notifyDataSetChanged on the pending RemoteViewsAdapter if it has not
6155     * connected yet.
6156     */
6157    @Override
6158    public void deferNotifyDataSetChanged() {
6159        mDeferNotifyDataSetChanged = true;
6160    }
6161
6162    /**
6163     * Called back when the adapter connects to the RemoteViewsService.
6164     */
6165    @Override
6166    public boolean onRemoteAdapterConnected() {
6167        if (mRemoteAdapter != mAdapter) {
6168            setAdapter(mRemoteAdapter);
6169            if (mDeferNotifyDataSetChanged) {
6170                mRemoteAdapter.notifyDataSetChanged();
6171                mDeferNotifyDataSetChanged = false;
6172            }
6173            return false;
6174        } else if (mRemoteAdapter != null) {
6175            mRemoteAdapter.superNotifyDataSetChanged();
6176            return true;
6177        }
6178        return false;
6179    }
6180
6181    /**
6182     * Called back when the adapter disconnects from the RemoteViewsService.
6183     */
6184    @Override
6185    public void onRemoteAdapterDisconnected() {
6186        // If the remote adapter disconnects, we keep it around
6187        // since the currently displayed items are still cached.
6188        // Further, we want the service to eventually reconnect
6189        // when necessary, as triggered by this view requesting
6190        // items from the Adapter.
6191    }
6192
6193    /**
6194     * Hints the RemoteViewsAdapter, if it exists, about which views are currently
6195     * being displayed by the AbsListView.
6196     */
6197    void setVisibleRangeHint(int start, int end) {
6198        if (mRemoteAdapter != null) {
6199            mRemoteAdapter.setVisibleRangeHint(start, end);
6200        }
6201    }
6202
6203    /**
6204     * Sets the recycler listener to be notified whenever a View is set aside in
6205     * the recycler for later reuse. This listener can be used to free resources
6206     * associated to the View.
6207     *
6208     * @param listener The recycler listener to be notified of views set aside
6209     *        in the recycler.
6210     *
6211     * @see android.widget.AbsListView.RecycleBin
6212     * @see android.widget.AbsListView.RecyclerListener
6213     */
6214    public void setRecyclerListener(RecyclerListener listener) {
6215        mRecycler.mRecyclerListener = listener;
6216    }
6217
6218    class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver {
6219        @Override
6220        public void onChanged() {
6221            super.onChanged();
6222            if (mFastScroller != null) {
6223                mFastScroller.onSectionsChanged();
6224            }
6225        }
6226
6227        @Override
6228        public void onInvalidated() {
6229            super.onInvalidated();
6230            if (mFastScroller != null) {
6231                mFastScroller.onSectionsChanged();
6232            }
6233        }
6234    }
6235
6236    /**
6237     * A MultiChoiceModeListener receives events for {@link AbsListView#CHOICE_MODE_MULTIPLE_MODAL}.
6238     * It acts as the {@link ActionMode.Callback} for the selection mode and also receives
6239     * {@link #onItemCheckedStateChanged(ActionMode, int, long, boolean)} events when the user
6240     * selects and deselects list items.
6241     */
6242    public interface MultiChoiceModeListener extends ActionMode.Callback {
6243        /**
6244         * Called when an item is checked or unchecked during selection mode.
6245         *
6246         * @param mode The {@link ActionMode} providing the selection mode
6247         * @param position Adapter position of the item that was checked or unchecked
6248         * @param id Adapter ID of the item that was checked or unchecked
6249         * @param checked <code>true</code> if the item is now checked, <code>false</code>
6250         *                if the item is now unchecked.
6251         */
6252        public void onItemCheckedStateChanged(ActionMode mode,
6253                int position, long id, boolean checked);
6254    }
6255
6256    class MultiChoiceModeWrapper implements MultiChoiceModeListener {
6257        private MultiChoiceModeListener mWrapped;
6258
6259        public void setWrapped(MultiChoiceModeListener wrapped) {
6260            mWrapped = wrapped;
6261        }
6262
6263        public boolean hasWrappedCallback() {
6264            return mWrapped != null;
6265        }
6266
6267        @Override
6268        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
6269            if (mWrapped.onCreateActionMode(mode, menu)) {
6270                // Initialize checked graphic state?
6271                setLongClickable(false);
6272                return true;
6273            }
6274            return false;
6275        }
6276
6277        @Override
6278        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
6279            return mWrapped.onPrepareActionMode(mode, menu);
6280        }
6281
6282        @Override
6283        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
6284            return mWrapped.onActionItemClicked(mode, item);
6285        }
6286
6287        @Override
6288        public void onDestroyActionMode(ActionMode mode) {
6289            mWrapped.onDestroyActionMode(mode);
6290            mChoiceActionMode = null;
6291
6292            // Ending selection mode means deselecting everything.
6293            clearChoices();
6294
6295            mDataChanged = true;
6296            rememberSyncState();
6297            requestLayout();
6298
6299            setLongClickable(true);
6300        }
6301
6302        @Override
6303        public void onItemCheckedStateChanged(ActionMode mode,
6304                int position, long id, boolean checked) {
6305            mWrapped.onItemCheckedStateChanged(mode, position, id, checked);
6306
6307            // If there are no items selected we no longer need the selection mode.
6308            if (getCheckedItemCount() == 0) {
6309                mode.finish();
6310            }
6311        }
6312    }
6313
6314    /**
6315     * AbsListView extends LayoutParams to provide a place to hold the view type.
6316     */
6317    public static class LayoutParams extends ViewGroup.LayoutParams {
6318        /**
6319         * View type for this view, as returned by
6320         * {@link android.widget.Adapter#getItemViewType(int) }
6321         */
6322        @ViewDebug.ExportedProperty(category = "list", mapping = {
6323            @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_IGNORE, to = "ITEM_VIEW_TYPE_IGNORE"),
6324            @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_HEADER_OR_FOOTER, to = "ITEM_VIEW_TYPE_HEADER_OR_FOOTER")
6325        })
6326        int viewType;
6327
6328        /**
6329         * When this boolean is set, the view has been added to the AbsListView
6330         * at least once. It is used to know whether headers/footers have already
6331         * been added to the list view and whether they should be treated as
6332         * recycled views or not.
6333         */
6334        @ViewDebug.ExportedProperty(category = "list")
6335        boolean recycledHeaderFooter;
6336
6337        /**
6338         * When an AbsListView is measured with an AT_MOST measure spec, it needs
6339         * to obtain children views to measure itself. When doing so, the children
6340         * are not attached to the window, but put in the recycler which assumes
6341         * they've been attached before. Setting this flag will force the reused
6342         * view to be attached to the window rather than just attached to the
6343         * parent.
6344         */
6345        @ViewDebug.ExportedProperty(category = "list")
6346        boolean forceAdd;
6347
6348        /**
6349         * The position the view was removed from when pulled out of the
6350         * scrap heap.
6351         * @hide
6352         */
6353        int scrappedFromPosition;
6354
6355        /**
6356         * The ID the view represents
6357         */
6358        long itemId = -1;
6359
6360        public LayoutParams(Context c, AttributeSet attrs) {
6361            super(c, attrs);
6362        }
6363
6364        public LayoutParams(int w, int h) {
6365            super(w, h);
6366        }
6367
6368        public LayoutParams(int w, int h, int viewType) {
6369            super(w, h);
6370            this.viewType = viewType;
6371        }
6372
6373        public LayoutParams(ViewGroup.LayoutParams source) {
6374            super(source);
6375        }
6376    }
6377
6378    /**
6379     * A RecyclerListener is used to receive a notification whenever a View is placed
6380     * inside the RecycleBin's scrap heap. This listener is used to free resources
6381     * associated to Views placed in the RecycleBin.
6382     *
6383     * @see android.widget.AbsListView.RecycleBin
6384     * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
6385     */
6386    public static interface RecyclerListener {
6387        /**
6388         * Indicates that the specified View was moved into the recycler's scrap heap.
6389         * The view is not displayed on screen any more and any expensive resource
6390         * associated with the view should be discarded.
6391         *
6392         * @param view
6393         */
6394        void onMovedToScrapHeap(View view);
6395    }
6396
6397    /**
6398     * The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of
6399     * storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the
6400     * start of a layout. By construction, they are displaying current information. At the end of
6401     * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that
6402     * could potentially be used by the adapter to avoid allocating views unnecessarily.
6403     *
6404     * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
6405     * @see android.widget.AbsListView.RecyclerListener
6406     */
6407    class RecycleBin {
6408        private RecyclerListener mRecyclerListener;
6409
6410        /**
6411         * The position of the first view stored in mActiveViews.
6412         */
6413        private int mFirstActivePosition;
6414
6415        /**
6416         * Views that were on screen at the start of layout. This array is populated at the start of
6417         * layout, and at the end of layout all view in mActiveViews are moved to mScrapViews.
6418         * Views in mActiveViews represent a contiguous range of Views, with position of the first
6419         * view store in mFirstActivePosition.
6420         */
6421        private View[] mActiveViews = new View[0];
6422
6423        /**
6424         * Unsorted views that can be used by the adapter as a convert view.
6425         */
6426        private ArrayList<View>[] mScrapViews;
6427
6428        private int mViewTypeCount;
6429
6430        private ArrayList<View> mCurrentScrap;
6431
6432        private ArrayList<View> mSkippedScrap;
6433
6434        private SparseArray<View> mTransientStateViews;
6435        private LongSparseArray<View> mTransientStateViewsById;
6436
6437        public void setViewTypeCount(int viewTypeCount) {
6438            if (viewTypeCount < 1) {
6439                throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
6440            }
6441            //noinspection unchecked
6442            ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
6443            for (int i = 0; i < viewTypeCount; i++) {
6444                scrapViews[i] = new ArrayList<View>();
6445            }
6446            mViewTypeCount = viewTypeCount;
6447            mCurrentScrap = scrapViews[0];
6448            mScrapViews = scrapViews;
6449        }
6450
6451        public void markChildrenDirty() {
6452            if (mViewTypeCount == 1) {
6453                final ArrayList<View> scrap = mCurrentScrap;
6454                final int scrapCount = scrap.size();
6455                for (int i = 0; i < scrapCount; i++) {
6456                    scrap.get(i).forceLayout();
6457                }
6458            } else {
6459                final int typeCount = mViewTypeCount;
6460                for (int i = 0; i < typeCount; i++) {
6461                    final ArrayList<View> scrap = mScrapViews[i];
6462                    final int scrapCount = scrap.size();
6463                    for (int j = 0; j < scrapCount; j++) {
6464                        scrap.get(j).forceLayout();
6465                    }
6466                }
6467            }
6468            if (mTransientStateViews != null) {
6469                final int count = mTransientStateViews.size();
6470                for (int i = 0; i < count; i++) {
6471                    mTransientStateViews.valueAt(i).forceLayout();
6472                }
6473            }
6474            if (mTransientStateViewsById != null) {
6475                final int count = mTransientStateViewsById.size();
6476                for (int i = 0; i < count; i++) {
6477                    mTransientStateViewsById.valueAt(i).forceLayout();
6478                }
6479            }
6480        }
6481
6482        public boolean shouldRecycleViewType(int viewType) {
6483            return viewType >= 0;
6484        }
6485
6486        /**
6487         * Clears the scrap heap.
6488         */
6489        void clear() {
6490            if (mViewTypeCount == 1) {
6491                final ArrayList<View> scrap = mCurrentScrap;
6492                final int scrapCount = scrap.size();
6493                for (int i = 0; i < scrapCount; i++) {
6494                    removeDetachedView(scrap.remove(scrapCount - 1 - i), false);
6495                }
6496            } else {
6497                final int typeCount = mViewTypeCount;
6498                for (int i = 0; i < typeCount; i++) {
6499                    final ArrayList<View> scrap = mScrapViews[i];
6500                    final int scrapCount = scrap.size();
6501                    for (int j = 0; j < scrapCount; j++) {
6502                        removeDetachedView(scrap.remove(scrapCount - 1 - j), false);
6503                    }
6504                }
6505            }
6506            if (mTransientStateViews != null) {
6507                mTransientStateViews.clear();
6508            }
6509            if (mTransientStateViewsById != null) {
6510                mTransientStateViewsById.clear();
6511            }
6512        }
6513
6514        /**
6515         * Fill ActiveViews with all of the children of the AbsListView.
6516         *
6517         * @param childCount The minimum number of views mActiveViews should hold
6518         * @param firstActivePosition The position of the first view that will be stored in
6519         *        mActiveViews
6520         */
6521        void fillActiveViews(int childCount, int firstActivePosition) {
6522            if (mActiveViews.length < childCount) {
6523                mActiveViews = new View[childCount];
6524            }
6525            mFirstActivePosition = firstActivePosition;
6526
6527            //noinspection MismatchedReadAndWriteOfArray
6528            final View[] activeViews = mActiveViews;
6529            for (int i = 0; i < childCount; i++) {
6530                View child = getChildAt(i);
6531                AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
6532                // Don't put header or footer views into the scrap heap
6533                if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
6534                    // Note:  We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views.
6535                    //        However, we will NOT place them into scrap views.
6536                    activeViews[i] = child;
6537                }
6538            }
6539        }
6540
6541        /**
6542         * Get the view corresponding to the specified position. The view will be removed from
6543         * mActiveViews if it is found.
6544         *
6545         * @param position The position to look up in mActiveViews
6546         * @return The view if it is found, null otherwise
6547         */
6548        View getActiveView(int position) {
6549            int index = position - mFirstActivePosition;
6550            final View[] activeViews = mActiveViews;
6551            if (index >=0 && index < activeViews.length) {
6552                final View match = activeViews[index];
6553                activeViews[index] = null;
6554                return match;
6555            }
6556            return null;
6557        }
6558
6559        View getTransientStateView(int position) {
6560            if (mAdapter != null && mAdapterHasStableIds && mTransientStateViewsById != null) {
6561                long id = mAdapter.getItemId(position);
6562                View result = mTransientStateViewsById.get(id);
6563                mTransientStateViewsById.remove(id);
6564                return result;
6565            }
6566            if (mTransientStateViews != null) {
6567                final int index = mTransientStateViews.indexOfKey(position);
6568                if (index >= 0) {
6569                    View result = mTransientStateViews.valueAt(index);
6570                    mTransientStateViews.removeAt(index);
6571                    return result;
6572                }
6573            }
6574            return null;
6575        }
6576
6577        /**
6578         * Dump any currently saved views with transient state.
6579         */
6580        void clearTransientStateViews() {
6581            if (mTransientStateViews != null) {
6582                mTransientStateViews.clear();
6583            }
6584            if (mTransientStateViewsById != null) {
6585                mTransientStateViewsById.clear();
6586            }
6587        }
6588
6589        /**
6590         * @return A view from the ScrapViews collection. These are unordered.
6591         */
6592        View getScrapView(int position) {
6593            if (mViewTypeCount == 1) {
6594                return retrieveFromScrap(mCurrentScrap, position);
6595            } else {
6596                int whichScrap = mAdapter.getItemViewType(position);
6597                if (whichScrap >= 0 && whichScrap < mScrapViews.length) {
6598                    return retrieveFromScrap(mScrapViews[whichScrap], position);
6599                }
6600            }
6601            return null;
6602        }
6603
6604        /**
6605         * Puts a view into the list of scrap views.
6606         * <p>
6607         * If the list data hasn't changed or the adapter has stable IDs, views
6608         * with transient state will be preserved for later retrieval.
6609         *
6610         * @param scrap The view to add
6611         * @param position The view's position within its parent
6612         */
6613        void addScrapView(View scrap, int position) {
6614            final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
6615            if (lp == null) {
6616                return;
6617            }
6618
6619            lp.scrappedFromPosition = position;
6620
6621            // Don't scrap header or footer views, or views that should
6622            // otherwise not be recycled.
6623            final int viewType = lp.viewType;
6624            if (!shouldRecycleViewType(viewType)) {
6625                return;
6626            }
6627
6628            scrap.dispatchStartTemporaryDetach();
6629
6630            // Don't scrap views that have transient state.
6631            final boolean scrapHasTransientState = scrap.hasTransientState();
6632            if (scrapHasTransientState) {
6633                if (mAdapter != null && mAdapterHasStableIds) {
6634                    // If the adapter has stable IDs, we can reuse the view for
6635                    // the same data.
6636                    if (mTransientStateViewsById == null) {
6637                        mTransientStateViewsById = new LongSparseArray<View>();
6638                    }
6639                    mTransientStateViewsById.put(lp.itemId, scrap);
6640                } else if (!mDataChanged) {
6641                    // If the data hasn't changed, we can reuse the views at
6642                    // their old positions.
6643                    if (mTransientStateViews == null) {
6644                        mTransientStateViews = new SparseArray<View>();
6645                    }
6646                    mTransientStateViews.put(position, scrap);
6647                } else {
6648                    // Otherwise, we'll have to remove the view and start over.
6649                    if (mSkippedScrap == null) {
6650                        mSkippedScrap = new ArrayList<View>();
6651                    }
6652                    mSkippedScrap.add(scrap);
6653                }
6654            } else {
6655                if (mViewTypeCount == 1) {
6656                    mCurrentScrap.add(scrap);
6657                } else {
6658                    mScrapViews[viewType].add(scrap);
6659                }
6660
6661                scrap.setAccessibilityDelegate(null);
6662
6663                if (mRecyclerListener != null) {
6664                    mRecyclerListener.onMovedToScrapHeap(scrap);
6665                }
6666            }
6667        }
6668
6669        /**
6670         * Finish the removal of any views that skipped the scrap heap.
6671         */
6672        void removeSkippedScrap() {
6673            if (mSkippedScrap == null) {
6674                return;
6675            }
6676            final int count = mSkippedScrap.size();
6677            for (int i = 0; i < count; i++) {
6678                removeDetachedView(mSkippedScrap.get(i), false);
6679            }
6680            mSkippedScrap.clear();
6681        }
6682
6683        /**
6684         * Move all views remaining in mActiveViews to mScrapViews.
6685         */
6686        void scrapActiveViews() {
6687            final View[] activeViews = mActiveViews;
6688            final boolean hasListener = mRecyclerListener != null;
6689            final boolean multipleScraps = mViewTypeCount > 1;
6690
6691            ArrayList<View> scrapViews = mCurrentScrap;
6692            final int count = activeViews.length;
6693            for (int i = count - 1; i >= 0; i--) {
6694                final View victim = activeViews[i];
6695                if (victim != null) {
6696                    final AbsListView.LayoutParams lp
6697                            = (AbsListView.LayoutParams) victim.getLayoutParams();
6698                    int whichScrap = lp.viewType;
6699
6700                    activeViews[i] = null;
6701
6702                    final boolean scrapHasTransientState = victim.hasTransientState();
6703                    if (!shouldRecycleViewType(whichScrap) || scrapHasTransientState) {
6704                        // Do not move views that should be ignored
6705                        if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER &&
6706                                scrapHasTransientState) {
6707                            removeDetachedView(victim, false);
6708                        }
6709                        if (scrapHasTransientState) {
6710                            if (mAdapter != null && mAdapterHasStableIds) {
6711                                if (mTransientStateViewsById == null) {
6712                                    mTransientStateViewsById = new LongSparseArray<View>();
6713                                }
6714                                long id = mAdapter.getItemId(mFirstActivePosition + i);
6715                                mTransientStateViewsById.put(id, victim);
6716                            } else {
6717                                if (mTransientStateViews == null) {
6718                                    mTransientStateViews = new SparseArray<View>();
6719                                }
6720                                mTransientStateViews.put(mFirstActivePosition + i, victim);
6721                            }
6722                        }
6723                        continue;
6724                    }
6725
6726                    if (multipleScraps) {
6727                        scrapViews = mScrapViews[whichScrap];
6728                    }
6729                    victim.dispatchStartTemporaryDetach();
6730                    lp.scrappedFromPosition = mFirstActivePosition + i;
6731                    scrapViews.add(victim);
6732
6733                    victim.setAccessibilityDelegate(null);
6734                    if (hasListener) {
6735                        mRecyclerListener.onMovedToScrapHeap(victim);
6736                    }
6737                }
6738            }
6739
6740            pruneScrapViews();
6741        }
6742
6743        /**
6744         * Makes sure that the size of mScrapViews does not exceed the size of mActiveViews.
6745         * (This can happen if an adapter does not recycle its views).
6746         */
6747        private void pruneScrapViews() {
6748            final int maxViews = mActiveViews.length;
6749            final int viewTypeCount = mViewTypeCount;
6750            final ArrayList<View>[] scrapViews = mScrapViews;
6751            for (int i = 0; i < viewTypeCount; ++i) {
6752                final ArrayList<View> scrapPile = scrapViews[i];
6753                int size = scrapPile.size();
6754                final int extras = size - maxViews;
6755                size--;
6756                for (int j = 0; j < extras; j++) {
6757                    removeDetachedView(scrapPile.remove(size--), false);
6758                }
6759            }
6760
6761            if (mTransientStateViews != null) {
6762                for (int i = 0; i < mTransientStateViews.size(); i++) {
6763                    final View v = mTransientStateViews.valueAt(i);
6764                    if (!v.hasTransientState()) {
6765                        mTransientStateViews.removeAt(i);
6766                        i--;
6767                    }
6768                }
6769            }
6770            if (mTransientStateViewsById != null) {
6771                for (int i = 0; i < mTransientStateViewsById.size(); i++) {
6772                    final View v = mTransientStateViewsById.valueAt(i);
6773                    if (!v.hasTransientState()) {
6774                        mTransientStateViewsById.removeAt(i);
6775                        i--;
6776                    }
6777                }
6778            }
6779        }
6780
6781        /**
6782         * Puts all views in the scrap heap into the supplied list.
6783         */
6784        void reclaimScrapViews(List<View> views) {
6785            if (mViewTypeCount == 1) {
6786                views.addAll(mCurrentScrap);
6787            } else {
6788                final int viewTypeCount = mViewTypeCount;
6789                final ArrayList<View>[] scrapViews = mScrapViews;
6790                for (int i = 0; i < viewTypeCount; ++i) {
6791                    final ArrayList<View> scrapPile = scrapViews[i];
6792                    views.addAll(scrapPile);
6793                }
6794            }
6795        }
6796
6797        /**
6798         * Updates the cache color hint of all known views.
6799         *
6800         * @param color The new cache color hint.
6801         */
6802        void setCacheColorHint(int color) {
6803            if (mViewTypeCount == 1) {
6804                final ArrayList<View> scrap = mCurrentScrap;
6805                final int scrapCount = scrap.size();
6806                for (int i = 0; i < scrapCount; i++) {
6807                    scrap.get(i).setDrawingCacheBackgroundColor(color);
6808                }
6809            } else {
6810                final int typeCount = mViewTypeCount;
6811                for (int i = 0; i < typeCount; i++) {
6812                    final ArrayList<View> scrap = mScrapViews[i];
6813                    final int scrapCount = scrap.size();
6814                    for (int j = 0; j < scrapCount; j++) {
6815                        scrap.get(j).setDrawingCacheBackgroundColor(color);
6816                    }
6817                }
6818            }
6819            // Just in case this is called during a layout pass
6820            final View[] activeViews = mActiveViews;
6821            final int count = activeViews.length;
6822            for (int i = 0; i < count; ++i) {
6823                final View victim = activeViews[i];
6824                if (victim != null) {
6825                    victim.setDrawingCacheBackgroundColor(color);
6826                }
6827            }
6828        }
6829    }
6830
6831    static View retrieveFromScrap(ArrayList<View> scrapViews, int position) {
6832        int size = scrapViews.size();
6833        if (size > 0) {
6834            // See if we still have a view for this position.
6835            for (int i=0; i<size; i++) {
6836                View view = scrapViews.get(i);
6837                if (((AbsListView.LayoutParams)view.getLayoutParams())
6838                        .scrappedFromPosition == position) {
6839                    scrapViews.remove(i);
6840                    return view;
6841                }
6842            }
6843            return scrapViews.remove(size - 1);
6844        } else {
6845            return null;
6846        }
6847    }
6848}
6849