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