AbsListView.java revision 9a5cc2810bbbcb0eab4579aa4131039820d92101
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        if (mCheckStates != null) {
1578            ss.checkState = mCheckStates.clone();
1579        }
1580        if (mCheckedIdStates != null) {
1581            final LongSparseArray<Boolean> idState = new LongSparseArray<Boolean>();
1582            final int count = mCheckedIdStates.size();
1583            for (int i = 0; i < count; i++) {
1584                idState.put(mCheckedIdStates.keyAt(i), mCheckedIdStates.valueAt(i));
1585            }
1586            ss.checkIdState = idState;
1587        }
1588        ss.checkedItemCount = mCheckedItemCount;
1589
1590        return ss;
1591    }
1592
1593    @Override
1594    public void onRestoreInstanceState(Parcelable state) {
1595        SavedState ss = (SavedState) state;
1596
1597        super.onRestoreInstanceState(ss.getSuperState());
1598        mDataChanged = true;
1599
1600        mSyncHeight = ss.height;
1601
1602        if (ss.selectedId >= 0) {
1603            mNeedSync = true;
1604            mSyncRowId = ss.selectedId;
1605            mSyncPosition = ss.position;
1606            mSpecificTop = ss.viewTop;
1607            mSyncMode = SYNC_SELECTED_POSITION;
1608        } else if (ss.firstId >= 0) {
1609            setSelectedPositionInt(INVALID_POSITION);
1610            // Do this before setting mNeedSync since setNextSelectedPosition looks at mNeedSync
1611            setNextSelectedPositionInt(INVALID_POSITION);
1612            mSelectorPosition = INVALID_POSITION;
1613            mNeedSync = true;
1614            mSyncRowId = ss.firstId;
1615            mSyncPosition = ss.position;
1616            mSpecificTop = ss.viewTop;
1617            mSyncMode = SYNC_FIRST_POSITION;
1618        }
1619
1620        setFilterText(ss.filter);
1621
1622        if (ss.checkState != null) {
1623            mCheckStates = ss.checkState;
1624        }
1625
1626        if (ss.checkIdState != null) {
1627            mCheckedIdStates = ss.checkIdState;
1628        }
1629
1630        mCheckedItemCount = ss.checkedItemCount;
1631
1632        if (ss.inActionMode && mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL &&
1633                mMultiChoiceModeCallback != null) {
1634            mChoiceActionMode = startActionMode(mMultiChoiceModeCallback);
1635        }
1636
1637        requestLayout();
1638    }
1639
1640    private boolean acceptFilter() {
1641        return mTextFilterEnabled && getAdapter() instanceof Filterable &&
1642                ((Filterable) getAdapter()).getFilter() != null;
1643    }
1644
1645    /**
1646     * Sets the initial value for the text filter.
1647     * @param filterText The text to use for the filter.
1648     *
1649     * @see #setTextFilterEnabled
1650     */
1651    public void setFilterText(String filterText) {
1652        // TODO: Should we check for acceptFilter()?
1653        if (mTextFilterEnabled && !TextUtils.isEmpty(filterText)) {
1654            createTextFilter(false);
1655            // This is going to call our listener onTextChanged, but we might not
1656            // be ready to bring up a window yet
1657            mTextFilter.setText(filterText);
1658            mTextFilter.setSelection(filterText.length());
1659            if (mAdapter instanceof Filterable) {
1660                // if mPopup is non-null, then onTextChanged will do the filtering
1661                if (mPopup == null) {
1662                    Filter f = ((Filterable) mAdapter).getFilter();
1663                    f.filter(filterText);
1664                }
1665                // Set filtered to true so we will display the filter window when our main
1666                // window is ready
1667                mFiltered = true;
1668                mDataSetObserver.clearSavedState();
1669            }
1670        }
1671    }
1672
1673    /**
1674     * Returns the list's text filter, if available.
1675     * @return the list's text filter or null if filtering isn't enabled
1676     */
1677    public CharSequence getTextFilter() {
1678        if (mTextFilterEnabled && mTextFilter != null) {
1679            return mTextFilter.getText();
1680        }
1681        return null;
1682    }
1683
1684    @Override
1685    protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
1686        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
1687        if (gainFocus && mSelectedPosition < 0 && !isInTouchMode()) {
1688            if (!mIsAttached && mAdapter != null) {
1689                // Data may have changed while we were detached and it's valid
1690                // to change focus while detached. Refresh so we don't die.
1691                mDataChanged = true;
1692                mOldItemCount = mItemCount;
1693                mItemCount = mAdapter.getCount();
1694            }
1695            resurrectSelection();
1696        }
1697    }
1698
1699    @Override
1700    public void requestLayout() {
1701        if (!mBlockLayoutRequests && !mInLayout) {
1702            super.requestLayout();
1703        }
1704    }
1705
1706    /**
1707     * The list is empty. Clear everything out.
1708     */
1709    void resetList() {
1710        removeAllViewsInLayout();
1711        mFirstPosition = 0;
1712        mDataChanged = false;
1713        mNeedSync = false;
1714        mOldSelectedPosition = INVALID_POSITION;
1715        mOldSelectedRowId = INVALID_ROW_ID;
1716        setSelectedPositionInt(INVALID_POSITION);
1717        setNextSelectedPositionInt(INVALID_POSITION);
1718        mSelectedTop = 0;
1719        mSelectorPosition = INVALID_POSITION;
1720        mSelectorRect.setEmpty();
1721        invalidate();
1722    }
1723
1724    @Override
1725    protected int computeVerticalScrollExtent() {
1726        final int count = getChildCount();
1727        if (count > 0) {
1728            if (mSmoothScrollbarEnabled) {
1729                int extent = count * 100;
1730
1731                View view = getChildAt(0);
1732                final int top = view.getTop();
1733                int height = view.getHeight();
1734                if (height > 0) {
1735                    extent += (top * 100) / height;
1736                }
1737
1738                view = getChildAt(count - 1);
1739                final int bottom = view.getBottom();
1740                height = view.getHeight();
1741                if (height > 0) {
1742                    extent -= ((bottom - getHeight()) * 100) / height;
1743                }
1744
1745                return extent;
1746            } else {
1747                return 1;
1748            }
1749        }
1750        return 0;
1751    }
1752
1753    @Override
1754    protected int computeVerticalScrollOffset() {
1755        final int firstPosition = mFirstPosition;
1756        final int childCount = getChildCount();
1757        if (firstPosition >= 0 && childCount > 0) {
1758            if (mSmoothScrollbarEnabled) {
1759                final View view = getChildAt(0);
1760                final int top = view.getTop();
1761                int height = view.getHeight();
1762                if (height > 0) {
1763                    return Math.max(firstPosition * 100 - (top * 100) / height +
1764                            (int)((float)mScrollY / getHeight() * mItemCount * 100), 0);
1765                }
1766            } else {
1767                int index;
1768                final int count = mItemCount;
1769                if (firstPosition == 0) {
1770                    index = 0;
1771                } else if (firstPosition + childCount == count) {
1772                    index = count;
1773                } else {
1774                    index = firstPosition + childCount / 2;
1775                }
1776                return (int) (firstPosition + childCount * (index / (float) count));
1777            }
1778        }
1779        return 0;
1780    }
1781
1782    @Override
1783    protected int computeVerticalScrollRange() {
1784        int result;
1785        if (mSmoothScrollbarEnabled) {
1786            result = Math.max(mItemCount * 100, 0);
1787            if (mScrollY != 0) {
1788                // Compensate for overscroll
1789                result += Math.abs((int) ((float) mScrollY / getHeight() * mItemCount * 100));
1790            }
1791        } else {
1792            result = mItemCount;
1793        }
1794        return result;
1795    }
1796
1797    @Override
1798    protected float getTopFadingEdgeStrength() {
1799        final int count = getChildCount();
1800        final float fadeEdge = super.getTopFadingEdgeStrength();
1801        if (count == 0) {
1802            return fadeEdge;
1803        } else {
1804            if (mFirstPosition > 0) {
1805                return 1.0f;
1806            }
1807
1808            final int top = getChildAt(0).getTop();
1809            final float fadeLength = (float) getVerticalFadingEdgeLength();
1810            return top < mPaddingTop ? (float) -(top - mPaddingTop) / fadeLength : fadeEdge;
1811        }
1812    }
1813
1814    @Override
1815    protected float getBottomFadingEdgeStrength() {
1816        final int count = getChildCount();
1817        final float fadeEdge = super.getBottomFadingEdgeStrength();
1818        if (count == 0) {
1819            return fadeEdge;
1820        } else {
1821            if (mFirstPosition + count - 1 < mItemCount - 1) {
1822                return 1.0f;
1823            }
1824
1825            final int bottom = getChildAt(count - 1).getBottom();
1826            final int height = getHeight();
1827            final float fadeLength = (float) getVerticalFadingEdgeLength();
1828            return bottom > height - mPaddingBottom ?
1829                    (float) (bottom - height + mPaddingBottom) / fadeLength : fadeEdge;
1830        }
1831    }
1832
1833    @Override
1834    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1835        if (mSelector == null) {
1836            useDefaultSelector();
1837        }
1838        final Rect listPadding = mListPadding;
1839        listPadding.left = mSelectionLeftPadding + mPaddingLeft;
1840        listPadding.top = mSelectionTopPadding + mPaddingTop;
1841        listPadding.right = mSelectionRightPadding + mPaddingRight;
1842        listPadding.bottom = mSelectionBottomPadding + mPaddingBottom;
1843
1844        // Check if our previous measured size was at a point where we should scroll later.
1845        if (mTranscriptMode == TRANSCRIPT_MODE_NORMAL) {
1846            final int childCount = getChildCount();
1847            final int listBottom = getHeight() - getPaddingBottom();
1848            final View lastChild = getChildAt(childCount - 1);
1849            final int lastBottom = lastChild != null ? lastChild.getBottom() : listBottom;
1850            mForceTranscriptScroll = mFirstPosition + childCount >= mLastHandledItemCount &&
1851                    lastBottom <= listBottom;
1852        }
1853    }
1854
1855    /**
1856     * Subclasses should NOT override this method but
1857     *  {@link #layoutChildren()} instead.
1858     */
1859    @Override
1860    protected void onLayout(boolean changed, int l, int t, int r, int b) {
1861        super.onLayout(changed, l, t, r, b);
1862        mInLayout = true;
1863        if (changed) {
1864            int childCount = getChildCount();
1865            for (int i = 0; i < childCount; i++) {
1866                getChildAt(i).forceLayout();
1867            }
1868            mRecycler.markChildrenDirty();
1869        }
1870
1871        if (mFastScroller != null && mItemCount != mOldItemCount) {
1872            mFastScroller.onItemCountChanged(mOldItemCount, mItemCount);
1873        }
1874
1875        layoutChildren();
1876        mInLayout = false;
1877
1878        mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;
1879    }
1880
1881    /**
1882     * @hide
1883     */
1884    @Override
1885    protected boolean setFrame(int left, int top, int right, int bottom) {
1886        final boolean changed = super.setFrame(left, top, right, bottom);
1887
1888        if (changed) {
1889            // Reposition the popup when the frame has changed. This includes
1890            // translating the widget, not just changing its dimension. The
1891            // filter popup needs to follow the widget.
1892            final boolean visible = getWindowVisibility() == View.VISIBLE;
1893            if (mFiltered && visible && mPopup != null && mPopup.isShowing()) {
1894                positionPopup();
1895            }
1896        }
1897
1898        return changed;
1899    }
1900
1901    /**
1902     * Subclasses must override this method to layout their children.
1903     */
1904    protected void layoutChildren() {
1905    }
1906
1907    void updateScrollIndicators() {
1908        if (mScrollUp != null) {
1909            boolean canScrollUp;
1910            // 0th element is not visible
1911            canScrollUp = mFirstPosition > 0;
1912
1913            // ... Or top of 0th element is not visible
1914            if (!canScrollUp) {
1915                if (getChildCount() > 0) {
1916                    View child = getChildAt(0);
1917                    canScrollUp = child.getTop() < mListPadding.top;
1918                }
1919            }
1920
1921            mScrollUp.setVisibility(canScrollUp ? View.VISIBLE : View.INVISIBLE);
1922        }
1923
1924        if (mScrollDown != null) {
1925            boolean canScrollDown;
1926            int count = getChildCount();
1927
1928            // Last item is not visible
1929            canScrollDown = (mFirstPosition + count) < mItemCount;
1930
1931            // ... Or bottom of the last element is not visible
1932            if (!canScrollDown && count > 0) {
1933                View child = getChildAt(count - 1);
1934                canScrollDown = child.getBottom() > mBottom - mListPadding.bottom;
1935            }
1936
1937            mScrollDown.setVisibility(canScrollDown ? View.VISIBLE : View.INVISIBLE);
1938        }
1939    }
1940
1941    @Override
1942    @ViewDebug.ExportedProperty
1943    public View getSelectedView() {
1944        if (mItemCount > 0 && mSelectedPosition >= 0) {
1945            return getChildAt(mSelectedPosition - mFirstPosition);
1946        } else {
1947            return null;
1948        }
1949    }
1950
1951    /**
1952     * List padding is the maximum of the normal view's padding and the padding of the selector.
1953     *
1954     * @see android.view.View#getPaddingTop()
1955     * @see #getSelector()
1956     *
1957     * @return The top list padding.
1958     */
1959    public int getListPaddingTop() {
1960        return mListPadding.top;
1961    }
1962
1963    /**
1964     * List padding is the maximum of the normal view's padding and the padding of the selector.
1965     *
1966     * @see android.view.View#getPaddingBottom()
1967     * @see #getSelector()
1968     *
1969     * @return The bottom list padding.
1970     */
1971    public int getListPaddingBottom() {
1972        return mListPadding.bottom;
1973    }
1974
1975    /**
1976     * List padding is the maximum of the normal view's padding and the padding of the selector.
1977     *
1978     * @see android.view.View#getPaddingLeft()
1979     * @see #getSelector()
1980     *
1981     * @return The left list padding.
1982     */
1983    public int getListPaddingLeft() {
1984        return mListPadding.left;
1985    }
1986
1987    /**
1988     * List padding is the maximum of the normal view's padding and the padding of the selector.
1989     *
1990     * @see android.view.View#getPaddingRight()
1991     * @see #getSelector()
1992     *
1993     * @return The right list padding.
1994     */
1995    public int getListPaddingRight() {
1996        return mListPadding.right;
1997    }
1998
1999    /**
2000     * Get a view and have it show the data associated with the specified
2001     * position. This is called when we have already discovered that the view is
2002     * not available for reuse in the recycle bin. The only choices left are
2003     * converting an old view or making a new one.
2004     *
2005     * @param position The position to display
2006     * @param isScrap Array of at least 1 boolean, the first entry will become true if
2007     *                the returned view was taken from the scrap heap, false if otherwise.
2008     *
2009     * @return A view displaying the data associated with the specified position
2010     */
2011    View obtainView(int position, boolean[] isScrap) {
2012        isScrap[0] = false;
2013        View scrapView;
2014
2015        scrapView = mRecycler.getScrapView(position);
2016
2017        View child;
2018        if (scrapView != null) {
2019            if (ViewDebug.TRACE_RECYCLER) {
2020                ViewDebug.trace(scrapView, ViewDebug.RecyclerTraceType.RECYCLE_FROM_SCRAP_HEAP,
2021                        position, -1);
2022            }
2023
2024            child = mAdapter.getView(position, scrapView, this);
2025
2026            if (ViewDebug.TRACE_RECYCLER) {
2027                ViewDebug.trace(child, ViewDebug.RecyclerTraceType.BIND_VIEW,
2028                        position, getChildCount());
2029            }
2030
2031            if (child != scrapView) {
2032                mRecycler.addScrapView(scrapView, position);
2033                if (mCacheColorHint != 0) {
2034                    child.setDrawingCacheBackgroundColor(mCacheColorHint);
2035                }
2036                if (ViewDebug.TRACE_RECYCLER) {
2037                    ViewDebug.trace(scrapView, ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP,
2038                            position, -1);
2039                }
2040            } else {
2041                isScrap[0] = true;
2042                child.dispatchFinishTemporaryDetach();
2043            }
2044        } else {
2045            child = mAdapter.getView(position, null, this);
2046            if (mCacheColorHint != 0) {
2047                child.setDrawingCacheBackgroundColor(mCacheColorHint);
2048            }
2049            if (ViewDebug.TRACE_RECYCLER) {
2050                ViewDebug.trace(child, ViewDebug.RecyclerTraceType.NEW_VIEW,
2051                        position, getChildCount());
2052            }
2053        }
2054
2055        return child;
2056    }
2057
2058    void positionSelector(int position, View sel) {
2059        if (position != INVALID_POSITION) {
2060            mSelectorPosition = position;
2061        }
2062
2063        final Rect selectorRect = mSelectorRect;
2064        selectorRect.set(sel.getLeft(), sel.getTop(), sel.getRight(), sel.getBottom());
2065        if (sel instanceof SelectionBoundsAdjuster) {
2066            ((SelectionBoundsAdjuster)sel).adjustListItemSelectionBounds(selectorRect);
2067        }
2068        positionSelector(selectorRect.left, selectorRect.top, selectorRect.right,
2069                selectorRect.bottom);
2070
2071        final boolean isChildViewEnabled = mIsChildViewEnabled;
2072        if (sel.isEnabled() != isChildViewEnabled) {
2073            mIsChildViewEnabled = !isChildViewEnabled;
2074            if (getSelectedItemPosition() != INVALID_POSITION) {
2075                refreshDrawableState();
2076            }
2077        }
2078    }
2079
2080    private void positionSelector(int l, int t, int r, int b) {
2081        mSelectorRect.set(l - mSelectionLeftPadding, t - mSelectionTopPadding, r
2082                + mSelectionRightPadding, b + mSelectionBottomPadding);
2083    }
2084
2085    @Override
2086    protected void dispatchDraw(Canvas canvas) {
2087        int saveCount = 0;
2088        final boolean clipToPadding = (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
2089        if (clipToPadding) {
2090            saveCount = canvas.save();
2091            final int scrollX = mScrollX;
2092            final int scrollY = mScrollY;
2093            canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,
2094                    scrollX + mRight - mLeft - mPaddingRight,
2095                    scrollY + mBottom - mTop - mPaddingBottom);
2096            mGroupFlags &= ~CLIP_TO_PADDING_MASK;
2097        }
2098
2099        final boolean drawSelectorOnTop = mDrawSelectorOnTop;
2100        if (!drawSelectorOnTop) {
2101            drawSelector(canvas);
2102        }
2103
2104        super.dispatchDraw(canvas);
2105
2106        if (drawSelectorOnTop) {
2107            drawSelector(canvas);
2108        }
2109
2110        if (clipToPadding) {
2111            canvas.restoreToCount(saveCount);
2112            mGroupFlags |= CLIP_TO_PADDING_MASK;
2113        }
2114    }
2115
2116    @Override
2117    protected boolean isPaddingOffsetRequired() {
2118        return (mGroupFlags & CLIP_TO_PADDING_MASK) != CLIP_TO_PADDING_MASK;
2119    }
2120
2121    @Override
2122    protected int getLeftPaddingOffset() {
2123        return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : -mPaddingLeft;
2124    }
2125
2126    @Override
2127    protected int getTopPaddingOffset() {
2128        return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : -mPaddingTop;
2129    }
2130
2131    @Override
2132    protected int getRightPaddingOffset() {
2133        return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : mPaddingRight;
2134    }
2135
2136    @Override
2137    protected int getBottomPaddingOffset() {
2138        return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : mPaddingBottom;
2139    }
2140
2141    @Override
2142    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
2143        if (getChildCount() > 0) {
2144            mDataChanged = true;
2145            rememberSyncState();
2146        }
2147
2148        if (mFastScroller != null) {
2149            mFastScroller.onSizeChanged(w, h, oldw, oldh);
2150        }
2151    }
2152
2153    /**
2154     * @return True if the current touch mode requires that we draw the selector in the pressed
2155     *         state.
2156     */
2157    boolean touchModeDrawsInPressedState() {
2158        // FIXME use isPressed for this
2159        switch (mTouchMode) {
2160        case TOUCH_MODE_TAP:
2161        case TOUCH_MODE_DONE_WAITING:
2162            return true;
2163        default:
2164            return false;
2165        }
2166    }
2167
2168    /**
2169     * Indicates whether this view is in a state where the selector should be drawn. This will
2170     * happen if we have focus but are not in touch mode, or we are in the middle of displaying
2171     * the pressed state for an item.
2172     *
2173     * @return True if the selector should be shown
2174     */
2175    boolean shouldShowSelector() {
2176        return (hasFocus() && !isInTouchMode()) || touchModeDrawsInPressedState();
2177    }
2178
2179    private void drawSelector(Canvas canvas) {
2180        if (!mSelectorRect.isEmpty()) {
2181            final Drawable selector = mSelector;
2182            selector.setBounds(mSelectorRect);
2183            selector.draw(canvas);
2184        }
2185    }
2186
2187    /**
2188     * Controls whether the selection highlight drawable should be drawn on top of the item or
2189     * behind it.
2190     *
2191     * @param onTop If true, the selector will be drawn on the item it is highlighting. The default
2192     *        is false.
2193     *
2194     * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop
2195     */
2196    public void setDrawSelectorOnTop(boolean onTop) {
2197        mDrawSelectorOnTop = onTop;
2198    }
2199
2200    /**
2201     * Set a Drawable that should be used to highlight the currently selected item.
2202     *
2203     * @param resID A Drawable resource to use as the selection highlight.
2204     *
2205     * @attr ref android.R.styleable#AbsListView_listSelector
2206     */
2207    public void setSelector(int resID) {
2208        setSelector(getResources().getDrawable(resID));
2209    }
2210
2211    public void setSelector(Drawable sel) {
2212        if (mSelector != null) {
2213            mSelector.setCallback(null);
2214            unscheduleDrawable(mSelector);
2215        }
2216        mSelector = sel;
2217        Rect padding = new Rect();
2218        sel.getPadding(padding);
2219        mSelectionLeftPadding = padding.left;
2220        mSelectionTopPadding = padding.top;
2221        mSelectionRightPadding = padding.right;
2222        mSelectionBottomPadding = padding.bottom;
2223        sel.setCallback(this);
2224        updateSelectorState();
2225    }
2226
2227    /**
2228     * Returns the selector {@link android.graphics.drawable.Drawable} that is used to draw the
2229     * selection in the list.
2230     *
2231     * @return the drawable used to display the selector
2232     */
2233    public Drawable getSelector() {
2234        return mSelector;
2235    }
2236
2237    /**
2238     * Sets the selector state to "pressed" and posts a CheckForKeyLongPress to see if
2239     * this is a long press.
2240     */
2241    void keyPressed() {
2242        if (!isEnabled() || !isClickable()) {
2243            return;
2244        }
2245
2246        Drawable selector = mSelector;
2247        Rect selectorRect = mSelectorRect;
2248        if (selector != null && (isFocused() || touchModeDrawsInPressedState())
2249                && !selectorRect.isEmpty()) {
2250
2251            final View v = getChildAt(mSelectedPosition - mFirstPosition);
2252
2253            if (v != null) {
2254                if (v.hasFocusable()) return;
2255                v.setPressed(true);
2256            }
2257            setPressed(true);
2258
2259            final boolean longClickable = isLongClickable();
2260            Drawable d = selector.getCurrent();
2261            if (d != null && d instanceof TransitionDrawable) {
2262                if (longClickable) {
2263                    ((TransitionDrawable) d).startTransition(
2264                            ViewConfiguration.getLongPressTimeout());
2265                } else {
2266                    ((TransitionDrawable) d).resetTransition();
2267                }
2268            }
2269            if (longClickable && !mDataChanged) {
2270                if (mPendingCheckForKeyLongPress == null) {
2271                    mPendingCheckForKeyLongPress = new CheckForKeyLongPress();
2272                }
2273                mPendingCheckForKeyLongPress.rememberWindowAttachCount();
2274                postDelayed(mPendingCheckForKeyLongPress, ViewConfiguration.getLongPressTimeout());
2275            }
2276        }
2277    }
2278
2279    public void setScrollIndicators(View up, View down) {
2280        mScrollUp = up;
2281        mScrollDown = down;
2282    }
2283
2284    void updateSelectorState() {
2285        if (mSelector != null) {
2286            if (shouldShowSelector()) {
2287                mSelector.setState(getDrawableState());
2288            } else {
2289                mSelector.setState(StateSet.NOTHING);
2290            }
2291        }
2292    }
2293
2294    @Override
2295    protected void drawableStateChanged() {
2296        super.drawableStateChanged();
2297        updateSelectorState();
2298    }
2299
2300    @Override
2301    protected int[] onCreateDrawableState(int extraSpace) {
2302        // If the child view is enabled then do the default behavior.
2303        if (mIsChildViewEnabled) {
2304            // Common case
2305            return super.onCreateDrawableState(extraSpace);
2306        }
2307
2308        // The selector uses this View's drawable state. The selected child view
2309        // is disabled, so we need to remove the enabled state from the drawable
2310        // states.
2311        final int enabledState = ENABLED_STATE_SET[0];
2312
2313        // If we don't have any extra space, it will return one of the static state arrays,
2314        // and clearing the enabled state on those arrays is a bad thing!  If we specify
2315        // we need extra space, it will create+copy into a new array that safely mutable.
2316        int[] state = super.onCreateDrawableState(extraSpace + 1);
2317        int enabledPos = -1;
2318        for (int i = state.length - 1; i >= 0; i--) {
2319            if (state[i] == enabledState) {
2320                enabledPos = i;
2321                break;
2322            }
2323        }
2324
2325        // Remove the enabled state
2326        if (enabledPos >= 0) {
2327            System.arraycopy(state, enabledPos + 1, state, enabledPos,
2328                    state.length - enabledPos - 1);
2329        }
2330
2331        return state;
2332    }
2333
2334    @Override
2335    public boolean verifyDrawable(Drawable dr) {
2336        return mSelector == dr || super.verifyDrawable(dr);
2337    }
2338
2339    @Override
2340    public void jumpDrawablesToCurrentState() {
2341        super.jumpDrawablesToCurrentState();
2342        if (mSelector != null) mSelector.jumpToCurrentState();
2343    }
2344
2345    @Override
2346    protected void onAttachedToWindow() {
2347        super.onAttachedToWindow();
2348
2349        final ViewTreeObserver treeObserver = getViewTreeObserver();
2350        treeObserver.addOnTouchModeChangeListener(this);
2351        if (mTextFilterEnabled && mPopup != null && !mGlobalLayoutListenerAddedFilter) {
2352            treeObserver.addOnGlobalLayoutListener(this);
2353        }
2354
2355        if (mAdapter != null && mDataSetObserver == null) {
2356            mDataSetObserver = new AdapterDataSetObserver();
2357            mAdapter.registerDataSetObserver(mDataSetObserver);
2358
2359            // Data may have changed while we were detached. Refresh.
2360            mDataChanged = true;
2361            mOldItemCount = mItemCount;
2362            mItemCount = mAdapter.getCount();
2363        }
2364        mIsAttached = true;
2365    }
2366
2367    @Override
2368    protected void onDetachedFromWindow() {
2369        super.onDetachedFromWindow();
2370
2371        // Dismiss the popup in case onSaveInstanceState() was not invoked
2372        dismissPopup();
2373
2374        // Detach any view left in the scrap heap
2375        mRecycler.clear();
2376
2377        final ViewTreeObserver treeObserver = getViewTreeObserver();
2378        treeObserver.removeOnTouchModeChangeListener(this);
2379        if (mTextFilterEnabled && mPopup != null) {
2380            treeObserver.removeGlobalOnLayoutListener(this);
2381            mGlobalLayoutListenerAddedFilter = false;
2382        }
2383
2384        if (mAdapter != null) {
2385            mAdapter.unregisterDataSetObserver(mDataSetObserver);
2386            mDataSetObserver = null;
2387        }
2388
2389        if (mScrollStrictSpan != null) {
2390            mScrollStrictSpan.finish();
2391            mScrollStrictSpan = null;
2392        }
2393
2394        if (mFlingStrictSpan != null) {
2395            mFlingStrictSpan.finish();
2396            mFlingStrictSpan = null;
2397        }
2398
2399        if (mFlingRunnable != null) {
2400            removeCallbacks(mFlingRunnable);
2401        }
2402
2403        if (mPositionScroller != null) {
2404            mPositionScroller.stop();
2405        }
2406
2407        if (mClearScrollingCache != null) {
2408            removeCallbacks(mClearScrollingCache);
2409        }
2410
2411        if (mPerformClick != null) {
2412            removeCallbacks(mPerformClick);
2413        }
2414
2415        if (mTouchModeReset != null) {
2416            removeCallbacks(mTouchModeReset);
2417            mTouchModeReset = null;
2418        }
2419        mIsAttached = false;
2420    }
2421
2422    @Override
2423    public void onWindowFocusChanged(boolean hasWindowFocus) {
2424        super.onWindowFocusChanged(hasWindowFocus);
2425
2426        final int touchMode = isInTouchMode() ? TOUCH_MODE_ON : TOUCH_MODE_OFF;
2427
2428        if (!hasWindowFocus) {
2429            setChildrenDrawingCacheEnabled(false);
2430            if (mFlingRunnable != null) {
2431                removeCallbacks(mFlingRunnable);
2432                // let the fling runnable report it's new state which
2433                // should be idle
2434                mFlingRunnable.endFling();
2435                if (mPositionScroller != null) {
2436                    mPositionScroller.stop();
2437                }
2438                if (mScrollY != 0) {
2439                    mScrollY = 0;
2440                    invalidateParentCaches();
2441                    finishGlows();
2442                    invalidate();
2443                }
2444            }
2445            // Always hide the type filter
2446            dismissPopup();
2447
2448            if (touchMode == TOUCH_MODE_OFF) {
2449                // Remember the last selected element
2450                mResurrectToPosition = mSelectedPosition;
2451            }
2452        } else {
2453            if (mFiltered && !mPopupHidden) {
2454                // Show the type filter only if a filter is in effect
2455                showPopup();
2456            }
2457
2458            // If we changed touch mode since the last time we had focus
2459            if (touchMode != mLastTouchMode && mLastTouchMode != TOUCH_MODE_UNKNOWN) {
2460                // If we come back in trackball mode, we bring the selection back
2461                if (touchMode == TOUCH_MODE_OFF) {
2462                    // This will trigger a layout
2463                    resurrectSelection();
2464
2465                // If we come back in touch mode, then we want to hide the selector
2466                } else {
2467                    hideSelector();
2468                    mLayoutMode = LAYOUT_NORMAL;
2469                    layoutChildren();
2470                }
2471            }
2472        }
2473
2474        mLastTouchMode = touchMode;
2475    }
2476
2477    /**
2478     * Creates the ContextMenuInfo returned from {@link #getContextMenuInfo()}. This
2479     * methods knows the view, position and ID of the item that received the
2480     * long press.
2481     *
2482     * @param view The view that received the long press.
2483     * @param position The position of the item that received the long press.
2484     * @param id The ID of the item that received the long press.
2485     * @return The extra information that should be returned by
2486     *         {@link #getContextMenuInfo()}.
2487     */
2488    ContextMenuInfo createContextMenuInfo(View view, int position, long id) {
2489        return new AdapterContextMenuInfo(view, position, id);
2490    }
2491
2492    /**
2493     * A base class for Runnables that will check that their view is still attached to
2494     * the original window as when the Runnable was created.
2495     *
2496     */
2497    private class WindowRunnnable {
2498        private int mOriginalAttachCount;
2499
2500        public void rememberWindowAttachCount() {
2501            mOriginalAttachCount = getWindowAttachCount();
2502        }
2503
2504        public boolean sameWindow() {
2505            return hasWindowFocus() && getWindowAttachCount() == mOriginalAttachCount;
2506        }
2507    }
2508
2509    private class PerformClick extends WindowRunnnable implements Runnable {
2510        int mClickMotionPosition;
2511
2512        public void run() {
2513            // The data has changed since we posted this action in the event queue,
2514            // bail out before bad things happen
2515            if (mDataChanged) return;
2516
2517            final ListAdapter adapter = mAdapter;
2518            final int motionPosition = mClickMotionPosition;
2519            if (adapter != null && mItemCount > 0 &&
2520                    motionPosition != INVALID_POSITION &&
2521                    motionPosition < adapter.getCount() && sameWindow()) {
2522                final View view = getChildAt(motionPosition - mFirstPosition);
2523                // If there is no view, something bad happened (the view scrolled off the
2524                // screen, etc.) and we should cancel the click
2525                if (view != null) {
2526                    performItemClick(view, motionPosition, adapter.getItemId(motionPosition));
2527                }
2528            }
2529        }
2530    }
2531
2532    private class CheckForLongPress extends WindowRunnnable implements Runnable {
2533        public void run() {
2534            final int motionPosition = mMotionPosition;
2535            final View child = getChildAt(motionPosition - mFirstPosition);
2536            if (child != null) {
2537                final int longPressPosition = mMotionPosition;
2538                final long longPressId = mAdapter.getItemId(mMotionPosition);
2539
2540                boolean handled = false;
2541                if (sameWindow() && !mDataChanged) {
2542                    handled = performLongPress(child, longPressPosition, longPressId);
2543                }
2544                if (handled) {
2545                    mTouchMode = TOUCH_MODE_REST;
2546                    setPressed(false);
2547                    child.setPressed(false);
2548                } else {
2549                    mTouchMode = TOUCH_MODE_DONE_WAITING;
2550                }
2551            }
2552        }
2553    }
2554
2555    private class CheckForKeyLongPress extends WindowRunnnable implements Runnable {
2556        public void run() {
2557            if (isPressed() && mSelectedPosition >= 0) {
2558                int index = mSelectedPosition - mFirstPosition;
2559                View v = getChildAt(index);
2560
2561                if (!mDataChanged) {
2562                    boolean handled = false;
2563                    if (sameWindow()) {
2564                        handled = performLongPress(v, mSelectedPosition, mSelectedRowId);
2565                    }
2566                    if (handled) {
2567                        setPressed(false);
2568                        v.setPressed(false);
2569                    }
2570                } else {
2571                    setPressed(false);
2572                    if (v != null) v.setPressed(false);
2573                }
2574            }
2575        }
2576    }
2577
2578    boolean performLongPress(final View child,
2579            final int longPressPosition, final long longPressId) {
2580        // CHOICE_MODE_MULTIPLE_MODAL takes over long press.
2581        if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
2582            if (mChoiceActionMode == null) {
2583                mChoiceActionMode = startActionMode(mMultiChoiceModeCallback);
2584                setItemChecked(longPressPosition, true);
2585            }
2586            // TODO Should we select the long pressed item if we were already in
2587            // selection mode? (i.e. treat it like an item click?)
2588            performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
2589            return true;
2590        }
2591
2592        boolean handled = false;
2593        if (mOnItemLongClickListener != null) {
2594            handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, child,
2595                    longPressPosition, longPressId);
2596        }
2597        if (!handled) {
2598            mContextMenuInfo = createContextMenuInfo(child, longPressPosition, longPressId);
2599            handled = super.showContextMenuForChild(AbsListView.this);
2600        }
2601        if (handled) {
2602            performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
2603        }
2604        return handled;
2605    }
2606
2607    @Override
2608    protected ContextMenuInfo getContextMenuInfo() {
2609        return mContextMenuInfo;
2610    }
2611
2612    /** @hide */
2613    @Override
2614    public boolean showContextMenu(float x, float y, int metaState) {
2615        final int position = pointToPosition((int)x, (int)y);
2616        if (position != INVALID_POSITION) {
2617            final long id = mAdapter.getItemId(position);
2618            View child = getChildAt(position - mFirstPosition);
2619            if (child != null) {
2620                mContextMenuInfo = createContextMenuInfo(child, position, id);
2621                return super.showContextMenuForChild(AbsListView.this);
2622            }
2623        }
2624        return super.showContextMenu(x, y, metaState);
2625    }
2626
2627    @Override
2628    public boolean showContextMenuForChild(View originalView) {
2629        final int longPressPosition = getPositionForView(originalView);
2630        if (longPressPosition >= 0) {
2631            final long longPressId = mAdapter.getItemId(longPressPosition);
2632            boolean handled = false;
2633
2634            if (mOnItemLongClickListener != null) {
2635                handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, originalView,
2636                        longPressPosition, longPressId);
2637            }
2638            if (!handled) {
2639                mContextMenuInfo = createContextMenuInfo(
2640                        getChildAt(longPressPosition - mFirstPosition),
2641                        longPressPosition, longPressId);
2642                handled = super.showContextMenuForChild(originalView);
2643            }
2644
2645            return handled;
2646        }
2647        return false;
2648    }
2649
2650    @Override
2651    public boolean onKeyDown(int keyCode, KeyEvent event) {
2652        return false;
2653    }
2654
2655    @Override
2656    public boolean onKeyUp(int keyCode, KeyEvent event) {
2657        switch (keyCode) {
2658        case KeyEvent.KEYCODE_DPAD_CENTER:
2659        case KeyEvent.KEYCODE_ENTER:
2660            if (!isEnabled()) {
2661                return true;
2662            }
2663            if (isClickable() && isPressed() &&
2664                    mSelectedPosition >= 0 && mAdapter != null &&
2665                    mSelectedPosition < mAdapter.getCount()) {
2666
2667                final View view = getChildAt(mSelectedPosition - mFirstPosition);
2668                if (view != null) {
2669                    performItemClick(view, mSelectedPosition, mSelectedRowId);
2670                    view.setPressed(false);
2671                }
2672                setPressed(false);
2673                return true;
2674            }
2675            break;
2676        }
2677        return super.onKeyUp(keyCode, event);
2678    }
2679
2680    @Override
2681    protected void dispatchSetPressed(boolean pressed) {
2682        // Don't dispatch setPressed to our children. We call setPressed on ourselves to
2683        // get the selector in the right state, but we don't want to press each child.
2684    }
2685
2686    /**
2687     * Maps a point to a position in the list.
2688     *
2689     * @param x X in local coordinate
2690     * @param y Y in local coordinate
2691     * @return The position of the item which contains the specified point, or
2692     *         {@link #INVALID_POSITION} if the point does not intersect an item.
2693     */
2694    public int pointToPosition(int x, int y) {
2695        Rect frame = mTouchFrame;
2696        if (frame == null) {
2697            mTouchFrame = new Rect();
2698            frame = mTouchFrame;
2699        }
2700
2701        final int count = getChildCount();
2702        for (int i = count - 1; i >= 0; i--) {
2703            final View child = getChildAt(i);
2704            if (child.getVisibility() == View.VISIBLE) {
2705                child.getHitRect(frame);
2706                if (frame.contains(x, y)) {
2707                    return mFirstPosition + i;
2708                }
2709            }
2710        }
2711        return INVALID_POSITION;
2712    }
2713
2714
2715    /**
2716     * Maps a point to a the rowId of the item which intersects that point.
2717     *
2718     * @param x X in local coordinate
2719     * @param y Y in local coordinate
2720     * @return The rowId of the item which contains the specified point, or {@link #INVALID_ROW_ID}
2721     *         if the point does not intersect an item.
2722     */
2723    public long pointToRowId(int x, int y) {
2724        int position = pointToPosition(x, y);
2725        if (position >= 0) {
2726            return mAdapter.getItemId(position);
2727        }
2728        return INVALID_ROW_ID;
2729    }
2730
2731    final class CheckForTap implements Runnable {
2732        public void run() {
2733            if (mTouchMode == TOUCH_MODE_DOWN) {
2734                mTouchMode = TOUCH_MODE_TAP;
2735                final View child = getChildAt(mMotionPosition - mFirstPosition);
2736                if (child != null && !child.hasFocusable()) {
2737                    mLayoutMode = LAYOUT_NORMAL;
2738
2739                    if (!mDataChanged) {
2740                        child.setPressed(true);
2741                        setPressed(true);
2742                        layoutChildren();
2743                        positionSelector(mMotionPosition, child);
2744                        refreshDrawableState();
2745
2746                        final int longPressTimeout = ViewConfiguration.getLongPressTimeout();
2747                        final boolean longClickable = isLongClickable();
2748
2749                        if (mSelector != null) {
2750                            Drawable d = mSelector.getCurrent();
2751                            if (d != null && d instanceof TransitionDrawable) {
2752                                if (longClickable) {
2753                                    ((TransitionDrawable) d).startTransition(longPressTimeout);
2754                                } else {
2755                                    ((TransitionDrawable) d).resetTransition();
2756                                }
2757                            }
2758                        }
2759
2760                        if (longClickable) {
2761                            if (mPendingCheckForLongPress == null) {
2762                                mPendingCheckForLongPress = new CheckForLongPress();
2763                            }
2764                            mPendingCheckForLongPress.rememberWindowAttachCount();
2765                            postDelayed(mPendingCheckForLongPress, longPressTimeout);
2766                        } else {
2767                            mTouchMode = TOUCH_MODE_DONE_WAITING;
2768                        }
2769                    } else {
2770                        mTouchMode = TOUCH_MODE_DONE_WAITING;
2771                    }
2772                }
2773            }
2774        }
2775    }
2776
2777    private boolean startScrollIfNeeded(int deltaY) {
2778        // Check if we have moved far enough that it looks more like a
2779        // scroll than a tap
2780        final int distance = Math.abs(deltaY);
2781        final boolean overscroll = mScrollY != 0;
2782        if (overscroll || distance > mTouchSlop) {
2783            createScrollingCache();
2784            mTouchMode = overscroll ? TOUCH_MODE_OVERSCROLL : TOUCH_MODE_SCROLL;
2785            mMotionCorrection = deltaY;
2786            final Handler handler = getHandler();
2787            // Handler should not be null unless the AbsListView is not attached to a
2788            // window, which would make it very hard to scroll it... but the monkeys
2789            // say it's possible.
2790            if (handler != null) {
2791                handler.removeCallbacks(mPendingCheckForLongPress);
2792            }
2793            setPressed(false);
2794            View motionView = getChildAt(mMotionPosition - mFirstPosition);
2795            if (motionView != null) {
2796                motionView.setPressed(false);
2797            }
2798            reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
2799            // Time to start stealing events! Once we've stolen them, don't let anyone
2800            // steal from us
2801            final ViewParent parent = getParent();
2802            if (parent != null) {
2803                parent.requestDisallowInterceptTouchEvent(true);
2804            }
2805            return true;
2806        }
2807
2808        return false;
2809    }
2810
2811    public void onTouchModeChanged(boolean isInTouchMode) {
2812        if (isInTouchMode) {
2813            // Get rid of the selection when we enter touch mode
2814            hideSelector();
2815            // Layout, but only if we already have done so previously.
2816            // (Otherwise may clobber a LAYOUT_SYNC layout that was requested to restore
2817            // state.)
2818            if (getHeight() > 0 && getChildCount() > 0) {
2819                // We do not lose focus initiating a touch (since AbsListView is focusable in
2820                // touch mode). Force an initial layout to get rid of the selection.
2821                layoutChildren();
2822            }
2823            updateSelectorState();
2824        } else {
2825            int touchMode = mTouchMode;
2826            if (touchMode == TOUCH_MODE_OVERSCROLL || touchMode == TOUCH_MODE_OVERFLING) {
2827                if (mFlingRunnable != null) {
2828                    mFlingRunnable.endFling();
2829                }
2830                if (mPositionScroller != null) {
2831                    mPositionScroller.stop();
2832                }
2833
2834                if (mScrollY != 0) {
2835                    mScrollY = 0;
2836                    invalidateParentCaches();
2837                    finishGlows();
2838                    invalidate();
2839                }
2840            }
2841        }
2842    }
2843
2844    @Override
2845    public boolean onTouchEvent(MotionEvent ev) {
2846        if (!isEnabled()) {
2847            // A disabled view that is clickable still consumes the touch
2848            // events, it just doesn't respond to them.
2849            return isClickable() || isLongClickable();
2850        }
2851
2852        if (mFastScroller != null) {
2853            boolean intercepted = mFastScroller.onTouchEvent(ev);
2854            if (intercepted) {
2855                return true;
2856            }
2857        }
2858
2859        final int action = ev.getAction();
2860
2861        View v;
2862        int deltaY;
2863
2864        initVelocityTrackerIfNotExists();
2865        mVelocityTracker.addMovement(ev);
2866
2867        switch (action & MotionEvent.ACTION_MASK) {
2868        case MotionEvent.ACTION_DOWN: {
2869            switch (mTouchMode) {
2870            case TOUCH_MODE_OVERFLING: {
2871                mFlingRunnable.endFling();
2872                if (mPositionScroller != null) {
2873                    mPositionScroller.stop();
2874                }
2875                mTouchMode = TOUCH_MODE_OVERSCROLL;
2876                mMotionX = (int) ev.getX();
2877                mMotionY = mLastY = (int) ev.getY();
2878                mMotionCorrection = 0;
2879                mActivePointerId = ev.getPointerId(0);
2880                mDirection = 0;
2881                break;
2882            }
2883
2884            default: {
2885                mActivePointerId = ev.getPointerId(0);
2886                final int x = (int) ev.getX();
2887                final int y = (int) ev.getY();
2888                int motionPosition = pointToPosition(x, y);
2889                if (!mDataChanged) {
2890                    if ((mTouchMode != TOUCH_MODE_FLING) && (motionPosition >= 0)
2891                            && (getAdapter().isEnabled(motionPosition))) {
2892                        // User clicked on an actual view (and was not stopping a fling).
2893                        // It might be a click or a scroll. Assume it is a click until
2894                        // proven otherwise
2895                        mTouchMode = TOUCH_MODE_DOWN;
2896                        // FIXME Debounce
2897                        if (mPendingCheckForTap == null) {
2898                            mPendingCheckForTap = new CheckForTap();
2899                        }
2900                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
2901                    } else {
2902                        if (mTouchMode == TOUCH_MODE_FLING) {
2903                            // Stopped a fling. It is a scroll.
2904                            createScrollingCache();
2905                            mTouchMode = TOUCH_MODE_SCROLL;
2906                            mMotionCorrection = 0;
2907                            motionPosition = findMotionRow(y);
2908                            mFlingRunnable.flywheelTouch();
2909                        }
2910                    }
2911                }
2912
2913                if (motionPosition >= 0) {
2914                    // Remember where the motion event started
2915                    v = getChildAt(motionPosition - mFirstPosition);
2916                    mMotionViewOriginalTop = v.getTop();
2917                }
2918                mMotionX = x;
2919                mMotionY = y;
2920                mMotionPosition = motionPosition;
2921                mLastY = Integer.MIN_VALUE;
2922                break;
2923            }
2924            }
2925
2926            if (performButtonActionOnTouchDown(ev)) {
2927                if (mTouchMode == TOUCH_MODE_DOWN) {
2928                    removeCallbacks(mPendingCheckForTap);
2929                }
2930            }
2931            break;
2932        }
2933
2934        case MotionEvent.ACTION_MOVE: {
2935            int pointerIndex = ev.findPointerIndex(mActivePointerId);
2936            if (pointerIndex == -1) {
2937                pointerIndex = 0;
2938                mActivePointerId = ev.getPointerId(pointerIndex);
2939            }
2940            final int y = (int) ev.getY(pointerIndex);
2941            deltaY = y - mMotionY;
2942            switch (mTouchMode) {
2943            case TOUCH_MODE_DOWN:
2944            case TOUCH_MODE_TAP:
2945            case TOUCH_MODE_DONE_WAITING:
2946                // Check if we have moved far enough that it looks more like a
2947                // scroll than a tap
2948                startScrollIfNeeded(deltaY);
2949                break;
2950            case TOUCH_MODE_SCROLL:
2951                if (PROFILE_SCROLLING) {
2952                    if (!mScrollProfilingStarted) {
2953                        Debug.startMethodTracing("AbsListViewScroll");
2954                        mScrollProfilingStarted = true;
2955                    }
2956                }
2957
2958                if (mScrollStrictSpan == null) {
2959                    // If it's non-null, we're already in a scroll.
2960                    mScrollStrictSpan = StrictMode.enterCriticalSpan("AbsListView-scroll");
2961                }
2962
2963                if (y != mLastY) {
2964                    // We may be here after stopping a fling and continuing to scroll.
2965                    // If so, we haven't disallowed intercepting touch events yet.
2966                    // Make sure that we do so in case we're in a parent that can intercept.
2967                    if ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) == 0 &&
2968                            Math.abs(deltaY) > mTouchSlop) {
2969                        final ViewParent parent = getParent();
2970                        if (parent != null) {
2971                            parent.requestDisallowInterceptTouchEvent(true);
2972                        }
2973                    }
2974
2975                    final int rawDeltaY = deltaY;
2976                    deltaY -= mMotionCorrection;
2977                    int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY;
2978
2979                    final int motionIndex;
2980                    if (mMotionPosition >= 0) {
2981                        motionIndex = mMotionPosition - mFirstPosition;
2982                    } else {
2983                        // If we don't have a motion position that we can reliably track,
2984                        // pick something in the middle to make a best guess at things below.
2985                        motionIndex = getChildCount() / 2;
2986                    }
2987
2988                    int motionViewPrevTop = 0;
2989                    View motionView = this.getChildAt(motionIndex);
2990                    if (motionView != null) {
2991                        motionViewPrevTop = motionView.getTop();
2992                    }
2993
2994                    // No need to do all this work if we're not going to move anyway
2995                    boolean atEdge = false;
2996                    if (incrementalDeltaY != 0) {
2997                        atEdge = trackMotionScroll(deltaY, incrementalDeltaY);
2998                    }
2999
3000                    // Check to see if we have bumped into the scroll limit
3001                    motionView = this.getChildAt(motionIndex);
3002                    if (motionView != null) {
3003                        // Check if the top of the motion view is where it is
3004                        // supposed to be
3005                        final int motionViewRealTop = motionView.getTop();
3006                        if (atEdge) {
3007                            // Apply overscroll
3008
3009                            int overscroll = -incrementalDeltaY -
3010                                    (motionViewRealTop - motionViewPrevTop);
3011                            overScrollBy(0, overscroll, 0, mScrollY, 0, 0,
3012                                    0, mOverscrollDistance, true);
3013                            if (Math.abs(mOverscrollDistance) == Math.abs(mScrollY)) {
3014                                // Don't allow overfling if we're at the edge.
3015                                if (mVelocityTracker != null) {
3016                                    mVelocityTracker.clear();
3017                                }
3018                            }
3019
3020                            final int overscrollMode = getOverScrollMode();
3021                            if (overscrollMode == OVER_SCROLL_ALWAYS ||
3022                                    (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
3023                                            !contentFits())) {
3024                                mDirection = 0; // Reset when entering overscroll.
3025                                mTouchMode = TOUCH_MODE_OVERSCROLL;
3026                                if (rawDeltaY > 0) {
3027                                    mEdgeGlowTop.onPull((float) overscroll / getHeight());
3028                                    if (!mEdgeGlowBottom.isFinished()) {
3029                                        mEdgeGlowBottom.onRelease();
3030                                    }
3031                                } else if (rawDeltaY < 0) {
3032                                    mEdgeGlowBottom.onPull((float) overscroll / getHeight());
3033                                    if (!mEdgeGlowTop.isFinished()) {
3034                                        mEdgeGlowTop.onRelease();
3035                                    }
3036                                }
3037                            }
3038                        }
3039                        mMotionY = y;
3040                        invalidate();
3041                    }
3042                    mLastY = y;
3043                }
3044                break;
3045
3046            case TOUCH_MODE_OVERSCROLL:
3047                if (y != mLastY) {
3048                    final int rawDeltaY = deltaY;
3049                    deltaY -= mMotionCorrection;
3050                    int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY;
3051
3052                    final int oldScroll = mScrollY;
3053                    final int newScroll = oldScroll - incrementalDeltaY;
3054                    int newDirection = y > mLastY ? 1 : -1;
3055
3056                    if (mDirection == 0) {
3057                        mDirection = newDirection;
3058                    }
3059
3060                    int overScrollDistance = -incrementalDeltaY;
3061                    if ((newScroll < 0 && oldScroll >= 0) || (newScroll > 0 && oldScroll <= 0)) {
3062                        overScrollDistance = -oldScroll;
3063                        incrementalDeltaY += overScrollDistance;
3064                    } else {
3065                        incrementalDeltaY = 0;
3066                    }
3067
3068                    if (overScrollDistance != 0) {
3069                        overScrollBy(0, overScrollDistance, 0, mScrollY, 0, 0,
3070                                0, mOverscrollDistance, true);
3071                        final int overscrollMode = getOverScrollMode();
3072                        if (overscrollMode == OVER_SCROLL_ALWAYS ||
3073                                (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
3074                                        !contentFits())) {
3075                            if (rawDeltaY > 0) {
3076                                mEdgeGlowTop.onPull((float) overScrollDistance / getHeight());
3077                                if (!mEdgeGlowBottom.isFinished()) {
3078                                    mEdgeGlowBottom.onRelease();
3079                                }
3080                            } else if (rawDeltaY < 0) {
3081                                mEdgeGlowBottom.onPull((float) overScrollDistance / getHeight());
3082                                if (!mEdgeGlowTop.isFinished()) {
3083                                    mEdgeGlowTop.onRelease();
3084                                }
3085                            }
3086                            invalidate();
3087                        }
3088                    }
3089
3090                    if (incrementalDeltaY != 0) {
3091                        // Coming back to 'real' list scrolling
3092                        mScrollY = 0;
3093                        invalidateParentIfNeeded();
3094
3095                        // No need to do all this work if we're not going to move anyway
3096                        if (incrementalDeltaY != 0) {
3097                            trackMotionScroll(incrementalDeltaY, incrementalDeltaY);
3098                        }
3099
3100                        mTouchMode = TOUCH_MODE_SCROLL;
3101
3102                        // We did not scroll the full amount. Treat this essentially like the
3103                        // start of a new touch scroll
3104                        final int motionPosition = findClosestMotionRow(y);
3105
3106                        mMotionCorrection = 0;
3107                        View motionView = getChildAt(motionPosition - mFirstPosition);
3108                        mMotionViewOriginalTop = motionView != null ? motionView.getTop() : 0;
3109                        mMotionY = y;
3110                        mMotionPosition = motionPosition;
3111                    }
3112                    mLastY = y;
3113                    mDirection = newDirection;
3114                }
3115                break;
3116            }
3117
3118            break;
3119        }
3120
3121        case MotionEvent.ACTION_UP: {
3122            switch (mTouchMode) {
3123            case TOUCH_MODE_DOWN:
3124            case TOUCH_MODE_TAP:
3125            case TOUCH_MODE_DONE_WAITING:
3126                final int motionPosition = mMotionPosition;
3127                final View child = getChildAt(motionPosition - mFirstPosition);
3128
3129                final float x = ev.getX();
3130                final boolean inList = x > mListPadding.left && x < getWidth() - mListPadding.right;
3131
3132                if (child != null && !child.hasFocusable() && inList) {
3133                    if (mTouchMode != TOUCH_MODE_DOWN) {
3134                        child.setPressed(false);
3135                    }
3136
3137                    if (mPerformClick == null) {
3138                        mPerformClick = new PerformClick();
3139                    }
3140
3141                    final AbsListView.PerformClick performClick = mPerformClick;
3142                    performClick.mClickMotionPosition = motionPosition;
3143                    performClick.rememberWindowAttachCount();
3144
3145                    mResurrectToPosition = motionPosition;
3146
3147                    if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) {
3148                        final Handler handler = getHandler();
3149                        if (handler != null) {
3150                            handler.removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
3151                                    mPendingCheckForTap : mPendingCheckForLongPress);
3152                        }
3153                        mLayoutMode = LAYOUT_NORMAL;
3154                        if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
3155                            mTouchMode = TOUCH_MODE_TAP;
3156                            setSelectedPositionInt(mMotionPosition);
3157                            layoutChildren();
3158                            child.setPressed(true);
3159                            positionSelector(mMotionPosition, child);
3160                            setPressed(true);
3161                            if (mSelector != null) {
3162                                Drawable d = mSelector.getCurrent();
3163                                if (d != null && d instanceof TransitionDrawable) {
3164                                    ((TransitionDrawable) d).resetTransition();
3165                                }
3166                            }
3167                            if (mTouchModeReset != null) {
3168                                removeCallbacks(mTouchModeReset);
3169                            }
3170                            mTouchModeReset = new Runnable() {
3171                                @Override
3172                                public void run() {
3173                                    mTouchMode = TOUCH_MODE_REST;
3174                                    child.setPressed(false);
3175                                    setPressed(false);
3176                                    if (!mDataChanged) {
3177                                        performClick.run();
3178                                    }
3179                                }
3180                            };
3181                            postDelayed(mTouchModeReset,
3182                                    ViewConfiguration.getPressedStateDuration());
3183                        } else {
3184                            mTouchMode = TOUCH_MODE_REST;
3185                            updateSelectorState();
3186                        }
3187                        return true;
3188                    } else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
3189                        performClick.run();
3190                    }
3191                }
3192                mTouchMode = TOUCH_MODE_REST;
3193                updateSelectorState();
3194                break;
3195            case TOUCH_MODE_SCROLL:
3196                final int childCount = getChildCount();
3197                if (childCount > 0) {
3198                    final int firstChildTop = getChildAt(0).getTop();
3199                    final int lastChildBottom = getChildAt(childCount - 1).getBottom();
3200                    final int contentTop = mListPadding.top;
3201                    final int contentBottom = getHeight() - mListPadding.bottom;
3202                    if (mFirstPosition == 0 && firstChildTop >= contentTop &&
3203                            mFirstPosition + childCount < mItemCount &&
3204                            lastChildBottom <= getHeight() - contentBottom) {
3205                        mTouchMode = TOUCH_MODE_REST;
3206                        reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
3207                    } else {
3208                        final VelocityTracker velocityTracker = mVelocityTracker;
3209                        velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
3210
3211                        final int initialVelocity = (int)
3212                                (velocityTracker.getYVelocity(mActivePointerId) * mVelocityScale);
3213                        // Fling if we have enough velocity and we aren't at a boundary.
3214                        // Since we can potentially overfling more than we can overscroll, don't
3215                        // allow the weird behavior where you can scroll to a boundary then
3216                        // fling further.
3217                        if (Math.abs(initialVelocity) > mMinimumVelocity &&
3218                                !((mFirstPosition == 0 &&
3219                                        firstChildTop == contentTop - mOverscrollDistance) ||
3220                                  (mFirstPosition + childCount == mItemCount &&
3221                                        lastChildBottom == contentBottom + mOverscrollDistance))) {
3222                            if (mFlingRunnable == null) {
3223                                mFlingRunnable = new FlingRunnable();
3224                            }
3225                            reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
3226
3227                            mFlingRunnable.start(-initialVelocity);
3228                        } else {
3229                            mTouchMode = TOUCH_MODE_REST;
3230                            reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
3231                            if (mFlingRunnable != null) {
3232                                mFlingRunnable.endFling();
3233                            }
3234                            if (mPositionScroller != null) {
3235                                mPositionScroller.stop();
3236                            }
3237                        }
3238                    }
3239                } else {
3240                    mTouchMode = TOUCH_MODE_REST;
3241                    reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
3242                }
3243                break;
3244
3245            case TOUCH_MODE_OVERSCROLL:
3246                if (mFlingRunnable == null) {
3247                    mFlingRunnable = new FlingRunnable();
3248                }
3249                final VelocityTracker velocityTracker = mVelocityTracker;
3250                velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
3251                final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
3252
3253                reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
3254                if (Math.abs(initialVelocity) > mMinimumVelocity) {
3255                    mFlingRunnable.startOverfling(-initialVelocity);
3256                } else {
3257                    mFlingRunnable.startSpringback();
3258                }
3259
3260                break;
3261            }
3262
3263            setPressed(false);
3264
3265            if (mEdgeGlowTop != null) {
3266                mEdgeGlowTop.onRelease();
3267                mEdgeGlowBottom.onRelease();
3268            }
3269
3270            // Need to redraw since we probably aren't drawing the selector anymore
3271            invalidate();
3272
3273            final Handler handler = getHandler();
3274            if (handler != null) {
3275                handler.removeCallbacks(mPendingCheckForLongPress);
3276            }
3277
3278            recycleVelocityTracker();
3279
3280            mActivePointerId = INVALID_POINTER;
3281
3282            if (PROFILE_SCROLLING) {
3283                if (mScrollProfilingStarted) {
3284                    Debug.stopMethodTracing();
3285                    mScrollProfilingStarted = false;
3286                }
3287            }
3288
3289            if (mScrollStrictSpan != null) {
3290                mScrollStrictSpan.finish();
3291                mScrollStrictSpan = null;
3292            }
3293            break;
3294        }
3295
3296        case MotionEvent.ACTION_CANCEL: {
3297            switch (mTouchMode) {
3298            case TOUCH_MODE_OVERSCROLL:
3299                if (mFlingRunnable == null) {
3300                    mFlingRunnable = new FlingRunnable();
3301                }
3302                mFlingRunnable.startSpringback();
3303                break;
3304
3305            case TOUCH_MODE_OVERFLING:
3306                // Do nothing - let it play out.
3307                break;
3308
3309            default:
3310                mTouchMode = TOUCH_MODE_REST;
3311                setPressed(false);
3312                View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
3313                if (motionView != null) {
3314                    motionView.setPressed(false);
3315                }
3316                clearScrollingCache();
3317
3318                final Handler handler = getHandler();
3319                if (handler != null) {
3320                    handler.removeCallbacks(mPendingCheckForLongPress);
3321                }
3322
3323                recycleVelocityTracker();
3324            }
3325
3326            if (mEdgeGlowTop != null) {
3327                mEdgeGlowTop.onRelease();
3328                mEdgeGlowBottom.onRelease();
3329            }
3330            mActivePointerId = INVALID_POINTER;
3331            break;
3332        }
3333
3334        case MotionEvent.ACTION_POINTER_UP: {
3335            onSecondaryPointerUp(ev);
3336            final int x = mMotionX;
3337            final int y = mMotionY;
3338            final int motionPosition = pointToPosition(x, y);
3339            if (motionPosition >= 0) {
3340                // Remember where the motion event started
3341                v = getChildAt(motionPosition - mFirstPosition);
3342                mMotionViewOriginalTop = v.getTop();
3343                mMotionPosition = motionPosition;
3344            }
3345            mLastY = y;
3346            break;
3347        }
3348
3349        case MotionEvent.ACTION_POINTER_DOWN: {
3350            // New pointers take over dragging duties
3351            final int index = ev.getActionIndex();
3352            final int id = ev.getPointerId(index);
3353            final int x = (int) ev.getX(index);
3354            final int y = (int) ev.getY(index);
3355            mMotionCorrection = 0;
3356            mActivePointerId = id;
3357            mMotionX = x;
3358            mMotionY = y;
3359            final int motionPosition = pointToPosition(x, y);
3360            if (motionPosition >= 0) {
3361                // Remember where the motion event started
3362                v = getChildAt(motionPosition - mFirstPosition);
3363                mMotionViewOriginalTop = v.getTop();
3364                mMotionPosition = motionPosition;
3365            }
3366            mLastY = y;
3367            break;
3368        }
3369        }
3370
3371        return true;
3372    }
3373
3374    @Override
3375    protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
3376        if (mScrollY != scrollY) {
3377            onScrollChanged(mScrollX, scrollY, mScrollX, mScrollY);
3378            mScrollY = scrollY;
3379            invalidateParentIfNeeded();
3380
3381            awakenScrollBars();
3382        }
3383    }
3384
3385    @Override
3386    public boolean onGenericMotionEvent(MotionEvent event) {
3387        if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
3388            switch (event.getAction()) {
3389                case MotionEvent.ACTION_SCROLL: {
3390                    if (mTouchMode == TOUCH_MODE_REST) {
3391                        final float vscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
3392                        if (vscroll != 0) {
3393                            final int delta = (int) (vscroll * getVerticalScrollFactor());
3394                            if (!trackMotionScroll(delta, delta)) {
3395                                return true;
3396                            }
3397                        }
3398                    }
3399                }
3400            }
3401        }
3402        return super.onGenericMotionEvent(event);
3403    }
3404
3405    @Override
3406    public void draw(Canvas canvas) {
3407        super.draw(canvas);
3408        if (mEdgeGlowTop != null) {
3409            final int scrollY = mScrollY;
3410            if (!mEdgeGlowTop.isFinished()) {
3411                final int restoreCount = canvas.save();
3412                final int leftPadding = mListPadding.left + mGlowPaddingLeft;
3413                final int rightPadding = mListPadding.right + mGlowPaddingRight;
3414                final int width = getWidth() - leftPadding - rightPadding;
3415
3416                canvas.translate(leftPadding,
3417                        Math.min(0, scrollY + mFirstPositionDistanceGuess));
3418                mEdgeGlowTop.setSize(width, getHeight());
3419                if (mEdgeGlowTop.draw(canvas)) {
3420                    invalidate();
3421                }
3422                canvas.restoreToCount(restoreCount);
3423            }
3424            if (!mEdgeGlowBottom.isFinished()) {
3425                final int restoreCount = canvas.save();
3426                final int leftPadding = mListPadding.left + mGlowPaddingLeft;
3427                final int rightPadding = mListPadding.right + mGlowPaddingRight;
3428                final int width = getWidth() - leftPadding - rightPadding;
3429                final int height = getHeight();
3430
3431                canvas.translate(-width + leftPadding,
3432                        Math.max(height, scrollY + mLastPositionDistanceGuess));
3433                canvas.rotate(180, width, 0);
3434                mEdgeGlowBottom.setSize(width, height);
3435                if (mEdgeGlowBottom.draw(canvas)) {
3436                    invalidate();
3437                }
3438                canvas.restoreToCount(restoreCount);
3439            }
3440        }
3441        if (mFastScroller != null) {
3442            final int scrollY = mScrollY;
3443            if (scrollY != 0) {
3444                // Pin to the top/bottom during overscroll
3445                int restoreCount = canvas.save();
3446                canvas.translate(0, (float) scrollY);
3447                mFastScroller.draw(canvas);
3448                canvas.restoreToCount(restoreCount);
3449            } else {
3450                mFastScroller.draw(canvas);
3451            }
3452        }
3453    }
3454
3455    /**
3456     * @hide
3457     */
3458    public void setOverScrollEffectPadding(int leftPadding, int rightPadding) {
3459        mGlowPaddingLeft = leftPadding;
3460        mGlowPaddingRight = rightPadding;
3461    }
3462
3463    private void initOrResetVelocityTracker() {
3464        if (mVelocityTracker == null) {
3465            mVelocityTracker = VelocityTracker.obtain();
3466        } else {
3467            mVelocityTracker.clear();
3468        }
3469    }
3470
3471    private void initVelocityTrackerIfNotExists() {
3472        if (mVelocityTracker == null) {
3473            mVelocityTracker = VelocityTracker.obtain();
3474        }
3475    }
3476
3477    private void recycleVelocityTracker() {
3478        if (mVelocityTracker != null) {
3479            mVelocityTracker.recycle();
3480            mVelocityTracker = null;
3481        }
3482    }
3483
3484    @Override
3485    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
3486        if (disallowIntercept) {
3487            recycleVelocityTracker();
3488        }
3489        super.requestDisallowInterceptTouchEvent(disallowIntercept);
3490    }
3491
3492    @Override
3493    public boolean onInterceptTouchEvent(MotionEvent ev) {
3494        int action = ev.getAction();
3495        View v;
3496
3497        if (mFastScroller != null) {
3498            boolean intercepted = mFastScroller.onInterceptTouchEvent(ev);
3499            if (intercepted) {
3500                return true;
3501            }
3502        }
3503
3504        switch (action & MotionEvent.ACTION_MASK) {
3505        case MotionEvent.ACTION_DOWN: {
3506            int touchMode = mTouchMode;
3507            if (touchMode == TOUCH_MODE_OVERFLING || touchMode == TOUCH_MODE_OVERSCROLL) {
3508                mMotionCorrection = 0;
3509                return true;
3510            }
3511
3512            final int x = (int) ev.getX();
3513            final int y = (int) ev.getY();
3514            mActivePointerId = ev.getPointerId(0);
3515
3516            int motionPosition = findMotionRow(y);
3517            if (touchMode != TOUCH_MODE_FLING && motionPosition >= 0) {
3518                // User clicked on an actual view (and was not stopping a fling).
3519                // Remember where the motion event started
3520                v = getChildAt(motionPosition - mFirstPosition);
3521                mMotionViewOriginalTop = v.getTop();
3522                mMotionX = x;
3523                mMotionY = y;
3524                mMotionPosition = motionPosition;
3525                mTouchMode = TOUCH_MODE_DOWN;
3526                clearScrollingCache();
3527            }
3528            mLastY = Integer.MIN_VALUE;
3529            initOrResetVelocityTracker();
3530            mVelocityTracker.addMovement(ev);
3531            if (touchMode == TOUCH_MODE_FLING) {
3532                return true;
3533            }
3534            break;
3535        }
3536
3537        case MotionEvent.ACTION_MOVE: {
3538            switch (mTouchMode) {
3539            case TOUCH_MODE_DOWN:
3540                int pointerIndex = ev.findPointerIndex(mActivePointerId);
3541                if (pointerIndex == -1) {
3542                    pointerIndex = 0;
3543                    mActivePointerId = ev.getPointerId(pointerIndex);
3544                }
3545                final int y = (int) ev.getY(pointerIndex);
3546                initVelocityTrackerIfNotExists();
3547                mVelocityTracker.addMovement(ev);
3548                if (startScrollIfNeeded(y - mMotionY)) {
3549                    return true;
3550                }
3551                break;
3552            }
3553            break;
3554        }
3555
3556        case MotionEvent.ACTION_CANCEL:
3557        case MotionEvent.ACTION_UP: {
3558            mTouchMode = TOUCH_MODE_REST;
3559            mActivePointerId = INVALID_POINTER;
3560            recycleVelocityTracker();
3561            reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
3562            break;
3563        }
3564
3565        case MotionEvent.ACTION_POINTER_UP: {
3566            onSecondaryPointerUp(ev);
3567            break;
3568        }
3569        }
3570
3571        return false;
3572    }
3573
3574    private void onSecondaryPointerUp(MotionEvent ev) {
3575        final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
3576                MotionEvent.ACTION_POINTER_INDEX_SHIFT;
3577        final int pointerId = ev.getPointerId(pointerIndex);
3578        if (pointerId == mActivePointerId) {
3579            // This was our active pointer going up. Choose a new
3580            // active pointer and adjust accordingly.
3581            // TODO: Make this decision more intelligent.
3582            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
3583            mMotionX = (int) ev.getX(newPointerIndex);
3584            mMotionY = (int) ev.getY(newPointerIndex);
3585            mMotionCorrection = 0;
3586            mActivePointerId = ev.getPointerId(newPointerIndex);
3587        }
3588    }
3589
3590    /**
3591     * {@inheritDoc}
3592     */
3593    @Override
3594    public void addTouchables(ArrayList<View> views) {
3595        final int count = getChildCount();
3596        final int firstPosition = mFirstPosition;
3597        final ListAdapter adapter = mAdapter;
3598
3599        if (adapter == null) {
3600            return;
3601        }
3602
3603        for (int i = 0; i < count; i++) {
3604            final View child = getChildAt(i);
3605            if (adapter.isEnabled(firstPosition + i)) {
3606                views.add(child);
3607            }
3608            child.addTouchables(views);
3609        }
3610    }
3611
3612    /**
3613     * Fires an "on scroll state changed" event to the registered
3614     * {@link android.widget.AbsListView.OnScrollListener}, if any. The state change
3615     * is fired only if the specified state is different from the previously known state.
3616     *
3617     * @param newState The new scroll state.
3618     */
3619    void reportScrollStateChange(int newState) {
3620        if (newState != mLastScrollState) {
3621            if (mOnScrollListener != null) {
3622                mLastScrollState = newState;
3623                mOnScrollListener.onScrollStateChanged(this, newState);
3624            }
3625        }
3626    }
3627
3628    /**
3629     * Responsible for fling behavior. Use {@link #start(int)} to
3630     * initiate a fling. Each frame of the fling is handled in {@link #run()}.
3631     * A FlingRunnable will keep re-posting itself until the fling is done.
3632     *
3633     */
3634    private class FlingRunnable implements Runnable {
3635        /**
3636         * Tracks the decay of a fling scroll
3637         */
3638        private final OverScroller mScroller;
3639
3640        /**
3641         * Y value reported by mScroller on the previous fling
3642         */
3643        private int mLastFlingY;
3644
3645        private final Runnable mCheckFlywheel = new Runnable() {
3646            public void run() {
3647                final int activeId = mActivePointerId;
3648                final VelocityTracker vt = mVelocityTracker;
3649                final OverScroller scroller = mScroller;
3650                if (vt == null || activeId == INVALID_POINTER) {
3651                    return;
3652                }
3653
3654                vt.computeCurrentVelocity(1000, mMaximumVelocity);
3655                final float yvel = -vt.getYVelocity(activeId);
3656
3657                if (scroller.isScrollingInDirection(0, yvel)) {
3658                    // Keep the fling alive a little longer
3659                    postDelayed(this, FLYWHEEL_TIMEOUT);
3660                } else {
3661                    endFling();
3662                    mTouchMode = TOUCH_MODE_SCROLL;
3663                    reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
3664                }
3665            }
3666        };
3667
3668        private static final int FLYWHEEL_TIMEOUT = 40; // milliseconds
3669
3670        FlingRunnable() {
3671            mScroller = new OverScroller(getContext());
3672        }
3673
3674        void start(int initialVelocity) {
3675            int initialY = initialVelocity < 0 ? Integer.MAX_VALUE : 0;
3676            mLastFlingY = initialY;
3677            mScroller.fling(0, initialY, 0, initialVelocity,
3678                    0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE);
3679            mTouchMode = TOUCH_MODE_FLING;
3680            post(this);
3681
3682            if (PROFILE_FLINGING) {
3683                if (!mFlingProfilingStarted) {
3684                    Debug.startMethodTracing("AbsListViewFling");
3685                    mFlingProfilingStarted = true;
3686                }
3687            }
3688
3689            if (mFlingStrictSpan == null) {
3690                mFlingStrictSpan = StrictMode.enterCriticalSpan("AbsListView-fling");
3691            }
3692        }
3693
3694        void startSpringback() {
3695            if (mScroller.springBack(0, mScrollY, 0, 0, 0, 0)) {
3696                mTouchMode = TOUCH_MODE_OVERFLING;
3697                invalidate();
3698                post(this);
3699            } else {
3700                mTouchMode = TOUCH_MODE_REST;
3701                reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
3702            }
3703        }
3704
3705        void startOverfling(int initialVelocity) {
3706            mScroller.fling(0, mScrollY, 0, initialVelocity, 0, 0,
3707                    Integer.MIN_VALUE, Integer.MAX_VALUE, 0, getHeight());
3708            mTouchMode = TOUCH_MODE_OVERFLING;
3709            invalidate();
3710            post(this);
3711        }
3712
3713        void edgeReached(int delta) {
3714            mScroller.notifyVerticalEdgeReached(mScrollY, 0, mOverflingDistance);
3715            final int overscrollMode = getOverScrollMode();
3716            if (overscrollMode == OVER_SCROLL_ALWAYS ||
3717                    (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && !contentFits())) {
3718                mTouchMode = TOUCH_MODE_OVERFLING;
3719                final int vel = (int) mScroller.getCurrVelocity();
3720                if (delta > 0) {
3721                    mEdgeGlowTop.onAbsorb(vel);
3722                } else {
3723                    mEdgeGlowBottom.onAbsorb(vel);
3724                }
3725            } else {
3726                mTouchMode = TOUCH_MODE_REST;
3727                if (mPositionScroller != null) {
3728                    mPositionScroller.stop();
3729                }
3730            }
3731            invalidate();
3732            post(this);
3733        }
3734
3735        void startScroll(int distance, int duration) {
3736            int initialY = distance < 0 ? Integer.MAX_VALUE : 0;
3737            mLastFlingY = initialY;
3738            mScroller.startScroll(0, initialY, 0, distance, duration);
3739            mTouchMode = TOUCH_MODE_FLING;
3740            post(this);
3741        }
3742
3743        void endFling() {
3744            mTouchMode = TOUCH_MODE_REST;
3745
3746            removeCallbacks(this);
3747            removeCallbacks(mCheckFlywheel);
3748
3749            reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
3750            clearScrollingCache();
3751            mScroller.abortAnimation();
3752
3753            if (mFlingStrictSpan != null) {
3754                mFlingStrictSpan.finish();
3755                mFlingStrictSpan = null;
3756            }
3757        }
3758
3759        void flywheelTouch() {
3760            postDelayed(mCheckFlywheel, FLYWHEEL_TIMEOUT);
3761        }
3762
3763        public void run() {
3764            switch (mTouchMode) {
3765            default:
3766                endFling();
3767                return;
3768
3769            case TOUCH_MODE_SCROLL:
3770                if (mScroller.isFinished()) {
3771                    return;
3772                }
3773                // Fall through
3774            case TOUCH_MODE_FLING: {
3775                if (mItemCount == 0 || getChildCount() == 0) {
3776                    endFling();
3777                    return;
3778                }
3779
3780                final OverScroller scroller = mScroller;
3781                boolean more = scroller.computeScrollOffset();
3782                final int y = scroller.getCurrY();
3783
3784                // Flip sign to convert finger direction to list items direction
3785                // (e.g. finger moving down means list is moving towards the top)
3786                int delta = mLastFlingY - y;
3787
3788                // Pretend that each frame of a fling scroll is a touch scroll
3789                if (delta > 0) {
3790                    // List is moving towards the top. Use first view as mMotionPosition
3791                    mMotionPosition = mFirstPosition;
3792                    final View firstView = getChildAt(0);
3793                    mMotionViewOriginalTop = firstView.getTop();
3794
3795                    // Don't fling more than 1 screen
3796                    delta = Math.min(getHeight() - mPaddingBottom - mPaddingTop - 1, delta);
3797                } else {
3798                    // List is moving towards the bottom. Use last view as mMotionPosition
3799                    int offsetToLast = getChildCount() - 1;
3800                    mMotionPosition = mFirstPosition + offsetToLast;
3801
3802                    final View lastView = getChildAt(offsetToLast);
3803                    mMotionViewOriginalTop = lastView.getTop();
3804
3805                    // Don't fling more than 1 screen
3806                    delta = Math.max(-(getHeight() - mPaddingBottom - mPaddingTop - 1), delta);
3807                }
3808
3809                // Check to see if we have bumped into the scroll limit
3810                View motionView = getChildAt(mMotionPosition - mFirstPosition);
3811                int oldTop = 0;
3812                if (motionView != null) {
3813                    oldTop = motionView.getTop();
3814                }
3815
3816                // Don't stop just because delta is zero (it could have been rounded)
3817                final boolean atEnd = trackMotionScroll(delta, delta) && (delta != 0);
3818                if (atEnd) {
3819                    if (motionView != null) {
3820                        // Tweak the scroll for how far we overshot
3821                        int overshoot = -(delta - (motionView.getTop() - oldTop));
3822                        overScrollBy(0, overshoot, 0, mScrollY, 0, 0,
3823                                0, mOverflingDistance, false);
3824                    }
3825                    if (more) {
3826                        edgeReached(delta);
3827                    }
3828                    break;
3829                }
3830
3831                if (more && !atEnd) {
3832                    invalidate();
3833                    mLastFlingY = y;
3834                    post(this);
3835                } else {
3836                    endFling();
3837
3838                    if (PROFILE_FLINGING) {
3839                        if (mFlingProfilingStarted) {
3840                            Debug.stopMethodTracing();
3841                            mFlingProfilingStarted = false;
3842                        }
3843
3844                        if (mFlingStrictSpan != null) {
3845                            mFlingStrictSpan.finish();
3846                            mFlingStrictSpan = null;
3847                        }
3848                    }
3849                }
3850                break;
3851            }
3852
3853            case TOUCH_MODE_OVERFLING: {
3854                final OverScroller scroller = mScroller;
3855                if (scroller.computeScrollOffset()) {
3856                    final int scrollY = mScrollY;
3857                    final int currY = scroller.getCurrY();
3858                    final int deltaY = currY - scrollY;
3859                    if (overScrollBy(0, deltaY, 0, scrollY, 0, 0,
3860                            0, mOverflingDistance, false)) {
3861                        final boolean crossDown = scrollY <= 0 && currY > 0;
3862                        final boolean crossUp = scrollY >= 0 && currY < 0;
3863                        if (crossDown || crossUp) {
3864                            int velocity = (int) scroller.getCurrVelocity();
3865                            if (crossUp) velocity = -velocity;
3866
3867                            // Don't flywheel from this; we're just continuing things.
3868                            scroller.abortAnimation();
3869                            start(velocity);
3870                        } else {
3871                            startSpringback();
3872                        }
3873                    } else {
3874                        invalidate();
3875                        post(this);
3876                    }
3877                } else {
3878                    endFling();
3879                }
3880                break;
3881            }
3882            }
3883        }
3884    }
3885
3886
3887    class PositionScroller implements Runnable {
3888        private static final int SCROLL_DURATION = 400;
3889
3890        private static final int MOVE_DOWN_POS = 1;
3891        private static final int MOVE_UP_POS = 2;
3892        private static final int MOVE_DOWN_BOUND = 3;
3893        private static final int MOVE_UP_BOUND = 4;
3894        private static final int MOVE_OFFSET = 5;
3895
3896        private int mMode;
3897        private int mTargetPos;
3898        private int mBoundPos;
3899        private int mLastSeenPos;
3900        private int mScrollDuration;
3901        private final int mExtraScroll;
3902
3903        private int mOffsetFromTop;
3904
3905        PositionScroller() {
3906            mExtraScroll = ViewConfiguration.get(mContext).getScaledFadingEdgeLength();
3907        }
3908
3909        void start(int position) {
3910            stop();
3911
3912            final int firstPos = mFirstPosition;
3913            final int lastPos = firstPos + getChildCount() - 1;
3914
3915            int viewTravelCount;
3916            if (position <= firstPos) {
3917                viewTravelCount = firstPos - position + 1;
3918                mMode = MOVE_UP_POS;
3919            } else if (position >= lastPos) {
3920                viewTravelCount = position - lastPos + 1;
3921                mMode = MOVE_DOWN_POS;
3922            } else {
3923                // Already on screen, nothing to do
3924                return;
3925            }
3926
3927            if (viewTravelCount > 0) {
3928                mScrollDuration = SCROLL_DURATION / viewTravelCount;
3929            } else {
3930                mScrollDuration = SCROLL_DURATION;
3931            }
3932            mTargetPos = position;
3933            mBoundPos = INVALID_POSITION;
3934            mLastSeenPos = INVALID_POSITION;
3935
3936            post(this);
3937        }
3938
3939        void start(int position, int boundPosition) {
3940            stop();
3941
3942            if (boundPosition == INVALID_POSITION) {
3943                start(position);
3944                return;
3945            }
3946
3947            final int firstPos = mFirstPosition;
3948            final int lastPos = firstPos + getChildCount() - 1;
3949
3950            int viewTravelCount;
3951            if (position <= firstPos) {
3952                final int boundPosFromLast = lastPos - boundPosition;
3953                if (boundPosFromLast < 1) {
3954                    // Moving would shift our bound position off the screen. Abort.
3955                    return;
3956                }
3957
3958                final int posTravel = firstPos - position + 1;
3959                final int boundTravel = boundPosFromLast - 1;
3960                if (boundTravel < posTravel) {
3961                    viewTravelCount = boundTravel;
3962                    mMode = MOVE_UP_BOUND;
3963                } else {
3964                    viewTravelCount = posTravel;
3965                    mMode = MOVE_UP_POS;
3966                }
3967            } else if (position >= lastPos) {
3968                final int boundPosFromFirst = boundPosition - firstPos;
3969                if (boundPosFromFirst < 1) {
3970                    // Moving would shift our bound position off the screen. Abort.
3971                    return;
3972                }
3973
3974                final int posTravel = position - lastPos + 1;
3975                final int boundTravel = boundPosFromFirst - 1;
3976                if (boundTravel < posTravel) {
3977                    viewTravelCount = boundTravel;
3978                    mMode = MOVE_DOWN_BOUND;
3979                } else {
3980                    viewTravelCount = posTravel;
3981                    mMode = MOVE_DOWN_POS;
3982                }
3983            } else {
3984                // Already on screen, nothing to do
3985                return;
3986            }
3987
3988            if (viewTravelCount > 0) {
3989                mScrollDuration = SCROLL_DURATION / viewTravelCount;
3990            } else {
3991                mScrollDuration = SCROLL_DURATION;
3992            }
3993            mTargetPos = position;
3994            mBoundPos = boundPosition;
3995            mLastSeenPos = INVALID_POSITION;
3996
3997            post(this);
3998        }
3999
4000        void startWithOffset(int position, int offset) {
4001            startWithOffset(position, offset, SCROLL_DURATION);
4002        }
4003
4004        void startWithOffset(int position, int offset, int duration) {
4005            stop();
4006
4007            mTargetPos = position;
4008            mOffsetFromTop = offset;
4009            mBoundPos = INVALID_POSITION;
4010            mLastSeenPos = INVALID_POSITION;
4011            mMode = MOVE_OFFSET;
4012
4013            final int firstPos = mFirstPosition;
4014            final int childCount = getChildCount();
4015            final int lastPos = firstPos + childCount - 1;
4016
4017            int viewTravelCount;
4018            if (position < firstPos) {
4019                viewTravelCount = firstPos - position;
4020            } else if (position > lastPos) {
4021                viewTravelCount = position - lastPos;
4022            } else {
4023                // On-screen, just scroll.
4024                final int targetTop = getChildAt(position - firstPos).getTop();
4025                smoothScrollBy(targetTop - offset, duration);
4026                return;
4027            }
4028
4029            // Estimate how many screens we should travel
4030            final float screenTravelCount = (float) viewTravelCount / childCount;
4031            mScrollDuration = screenTravelCount < 1 ? (int) (screenTravelCount * duration) :
4032                    (int) (duration / screenTravelCount);
4033            mLastSeenPos = INVALID_POSITION;
4034
4035            post(this);
4036        }
4037
4038        void stop() {
4039            removeCallbacks(this);
4040        }
4041
4042        public void run() {
4043            if (mTouchMode != TOUCH_MODE_FLING && mLastSeenPos != INVALID_POSITION) {
4044                return;
4045            }
4046
4047            final int listHeight = getHeight();
4048            final int firstPos = mFirstPosition;
4049
4050            switch (mMode) {
4051            case MOVE_DOWN_POS: {
4052                final int lastViewIndex = getChildCount() - 1;
4053                final int lastPos = firstPos + lastViewIndex;
4054
4055                if (lastViewIndex < 0) {
4056                    return;
4057                }
4058
4059                if (lastPos == mLastSeenPos) {
4060                    // No new views, let things keep going.
4061                    post(this);
4062                    return;
4063                }
4064
4065                final View lastView = getChildAt(lastViewIndex);
4066                final int lastViewHeight = lastView.getHeight();
4067                final int lastViewTop = lastView.getTop();
4068                final int lastViewPixelsShowing = listHeight - lastViewTop;
4069                final int extraScroll = lastPos < mItemCount - 1 ? mExtraScroll : mListPadding.bottom;
4070
4071                smoothScrollBy(lastViewHeight - lastViewPixelsShowing + extraScroll,
4072                        mScrollDuration);
4073
4074                mLastSeenPos = lastPos;
4075                if (lastPos < mTargetPos) {
4076                    post(this);
4077                }
4078                break;
4079            }
4080
4081            case MOVE_DOWN_BOUND: {
4082                final int nextViewIndex = 1;
4083                final int childCount = getChildCount();
4084
4085                if (firstPos == mBoundPos || childCount <= nextViewIndex
4086                        || firstPos + childCount >= mItemCount) {
4087                    return;
4088                }
4089                final int nextPos = firstPos + nextViewIndex;
4090
4091                if (nextPos == mLastSeenPos) {
4092                    // No new views, let things keep going.
4093                    post(this);
4094                    return;
4095                }
4096
4097                final View nextView = getChildAt(nextViewIndex);
4098                final int nextViewHeight = nextView.getHeight();
4099                final int nextViewTop = nextView.getTop();
4100                final int extraScroll = mExtraScroll;
4101                if (nextPos < mBoundPos) {
4102                    smoothScrollBy(Math.max(0, nextViewHeight + nextViewTop - extraScroll),
4103                            mScrollDuration);
4104
4105                    mLastSeenPos = nextPos;
4106
4107                    post(this);
4108                } else  {
4109                    if (nextViewTop > extraScroll) {
4110                        smoothScrollBy(nextViewTop - extraScroll, mScrollDuration);
4111                    }
4112                }
4113                break;
4114            }
4115
4116            case MOVE_UP_POS: {
4117                if (firstPos == mLastSeenPos) {
4118                    // No new views, let things keep going.
4119                    post(this);
4120                    return;
4121                }
4122
4123                final View firstView = getChildAt(0);
4124                if (firstView == null) {
4125                    return;
4126                }
4127                final int firstViewTop = firstView.getTop();
4128                final int extraScroll = firstPos > 0 ? mExtraScroll : mListPadding.top;
4129
4130                smoothScrollBy(firstViewTop - extraScroll, mScrollDuration);
4131
4132                mLastSeenPos = firstPos;
4133
4134                if (firstPos > mTargetPos) {
4135                    post(this);
4136                }
4137                break;
4138            }
4139
4140            case MOVE_UP_BOUND: {
4141                final int lastViewIndex = getChildCount() - 2;
4142                if (lastViewIndex < 0) {
4143                    return;
4144                }
4145                final int lastPos = firstPos + lastViewIndex;
4146
4147                if (lastPos == mLastSeenPos) {
4148                    // No new views, let things keep going.
4149                    post(this);
4150                    return;
4151                }
4152
4153                final View lastView = getChildAt(lastViewIndex);
4154                final int lastViewHeight = lastView.getHeight();
4155                final int lastViewTop = lastView.getTop();
4156                final int lastViewPixelsShowing = listHeight - lastViewTop;
4157                mLastSeenPos = lastPos;
4158                if (lastPos > mBoundPos) {
4159                    smoothScrollBy(-(lastViewPixelsShowing - mExtraScroll), mScrollDuration);
4160                    post(this);
4161                } else {
4162                    final int bottom = listHeight - mExtraScroll;
4163                    final int lastViewBottom = lastViewTop + lastViewHeight;
4164                    if (bottom > lastViewBottom) {
4165                        smoothScrollBy(-(bottom - lastViewBottom), mScrollDuration);
4166                    }
4167                }
4168                break;
4169            }
4170
4171            case MOVE_OFFSET: {
4172                if (mLastSeenPos == firstPos) {
4173                    // No new views, let things keep going.
4174                    post(this);
4175                    return;
4176                }
4177
4178                mLastSeenPos = firstPos;
4179
4180                final int childCount = getChildCount();
4181                final int position = mTargetPos;
4182                final int lastPos = firstPos + childCount - 1;
4183
4184                int viewTravelCount = 0;
4185                if (position < firstPos) {
4186                    viewTravelCount = firstPos - position + 1;
4187                } else if (position > lastPos) {
4188                    viewTravelCount = position - lastPos;
4189                }
4190
4191                // Estimate how many screens we should travel
4192                final float screenTravelCount = (float) viewTravelCount / childCount;
4193
4194                final float modifier = Math.min(Math.abs(screenTravelCount), 1.f);
4195                if (position < firstPos) {
4196                    smoothScrollBy((int) (-getHeight() * modifier), mScrollDuration);
4197                    post(this);
4198                } else if (position > lastPos) {
4199                    smoothScrollBy((int) (getHeight() * modifier), mScrollDuration);
4200                    post(this);
4201                } else {
4202                    // On-screen, just scroll.
4203                    final int targetTop = getChildAt(position - firstPos).getTop();
4204                    final int distance = targetTop - mOffsetFromTop;
4205                    smoothScrollBy(distance,
4206                            (int) (mScrollDuration * ((float) distance / getHeight())));
4207                }
4208                break;
4209            }
4210
4211            default:
4212                break;
4213            }
4214        }
4215    }
4216
4217    /**
4218     * The amount of friction applied to flings. The default value
4219     * is {@link ViewConfiguration#getScrollFriction}.
4220     *
4221     * @return A scalar dimensionless value representing the coefficient of
4222     *         friction.
4223     */
4224    public void setFriction(float friction) {
4225        if (mFlingRunnable == null) {
4226            mFlingRunnable = new FlingRunnable();
4227        }
4228        mFlingRunnable.mScroller.setFriction(friction);
4229    }
4230
4231    /**
4232     * Sets a scale factor for the fling velocity. The initial scale
4233     * factor is 1.0.
4234     *
4235     * @param scale The scale factor to multiply the velocity by.
4236     */
4237    public void setVelocityScale(float scale) {
4238        mVelocityScale = scale;
4239    }
4240
4241    /**
4242     * Smoothly scroll to the specified adapter position. The view will
4243     * scroll such that the indicated position is displayed.
4244     * @param position Scroll to this adapter position.
4245     */
4246    public void smoothScrollToPosition(int position) {
4247        if (mPositionScroller == null) {
4248            mPositionScroller = new PositionScroller();
4249        }
4250        mPositionScroller.start(position);
4251    }
4252
4253    /**
4254     * Smoothly scroll to the specified adapter position. The view will scroll
4255     * such that the indicated position is displayed <code>offset</code> pixels from
4256     * the top edge of the view. If this is impossible, (e.g. the offset would scroll
4257     * the first or last item beyond the boundaries of the list) it will get as close
4258     * as possible. The scroll will take <code>duration</code> milliseconds to complete.
4259     *
4260     * @param position Position to scroll to
4261     * @param offset Desired distance in pixels of <code>position</code> from the top
4262     *               of the view when scrolling is finished
4263     * @param duration Number of milliseconds to use for the scroll
4264     */
4265    public void smoothScrollToPositionFromTop(int position, int offset, int duration) {
4266        if (mPositionScroller == null) {
4267            mPositionScroller = new PositionScroller();
4268        }
4269        mPositionScroller.startWithOffset(position, offset, duration);
4270    }
4271
4272    /**
4273     * Smoothly scroll to the specified adapter position. The view will scroll
4274     * such that the indicated position is displayed <code>offset</code> pixels from
4275     * the top edge of the view. If this is impossible, (e.g. the offset would scroll
4276     * the first or last item beyond the boundaries of the list) it will get as close
4277     * as possible.
4278     *
4279     * @param position Position to scroll to
4280     * @param offset Desired distance in pixels of <code>position</code> from the top
4281     *               of the view when scrolling is finished
4282     */
4283    public void smoothScrollToPositionFromTop(int position, int offset) {
4284        if (mPositionScroller == null) {
4285            mPositionScroller = new PositionScroller();
4286        }
4287        mPositionScroller.startWithOffset(position, offset);
4288    }
4289
4290    /**
4291     * Smoothly scroll to the specified adapter position. The view will
4292     * scroll such that the indicated position is displayed, but it will
4293     * stop early if scrolling further would scroll boundPosition out of
4294     * view.
4295     * @param position Scroll to this adapter position.
4296     * @param boundPosition Do not scroll if it would move this adapter
4297     *          position out of view.
4298     */
4299    public void smoothScrollToPosition(int position, int boundPosition) {
4300        if (mPositionScroller == null) {
4301            mPositionScroller = new PositionScroller();
4302        }
4303        mPositionScroller.start(position, boundPosition);
4304    }
4305
4306    /**
4307     * Smoothly scroll by distance pixels over duration milliseconds.
4308     * @param distance Distance to scroll in pixels.
4309     * @param duration Duration of the scroll animation in milliseconds.
4310     */
4311    public void smoothScrollBy(int distance, int duration) {
4312        if (mFlingRunnable == null) {
4313            mFlingRunnable = new FlingRunnable();
4314        }
4315
4316        // No sense starting to scroll if we're not going anywhere
4317        final int firstPos = mFirstPosition;
4318        final int childCount = getChildCount();
4319        final int lastPos = firstPos + childCount;
4320        final int topLimit = getPaddingTop();
4321        final int bottomLimit = getHeight() - getPaddingBottom();
4322
4323        if (distance == 0 || mItemCount == 0 || childCount == 0 ||
4324                (firstPos == 0 && getChildAt(0).getTop() == topLimit && distance < 0) ||
4325                (lastPos == mItemCount - 1 &&
4326                        getChildAt(childCount - 1).getBottom() == bottomLimit && distance > 0)) {
4327            mFlingRunnable.endFling();
4328            if (mPositionScroller != null) {
4329                mPositionScroller.stop();
4330            }
4331        } else {
4332            reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
4333            mFlingRunnable.startScroll(distance, duration);
4334        }
4335    }
4336
4337    /**
4338     * Allows RemoteViews to scroll relatively to a position.
4339     */
4340    void smoothScrollByOffset(int position) {
4341        int index = -1;
4342        if (position < 0) {
4343            index = getFirstVisiblePosition();
4344        } else if (position > 0) {
4345            index = getLastVisiblePosition();
4346        }
4347
4348        if (index > -1) {
4349            View child = getChildAt(index - getFirstVisiblePosition());
4350            if (child != null) {
4351                Rect visibleRect = new Rect();
4352                if (child.getGlobalVisibleRect(visibleRect)) {
4353                    // the child is partially visible
4354                    int childRectArea = child.getWidth() * child.getHeight();
4355                    int visibleRectArea = visibleRect.width() * visibleRect.height();
4356                    float visibleArea = (visibleRectArea / (float) childRectArea);
4357                    final float visibleThreshold = 0.75f;
4358                    if ((position < 0) && (visibleArea < visibleThreshold)) {
4359                        // the top index is not perceivably visible so offset
4360                        // to account for showing that top index as well
4361                        ++index;
4362                    } else if ((position > 0) && (visibleArea < visibleThreshold)) {
4363                        // the bottom index is not perceivably visible so offset
4364                        // to account for showing that bottom index as well
4365                        --index;
4366                    }
4367                }
4368                smoothScrollToPosition(Math.max(0, Math.min(getCount(), index + position)));
4369            }
4370        }
4371    }
4372
4373    private void createScrollingCache() {
4374        if (mScrollingCacheEnabled && !mCachingStarted) {
4375            setChildrenDrawnWithCacheEnabled(true);
4376            setChildrenDrawingCacheEnabled(true);
4377            mCachingStarted = mCachingActive = true;
4378        }
4379    }
4380
4381    private void clearScrollingCache() {
4382        if (mClearScrollingCache == null) {
4383            mClearScrollingCache = new Runnable() {
4384                public void run() {
4385                    if (mCachingStarted) {
4386                        mCachingStarted = mCachingActive = false;
4387                        setChildrenDrawnWithCacheEnabled(false);
4388                        if ((mPersistentDrawingCache & PERSISTENT_SCROLLING_CACHE) == 0) {
4389                            setChildrenDrawingCacheEnabled(false);
4390                        }
4391                        if (!isAlwaysDrawnWithCacheEnabled()) {
4392                            invalidate();
4393                        }
4394                    }
4395                }
4396            };
4397        }
4398        post(mClearScrollingCache);
4399    }
4400
4401    /**
4402     * Track a motion scroll
4403     *
4404     * @param deltaY Amount to offset mMotionView. This is the accumulated delta since the motion
4405     *        began. Positive numbers mean the user's finger is moving down the screen.
4406     * @param incrementalDeltaY Change in deltaY from the previous event.
4407     * @return true if we're already at the beginning/end of the list and have nothing to do.
4408     */
4409    boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
4410        final int childCount = getChildCount();
4411        if (childCount == 0) {
4412            return true;
4413        }
4414
4415        final int firstTop = getChildAt(0).getTop();
4416        final int lastBottom = getChildAt(childCount - 1).getBottom();
4417
4418        final Rect listPadding = mListPadding;
4419
4420        // "effective padding" In this case is the amount of padding that affects
4421        // how much space should not be filled by items. If we don't clip to padding
4422        // there is no effective padding.
4423        int effectivePaddingTop = 0;
4424        int effectivePaddingBottom = 0;
4425        if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
4426            effectivePaddingTop = listPadding.top;
4427            effectivePaddingBottom = listPadding.bottom;
4428        }
4429
4430         // FIXME account for grid vertical spacing too?
4431        final int spaceAbove = effectivePaddingTop - firstTop;
4432        final int end = getHeight() - effectivePaddingBottom;
4433        final int spaceBelow = lastBottom - end;
4434
4435        final int height = getHeight() - mPaddingBottom - mPaddingTop;
4436        if (deltaY < 0) {
4437            deltaY = Math.max(-(height - 1), deltaY);
4438        } else {
4439            deltaY = Math.min(height - 1, deltaY);
4440        }
4441
4442        if (incrementalDeltaY < 0) {
4443            incrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY);
4444        } else {
4445            incrementalDeltaY = Math.min(height - 1, incrementalDeltaY);
4446        }
4447
4448        final int firstPosition = mFirstPosition;
4449
4450        // Update our guesses for where the first and last views are
4451        if (firstPosition == 0) {
4452            mFirstPositionDistanceGuess = firstTop - listPadding.top;
4453        } else {
4454            mFirstPositionDistanceGuess += incrementalDeltaY;
4455        }
4456        if (firstPosition + childCount == mItemCount) {
4457            mLastPositionDistanceGuess = lastBottom + listPadding.bottom;
4458        } else {
4459            mLastPositionDistanceGuess += incrementalDeltaY;
4460        }
4461
4462        final boolean cannotScrollDown = (firstPosition == 0 &&
4463                firstTop >= listPadding.top && incrementalDeltaY >= 0);
4464        final boolean cannotScrollUp = (firstPosition + childCount == mItemCount &&
4465                lastBottom <= getHeight() - listPadding.bottom && incrementalDeltaY <= 0);
4466
4467        if (cannotScrollDown || cannotScrollUp) {
4468            return incrementalDeltaY != 0;
4469        }
4470
4471        final boolean down = incrementalDeltaY < 0;
4472
4473        final boolean inTouchMode = isInTouchMode();
4474        if (inTouchMode) {
4475            hideSelector();
4476        }
4477
4478        final int headerViewsCount = getHeaderViewsCount();
4479        final int footerViewsStart = mItemCount - getFooterViewsCount();
4480
4481        int start = 0;
4482        int count = 0;
4483
4484        if (down) {
4485            int top = -incrementalDeltaY;
4486            if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
4487                top += listPadding.top;
4488            }
4489            for (int i = 0; i < childCount; i++) {
4490                final View child = getChildAt(i);
4491                if (child.getBottom() >= top) {
4492                    break;
4493                } else {
4494                    count++;
4495                    int position = firstPosition + i;
4496                    if (position >= headerViewsCount && position < footerViewsStart) {
4497                        mRecycler.addScrapView(child, position);
4498
4499                        if (ViewDebug.TRACE_RECYCLER) {
4500                            ViewDebug.trace(child,
4501                                    ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP,
4502                                    firstPosition + i, -1);
4503                        }
4504                    }
4505                }
4506            }
4507        } else {
4508            int bottom = getHeight() - incrementalDeltaY;
4509            if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
4510                bottom -= listPadding.bottom;
4511            }
4512            for (int i = childCount - 1; i >= 0; i--) {
4513                final View child = getChildAt(i);
4514                if (child.getTop() <= bottom) {
4515                    break;
4516                } else {
4517                    start = i;
4518                    count++;
4519                    int position = firstPosition + i;
4520                    if (position >= headerViewsCount && position < footerViewsStart) {
4521                        mRecycler.addScrapView(child, position);
4522
4523                        if (ViewDebug.TRACE_RECYCLER) {
4524                            ViewDebug.trace(child,
4525                                    ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP,
4526                                    firstPosition + i, -1);
4527                        }
4528                    }
4529                }
4530            }
4531        }
4532
4533        mMotionViewNewTop = mMotionViewOriginalTop + deltaY;
4534
4535        mBlockLayoutRequests = true;
4536
4537        if (count > 0) {
4538            detachViewsFromParent(start, count);
4539        }
4540        offsetChildrenTopAndBottom(incrementalDeltaY);
4541
4542        if (down) {
4543            mFirstPosition += count;
4544        }
4545
4546        invalidate();
4547
4548        final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
4549        if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
4550            fillGap(down);
4551        }
4552
4553        if (!inTouchMode && mSelectedPosition != INVALID_POSITION) {
4554            final int childIndex = mSelectedPosition - mFirstPosition;
4555            if (childIndex >= 0 && childIndex < getChildCount()) {
4556                positionSelector(mSelectedPosition, getChildAt(childIndex));
4557            }
4558        } else if (mSelectorPosition != INVALID_POSITION) {
4559            final int childIndex = mSelectorPosition - mFirstPosition;
4560            if (childIndex >= 0 && childIndex < getChildCount()) {
4561                positionSelector(INVALID_POSITION, getChildAt(childIndex));
4562            }
4563        } else {
4564            mSelectorRect.setEmpty();
4565        }
4566
4567        mBlockLayoutRequests = false;
4568
4569        invokeOnItemScrollListener();
4570        awakenScrollBars();
4571
4572        return false;
4573    }
4574
4575    /**
4576     * Returns the number of header views in the list. Header views are special views
4577     * at the top of the list that should not be recycled during a layout.
4578     *
4579     * @return The number of header views, 0 in the default implementation.
4580     */
4581    int getHeaderViewsCount() {
4582        return 0;
4583    }
4584
4585    /**
4586     * Returns the number of footer views in the list. Footer views are special views
4587     * at the bottom of the list that should not be recycled during a layout.
4588     *
4589     * @return The number of footer views, 0 in the default implementation.
4590     */
4591    int getFooterViewsCount() {
4592        return 0;
4593    }
4594
4595    /**
4596     * Fills the gap left open by a touch-scroll. During a touch scroll, children that
4597     * remain on screen are shifted and the other ones are discarded. The role of this
4598     * method is to fill the gap thus created by performing a partial layout in the
4599     * empty space.
4600     *
4601     * @param down true if the scroll is going down, false if it is going up
4602     */
4603    abstract void fillGap(boolean down);
4604
4605    void hideSelector() {
4606        if (mSelectedPosition != INVALID_POSITION) {
4607            if (mLayoutMode != LAYOUT_SPECIFIC) {
4608                mResurrectToPosition = mSelectedPosition;
4609            }
4610            if (mNextSelectedPosition >= 0 && mNextSelectedPosition != mSelectedPosition) {
4611                mResurrectToPosition = mNextSelectedPosition;
4612            }
4613            setSelectedPositionInt(INVALID_POSITION);
4614            setNextSelectedPositionInt(INVALID_POSITION);
4615            mSelectedTop = 0;
4616        }
4617    }
4618
4619    /**
4620     * @return A position to select. First we try mSelectedPosition. If that has been clobbered by
4621     * entering touch mode, we then try mResurrectToPosition. Values are pinned to the range
4622     * of items available in the adapter
4623     */
4624    int reconcileSelectedPosition() {
4625        int position = mSelectedPosition;
4626        if (position < 0) {
4627            position = mResurrectToPosition;
4628        }
4629        position = Math.max(0, position);
4630        position = Math.min(position, mItemCount - 1);
4631        return position;
4632    }
4633
4634    /**
4635     * Find the row closest to y. This row will be used as the motion row when scrolling
4636     *
4637     * @param y Where the user touched
4638     * @return The position of the first (or only) item in the row containing y
4639     */
4640    abstract int findMotionRow(int y);
4641
4642    /**
4643     * Find the row closest to y. This row will be used as the motion row when scrolling.
4644     *
4645     * @param y Where the user touched
4646     * @return The position of the first (or only) item in the row closest to y
4647     */
4648    int findClosestMotionRow(int y) {
4649        final int childCount = getChildCount();
4650        if (childCount == 0) {
4651            return INVALID_POSITION;
4652        }
4653
4654        final int motionRow = findMotionRow(y);
4655        return motionRow != INVALID_POSITION ? motionRow : mFirstPosition + childCount - 1;
4656    }
4657
4658    /**
4659     * Causes all the views to be rebuilt and redrawn.
4660     */
4661    public void invalidateViews() {
4662        mDataChanged = true;
4663        rememberSyncState();
4664        requestLayout();
4665        invalidate();
4666    }
4667
4668    /**
4669     * If there is a selection returns false.
4670     * Otherwise resurrects the selection and returns true if resurrected.
4671     */
4672    boolean resurrectSelectionIfNeeded() {
4673        if (mSelectedPosition < 0 && resurrectSelection()) {
4674            updateSelectorState();
4675            return true;
4676        }
4677        return false;
4678    }
4679
4680    /**
4681     * Makes the item at the supplied position selected.
4682     *
4683     * @param position the position of the new selection
4684     */
4685    abstract void setSelectionInt(int position);
4686
4687    /**
4688     * Attempt to bring the selection back if the user is switching from touch
4689     * to trackball mode
4690     * @return Whether selection was set to something.
4691     */
4692    boolean resurrectSelection() {
4693        final int childCount = getChildCount();
4694
4695        if (childCount <= 0) {
4696            return false;
4697        }
4698
4699        int selectedTop = 0;
4700        int selectedPos;
4701        int childrenTop = mListPadding.top;
4702        int childrenBottom = mBottom - mTop - mListPadding.bottom;
4703        final int firstPosition = mFirstPosition;
4704        final int toPosition = mResurrectToPosition;
4705        boolean down = true;
4706
4707        if (toPosition >= firstPosition && toPosition < firstPosition + childCount) {
4708            selectedPos = toPosition;
4709
4710            final View selected = getChildAt(selectedPos - mFirstPosition);
4711            selectedTop = selected.getTop();
4712            int selectedBottom = selected.getBottom();
4713
4714            // We are scrolled, don't get in the fade
4715            if (selectedTop < childrenTop) {
4716                selectedTop = childrenTop + getVerticalFadingEdgeLength();
4717            } else if (selectedBottom > childrenBottom) {
4718                selectedTop = childrenBottom - selected.getMeasuredHeight()
4719                        - getVerticalFadingEdgeLength();
4720            }
4721        } else {
4722            if (toPosition < firstPosition) {
4723                // Default to selecting whatever is first
4724                selectedPos = firstPosition;
4725                for (int i = 0; i < childCount; i++) {
4726                    final View v = getChildAt(i);
4727                    final int top = v.getTop();
4728
4729                    if (i == 0) {
4730                        // Remember the position of the first item
4731                        selectedTop = top;
4732                        // See if we are scrolled at all
4733                        if (firstPosition > 0 || top < childrenTop) {
4734                            // If we are scrolled, don't select anything that is
4735                            // in the fade region
4736                            childrenTop += getVerticalFadingEdgeLength();
4737                        }
4738                    }
4739                    if (top >= childrenTop) {
4740                        // Found a view whose top is fully visisble
4741                        selectedPos = firstPosition + i;
4742                        selectedTop = top;
4743                        break;
4744                    }
4745                }
4746            } else {
4747                final int itemCount = mItemCount;
4748                down = false;
4749                selectedPos = firstPosition + childCount - 1;
4750
4751                for (int i = childCount - 1; i >= 0; i--) {
4752                    final View v = getChildAt(i);
4753                    final int top = v.getTop();
4754                    final int bottom = v.getBottom();
4755
4756                    if (i == childCount - 1) {
4757                        selectedTop = top;
4758                        if (firstPosition + childCount < itemCount || bottom > childrenBottom) {
4759                            childrenBottom -= getVerticalFadingEdgeLength();
4760                        }
4761                    }
4762
4763                    if (bottom <= childrenBottom) {
4764                        selectedPos = firstPosition + i;
4765                        selectedTop = top;
4766                        break;
4767                    }
4768                }
4769            }
4770        }
4771
4772        mResurrectToPosition = INVALID_POSITION;
4773        removeCallbacks(mFlingRunnable);
4774        if (mPositionScroller != null) {
4775            mPositionScroller.stop();
4776        }
4777        mTouchMode = TOUCH_MODE_REST;
4778        clearScrollingCache();
4779        mSpecificTop = selectedTop;
4780        selectedPos = lookForSelectablePosition(selectedPos, down);
4781        if (selectedPos >= firstPosition && selectedPos <= getLastVisiblePosition()) {
4782            mLayoutMode = LAYOUT_SPECIFIC;
4783            updateSelectorState();
4784            setSelectionInt(selectedPos);
4785            invokeOnItemScrollListener();
4786        } else {
4787            selectedPos = INVALID_POSITION;
4788        }
4789        reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4790
4791        return selectedPos >= 0;
4792    }
4793
4794    @Override
4795    protected void handleDataChanged() {
4796        int count = mItemCount;
4797        int lastHandledItemCount = mLastHandledItemCount;
4798        mLastHandledItemCount = mItemCount;
4799        if (count > 0) {
4800
4801            int newPos;
4802
4803            int selectablePos;
4804
4805            // Find the row we are supposed to sync to
4806            if (mNeedSync) {
4807                // Update this first, since setNextSelectedPositionInt inspects it
4808                mNeedSync = false;
4809
4810                if (mTranscriptMode == TRANSCRIPT_MODE_ALWAYS_SCROLL) {
4811                    mLayoutMode = LAYOUT_FORCE_BOTTOM;
4812                    return;
4813                } else if (mTranscriptMode == TRANSCRIPT_MODE_NORMAL) {
4814                    if (mForceTranscriptScroll) {
4815                        mForceTranscriptScroll = false;
4816                        mLayoutMode = LAYOUT_FORCE_BOTTOM;
4817                        return;
4818                    }
4819                    final int childCount = getChildCount();
4820                    final int listBottom = getHeight() - getPaddingBottom();
4821                    final View lastChild = getChildAt(childCount - 1);
4822                    final int lastBottom = lastChild != null ? lastChild.getBottom() : listBottom;
4823                    if (mFirstPosition + childCount >= lastHandledItemCount &&
4824                            lastBottom <= listBottom) {
4825                        mLayoutMode = LAYOUT_FORCE_BOTTOM;
4826                        return;
4827                    }
4828                    // Something new came in and we didn't scroll; give the user a clue that
4829                    // there's something new.
4830                    awakenScrollBars();
4831                }
4832
4833                switch (mSyncMode) {
4834                case SYNC_SELECTED_POSITION:
4835                    if (isInTouchMode()) {
4836                        // We saved our state when not in touch mode. (We know this because
4837                        // mSyncMode is SYNC_SELECTED_POSITION.) Now we are trying to
4838                        // restore in touch mode. Just leave mSyncPosition as it is (possibly
4839                        // adjusting if the available range changed) and return.
4840                        mLayoutMode = LAYOUT_SYNC;
4841                        mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
4842
4843                        return;
4844                    } else {
4845                        // See if we can find a position in the new data with the same
4846                        // id as the old selection. This will change mSyncPosition.
4847                        newPos = findSyncPosition();
4848                        if (newPos >= 0) {
4849                            // Found it. Now verify that new selection is still selectable
4850                            selectablePos = lookForSelectablePosition(newPos, true);
4851                            if (selectablePos == newPos) {
4852                                // Same row id is selected
4853                                mSyncPosition = newPos;
4854
4855                                if (mSyncHeight == getHeight()) {
4856                                    // If we are at the same height as when we saved state, try
4857                                    // to restore the scroll position too.
4858                                    mLayoutMode = LAYOUT_SYNC;
4859                                } else {
4860                                    // We are not the same height as when the selection was saved, so
4861                                    // don't try to restore the exact position
4862                                    mLayoutMode = LAYOUT_SET_SELECTION;
4863                                }
4864
4865                                // Restore selection
4866                                setNextSelectedPositionInt(newPos);
4867                                return;
4868                            }
4869                        }
4870                    }
4871                    break;
4872                case SYNC_FIRST_POSITION:
4873                    // Leave mSyncPosition as it is -- just pin to available range
4874                    mLayoutMode = LAYOUT_SYNC;
4875                    mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
4876
4877                    return;
4878                }
4879            }
4880
4881            if (!isInTouchMode()) {
4882                // We couldn't find matching data -- try to use the same position
4883                newPos = getSelectedItemPosition();
4884
4885                // Pin position to the available range
4886                if (newPos >= count) {
4887                    newPos = count - 1;
4888                }
4889                if (newPos < 0) {
4890                    newPos = 0;
4891                }
4892
4893                // Make sure we select something selectable -- first look down
4894                selectablePos = lookForSelectablePosition(newPos, true);
4895
4896                if (selectablePos >= 0) {
4897                    setNextSelectedPositionInt(selectablePos);
4898                    return;
4899                } else {
4900                    // Looking down didn't work -- try looking up
4901                    selectablePos = lookForSelectablePosition(newPos, false);
4902                    if (selectablePos >= 0) {
4903                        setNextSelectedPositionInt(selectablePos);
4904                        return;
4905                    }
4906                }
4907            } else {
4908
4909                // We already know where we want to resurrect the selection
4910                if (mResurrectToPosition >= 0) {
4911                    return;
4912                }
4913            }
4914
4915        }
4916
4917        // Nothing is selected. Give up and reset everything.
4918        mLayoutMode = mStackFromBottom ? LAYOUT_FORCE_BOTTOM : LAYOUT_FORCE_TOP;
4919        mSelectedPosition = INVALID_POSITION;
4920        mSelectedRowId = INVALID_ROW_ID;
4921        mNextSelectedPosition = INVALID_POSITION;
4922        mNextSelectedRowId = INVALID_ROW_ID;
4923        mNeedSync = false;
4924        mSelectorPosition = INVALID_POSITION;
4925        checkSelectionChanged();
4926    }
4927
4928    @Override
4929    protected void onDisplayHint(int hint) {
4930        super.onDisplayHint(hint);
4931        switch (hint) {
4932            case INVISIBLE:
4933                if (mPopup != null && mPopup.isShowing()) {
4934                    dismissPopup();
4935                }
4936                break;
4937            case VISIBLE:
4938                if (mFiltered && mPopup != null && !mPopup.isShowing()) {
4939                    showPopup();
4940                }
4941                break;
4942        }
4943        mPopupHidden = hint == INVISIBLE;
4944    }
4945
4946    /**
4947     * Removes the filter window
4948     */
4949    private void dismissPopup() {
4950        if (mPopup != null) {
4951            mPopup.dismiss();
4952        }
4953    }
4954
4955    /**
4956     * Shows the filter window
4957     */
4958    private void showPopup() {
4959        // Make sure we have a window before showing the popup
4960        if (getWindowVisibility() == View.VISIBLE) {
4961            createTextFilter(true);
4962            positionPopup();
4963            // Make sure we get focus if we are showing the popup
4964            checkFocus();
4965        }
4966    }
4967
4968    private void positionPopup() {
4969        int screenHeight = getResources().getDisplayMetrics().heightPixels;
4970        final int[] xy = new int[2];
4971        getLocationOnScreen(xy);
4972        // TODO: The 20 below should come from the theme
4973        // TODO: And the gravity should be defined in the theme as well
4974        final int bottomGap = screenHeight - xy[1] - getHeight() + (int) (mDensityScale * 20);
4975        if (!mPopup.isShowing()) {
4976            mPopup.showAtLocation(this, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL,
4977                    xy[0], bottomGap);
4978        } else {
4979            mPopup.update(xy[0], bottomGap, -1, -1);
4980        }
4981    }
4982
4983    /**
4984     * What is the distance between the source and destination rectangles given the direction of
4985     * focus navigation between them? The direction basically helps figure out more quickly what is
4986     * self evident by the relationship between the rects...
4987     *
4988     * @param source the source rectangle
4989     * @param dest the destination rectangle
4990     * @param direction the direction
4991     * @return the distance between the rectangles
4992     */
4993    static int getDistance(Rect source, Rect dest, int direction) {
4994        int sX, sY; // source x, y
4995        int dX, dY; // dest x, y
4996        switch (direction) {
4997        case View.FOCUS_RIGHT:
4998            sX = source.right;
4999            sY = source.top + source.height() / 2;
5000            dX = dest.left;
5001            dY = dest.top + dest.height() / 2;
5002            break;
5003        case View.FOCUS_DOWN:
5004            sX = source.left + source.width() / 2;
5005            sY = source.bottom;
5006            dX = dest.left + dest.width() / 2;
5007            dY = dest.top;
5008            break;
5009        case View.FOCUS_LEFT:
5010            sX = source.left;
5011            sY = source.top + source.height() / 2;
5012            dX = dest.right;
5013            dY = dest.top + dest.height() / 2;
5014            break;
5015        case View.FOCUS_UP:
5016            sX = source.left + source.width() / 2;
5017            sY = source.top;
5018            dX = dest.left + dest.width() / 2;
5019            dY = dest.bottom;
5020            break;
5021        case View.FOCUS_FORWARD:
5022        case View.FOCUS_BACKWARD:
5023            sX = source.right + source.width() / 2;
5024            sY = source.top + source.height() / 2;
5025            dX = dest.left + dest.width() / 2;
5026            dY = dest.top + dest.height() / 2;
5027            break;
5028        default:
5029            throw new IllegalArgumentException("direction must be one of "
5030                    + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, "
5031                    + "FOCUS_FORWARD, FOCUS_BACKWARD}.");
5032        }
5033        int deltaX = dX - sX;
5034        int deltaY = dY - sY;
5035        return deltaY * deltaY + deltaX * deltaX;
5036    }
5037
5038    @Override
5039    protected boolean isInFilterMode() {
5040        return mFiltered;
5041    }
5042
5043    /**
5044     * Sends a key to the text filter window
5045     *
5046     * @param keyCode The keycode for the event
5047     * @param event The actual key event
5048     *
5049     * @return True if the text filter handled the event, false otherwise.
5050     */
5051    boolean sendToTextFilter(int keyCode, int count, KeyEvent event) {
5052        if (!acceptFilter()) {
5053            return false;
5054        }
5055
5056        boolean handled = false;
5057        boolean okToSend = true;
5058        switch (keyCode) {
5059        case KeyEvent.KEYCODE_DPAD_UP:
5060        case KeyEvent.KEYCODE_DPAD_DOWN:
5061        case KeyEvent.KEYCODE_DPAD_LEFT:
5062        case KeyEvent.KEYCODE_DPAD_RIGHT:
5063        case KeyEvent.KEYCODE_DPAD_CENTER:
5064        case KeyEvent.KEYCODE_ENTER:
5065            okToSend = false;
5066            break;
5067        case KeyEvent.KEYCODE_BACK:
5068            if (mFiltered && mPopup != null && mPopup.isShowing()) {
5069                if (event.getAction() == KeyEvent.ACTION_DOWN
5070                        && event.getRepeatCount() == 0) {
5071                    KeyEvent.DispatcherState state = getKeyDispatcherState();
5072                    if (state != null) {
5073                        state.startTracking(event, this);
5074                    }
5075                    handled = true;
5076                } else if (event.getAction() == KeyEvent.ACTION_UP
5077                        && event.isTracking() && !event.isCanceled()) {
5078                    handled = true;
5079                    mTextFilter.setText("");
5080                }
5081            }
5082            okToSend = false;
5083            break;
5084        case KeyEvent.KEYCODE_SPACE:
5085            // Only send spaces once we are filtered
5086            okToSend = mFiltered;
5087            break;
5088        }
5089
5090        if (okToSend) {
5091            createTextFilter(true);
5092
5093            KeyEvent forwardEvent = event;
5094            if (forwardEvent.getRepeatCount() > 0) {
5095                forwardEvent = KeyEvent.changeTimeRepeat(event, event.getEventTime(), 0);
5096            }
5097
5098            int action = event.getAction();
5099            switch (action) {
5100                case KeyEvent.ACTION_DOWN:
5101                    handled = mTextFilter.onKeyDown(keyCode, forwardEvent);
5102                    break;
5103
5104                case KeyEvent.ACTION_UP:
5105                    handled = mTextFilter.onKeyUp(keyCode, forwardEvent);
5106                    break;
5107
5108                case KeyEvent.ACTION_MULTIPLE:
5109                    handled = mTextFilter.onKeyMultiple(keyCode, count, event);
5110                    break;
5111            }
5112        }
5113        return handled;
5114    }
5115
5116    /**
5117     * Return an InputConnection for editing of the filter text.
5118     */
5119    @Override
5120    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
5121        if (isTextFilterEnabled()) {
5122            // XXX we need to have the text filter created, so we can get an
5123            // InputConnection to proxy to.  Unfortunately this means we pretty
5124            // much need to make it as soon as a list view gets focus.
5125            createTextFilter(false);
5126            if (mPublicInputConnection == null) {
5127                mDefInputConnection = new BaseInputConnection(this, false);
5128                mPublicInputConnection = new InputConnectionWrapper(
5129                        mTextFilter.onCreateInputConnection(outAttrs), true) {
5130                    @Override
5131                    public boolean reportFullscreenMode(boolean enabled) {
5132                        // Use our own input connection, since it is
5133                        // the "real" one the IME is talking with.
5134                        return mDefInputConnection.reportFullscreenMode(enabled);
5135                    }
5136
5137                    @Override
5138                    public boolean performEditorAction(int editorAction) {
5139                        // The editor is off in its own window; we need to be
5140                        // the one that does this.
5141                        if (editorAction == EditorInfo.IME_ACTION_DONE) {
5142                            InputMethodManager imm = (InputMethodManager)
5143                                    getContext().getSystemService(
5144                                            Context.INPUT_METHOD_SERVICE);
5145                            if (imm != null) {
5146                                imm.hideSoftInputFromWindow(getWindowToken(), 0);
5147                            }
5148                            return true;
5149                        }
5150                        return false;
5151                    }
5152
5153                    @Override
5154                    public boolean sendKeyEvent(KeyEvent event) {
5155                        // Use our own input connection, since the filter
5156                        // text view may not be shown in a window so has
5157                        // no ViewAncestor to dispatch events with.
5158                        return mDefInputConnection.sendKeyEvent(event);
5159                    }
5160                };
5161            }
5162            outAttrs.inputType = EditorInfo.TYPE_CLASS_TEXT
5163                    | EditorInfo.TYPE_TEXT_VARIATION_FILTER;
5164            outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE;
5165            return mPublicInputConnection;
5166        }
5167        return null;
5168    }
5169
5170    /**
5171     * For filtering we proxy an input connection to an internal text editor,
5172     * and this allows the proxying to happen.
5173     */
5174    @Override
5175    public boolean checkInputConnectionProxy(View view) {
5176        return view == mTextFilter;
5177    }
5178
5179    /**
5180     * Creates the window for the text filter and populates it with an EditText field;
5181     *
5182     * @param animateEntrance true if the window should appear with an animation
5183     */
5184    private void createTextFilter(boolean animateEntrance) {
5185        if (mPopup == null) {
5186            Context c = getContext();
5187            PopupWindow p = new PopupWindow(c);
5188            LayoutInflater layoutInflater = (LayoutInflater)
5189                    c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
5190            mTextFilter = (EditText) layoutInflater.inflate(
5191                    com.android.internal.R.layout.typing_filter, null);
5192            // For some reason setting this as the "real" input type changes
5193            // the text view in some way that it doesn't work, and I don't
5194            // want to figure out why this is.
5195            mTextFilter.setRawInputType(EditorInfo.TYPE_CLASS_TEXT
5196                    | EditorInfo.TYPE_TEXT_VARIATION_FILTER);
5197            mTextFilter.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI);
5198            mTextFilter.addTextChangedListener(this);
5199            p.setFocusable(false);
5200            p.setTouchable(false);
5201            p.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
5202            p.setContentView(mTextFilter);
5203            p.setWidth(LayoutParams.WRAP_CONTENT);
5204            p.setHeight(LayoutParams.WRAP_CONTENT);
5205            p.setBackgroundDrawable(null);
5206            mPopup = p;
5207            getViewTreeObserver().addOnGlobalLayoutListener(this);
5208            mGlobalLayoutListenerAddedFilter = true;
5209        }
5210        if (animateEntrance) {
5211            mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilter);
5212        } else {
5213            mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilterRestore);
5214        }
5215    }
5216
5217    /**
5218     * Clear the text filter.
5219     */
5220    public void clearTextFilter() {
5221        if (mFiltered) {
5222            mTextFilter.setText("");
5223            mFiltered = false;
5224            if (mPopup != null && mPopup.isShowing()) {
5225                dismissPopup();
5226            }
5227        }
5228    }
5229
5230    /**
5231     * Returns if the ListView currently has a text filter.
5232     */
5233    public boolean hasTextFilter() {
5234        return mFiltered;
5235    }
5236
5237    public void onGlobalLayout() {
5238        if (isShown()) {
5239            // Show the popup if we are filtered
5240            if (mFiltered && mPopup != null && !mPopup.isShowing() && !mPopupHidden) {
5241                showPopup();
5242            }
5243        } else {
5244            // Hide the popup when we are no longer visible
5245            if (mPopup != null && mPopup.isShowing()) {
5246                dismissPopup();
5247            }
5248        }
5249
5250    }
5251
5252    /**
5253     * For our text watcher that is associated with the text filter.  Does
5254     * nothing.
5255     */
5256    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
5257    }
5258
5259    /**
5260     * For our text watcher that is associated with the text filter. Performs
5261     * the actual filtering as the text changes, and takes care of hiding and
5262     * showing the popup displaying the currently entered filter text.
5263     */
5264    public void onTextChanged(CharSequence s, int start, int before, int count) {
5265        if (mPopup != null && isTextFilterEnabled()) {
5266            int length = s.length();
5267            boolean showing = mPopup.isShowing();
5268            if (!showing && length > 0) {
5269                // Show the filter popup if necessary
5270                showPopup();
5271                mFiltered = true;
5272            } else if (showing && length == 0) {
5273                // Remove the filter popup if the user has cleared all text
5274                dismissPopup();
5275                mFiltered = false;
5276            }
5277            if (mAdapter instanceof Filterable) {
5278                Filter f = ((Filterable) mAdapter).getFilter();
5279                // Filter should not be null when we reach this part
5280                if (f != null) {
5281                    f.filter(s, this);
5282                } else {
5283                    throw new IllegalStateException("You cannot call onTextChanged with a non "
5284                            + "filterable adapter");
5285                }
5286            }
5287        }
5288    }
5289
5290    /**
5291     * For our text watcher that is associated with the text filter.  Does
5292     * nothing.
5293     */
5294    public void afterTextChanged(Editable s) {
5295    }
5296
5297    public void onFilterComplete(int count) {
5298        if (mSelectedPosition < 0 && count > 0) {
5299            mResurrectToPosition = INVALID_POSITION;
5300            resurrectSelection();
5301        }
5302    }
5303
5304    @Override
5305    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
5306        return new LayoutParams(p);
5307    }
5308
5309    @Override
5310    public LayoutParams generateLayoutParams(AttributeSet attrs) {
5311        return new AbsListView.LayoutParams(getContext(), attrs);
5312    }
5313
5314    @Override
5315    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
5316        return p instanceof AbsListView.LayoutParams;
5317    }
5318
5319    /**
5320     * Puts the list or grid into transcript mode. In this mode the list or grid will always scroll
5321     * to the bottom to show new items.
5322     *
5323     * @param mode the transcript mode to set
5324     *
5325     * @see #TRANSCRIPT_MODE_DISABLED
5326     * @see #TRANSCRIPT_MODE_NORMAL
5327     * @see #TRANSCRIPT_MODE_ALWAYS_SCROLL
5328     */
5329    public void setTranscriptMode(int mode) {
5330        mTranscriptMode = mode;
5331    }
5332
5333    /**
5334     * Returns the current transcript mode.
5335     *
5336     * @return {@link #TRANSCRIPT_MODE_DISABLED}, {@link #TRANSCRIPT_MODE_NORMAL} or
5337     *         {@link #TRANSCRIPT_MODE_ALWAYS_SCROLL}
5338     */
5339    public int getTranscriptMode() {
5340        return mTranscriptMode;
5341    }
5342
5343    @Override
5344    public int getSolidColor() {
5345        return mCacheColorHint;
5346    }
5347
5348    /**
5349     * When set to a non-zero value, the cache color hint indicates that this list is always drawn
5350     * on top of a solid, single-color, opaque background.
5351     *
5352     * Zero means that what's behind this object is translucent (non solid) or is not made of a
5353     * single color. This hint will not affect any existing background drawable set on this view (
5354     * typically set via {@link #setBackgroundDrawable(Drawable)}).
5355     *
5356     * @param color The background color
5357     */
5358    public void setCacheColorHint(int color) {
5359        if (color != mCacheColorHint) {
5360            mCacheColorHint = color;
5361            int count = getChildCount();
5362            for (int i = 0; i < count; i++) {
5363                getChildAt(i).setDrawingCacheBackgroundColor(color);
5364            }
5365            mRecycler.setCacheColorHint(color);
5366        }
5367    }
5368
5369    /**
5370     * When set to a non-zero value, the cache color hint indicates that this list is always drawn
5371     * on top of a solid, single-color, opaque background
5372     *
5373     * @return The cache color hint
5374     */
5375    @ViewDebug.ExportedProperty(category = "drawing")
5376    public int getCacheColorHint() {
5377        return mCacheColorHint;
5378    }
5379
5380    /**
5381     * Move all views (excluding headers and footers) held by this AbsListView into the supplied
5382     * List. This includes views displayed on the screen as well as views stored in AbsListView's
5383     * internal view recycler.
5384     *
5385     * @param views A list into which to put the reclaimed views
5386     */
5387    public void reclaimViews(List<View> views) {
5388        int childCount = getChildCount();
5389        RecyclerListener listener = mRecycler.mRecyclerListener;
5390
5391        // Reclaim views on screen
5392        for (int i = 0; i < childCount; i++) {
5393            View child = getChildAt(i);
5394            AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
5395            // Don't reclaim header or footer views, or views that should be ignored
5396            if (lp != null && mRecycler.shouldRecycleViewType(lp.viewType)) {
5397                views.add(child);
5398                if (listener != null) {
5399                    // Pretend they went through the scrap heap
5400                    listener.onMovedToScrapHeap(child);
5401                }
5402            }
5403        }
5404        mRecycler.reclaimScrapViews(views);
5405        removeAllViewsInLayout();
5406    }
5407
5408    /**
5409     * @hide
5410     */
5411    @Override
5412    protected boolean onConsistencyCheck(int consistency) {
5413        boolean result = super.onConsistencyCheck(consistency);
5414
5415        final boolean checkLayout = (consistency & ViewDebug.CONSISTENCY_LAYOUT) != 0;
5416
5417        if (checkLayout) {
5418            // The active recycler must be empty
5419            final View[] activeViews = mRecycler.mActiveViews;
5420            int count = activeViews.length;
5421            for (int i = 0; i < count; i++) {
5422                if (activeViews[i] != null) {
5423                    result = false;
5424                    Log.d(ViewDebug.CONSISTENCY_LOG_TAG,
5425                            "AbsListView " + this + " has a view in its active recycler: " +
5426                                    activeViews[i]);
5427                }
5428            }
5429
5430            // All views in the recycler must NOT be on screen and must NOT have a parent
5431            final ArrayList<View> scrap = mRecycler.mCurrentScrap;
5432            if (!checkScrap(scrap)) result = false;
5433            final ArrayList<View>[] scraps = mRecycler.mScrapViews;
5434            count = scraps.length;
5435            for (int i = 0; i < count; i++) {
5436                if (!checkScrap(scraps[i])) result = false;
5437            }
5438        }
5439
5440        return result;
5441    }
5442
5443    private boolean checkScrap(ArrayList<View> scrap) {
5444        if (scrap == null) return true;
5445        boolean result = true;
5446
5447        final int count = scrap.size();
5448        for (int i = 0; i < count; i++) {
5449            final View view = scrap.get(i);
5450            if (view.getParent() != null) {
5451                result = false;
5452                Log.d(ViewDebug.CONSISTENCY_LOG_TAG, "AbsListView " + this +
5453                        " has a view in its scrap heap still attached to a parent: " + view);
5454            }
5455            if (indexOfChild(view) >= 0) {
5456                result = false;
5457                Log.d(ViewDebug.CONSISTENCY_LOG_TAG, "AbsListView " + this +
5458                        " has a view in its scrap heap that is also a direct child: " + view);
5459            }
5460        }
5461
5462        return result;
5463    }
5464
5465    private void finishGlows() {
5466        if (mEdgeGlowTop != null) {
5467            mEdgeGlowTop.finish();
5468            mEdgeGlowBottom.finish();
5469        }
5470    }
5471
5472    /**
5473     * Sets up this AbsListView to use a remote views adapter which connects to a RemoteViewsService
5474     * through the specified intent.
5475     * @param intent the intent used to identify the RemoteViewsService for the adapter to connect to.
5476     */
5477    public void setRemoteViewsAdapter(Intent intent) {
5478        // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
5479        // service handling the specified intent.
5480        if (mRemoteAdapter != null) {
5481            Intent.FilterComparison fcNew = new Intent.FilterComparison(intent);
5482            Intent.FilterComparison fcOld = new Intent.FilterComparison(
5483                    mRemoteAdapter.getRemoteViewsServiceIntent());
5484            if (fcNew.equals(fcOld)) {
5485                return;
5486            }
5487        }
5488        mDeferNotifyDataSetChanged = false;
5489        // Otherwise, create a new RemoteViewsAdapter for binding
5490        mRemoteAdapter = new RemoteViewsAdapter(getContext(), intent, this);
5491    }
5492
5493    /**
5494     * This defers a notifyDataSetChanged on the pending RemoteViewsAdapter if it has not
5495     * connected yet.
5496     */
5497    public void deferNotifyDataSetChanged() {
5498        mDeferNotifyDataSetChanged = true;
5499    }
5500
5501    /**
5502     * Called back when the adapter connects to the RemoteViewsService.
5503     */
5504    public boolean onRemoteAdapterConnected() {
5505        if (mRemoteAdapter != mAdapter) {
5506            setAdapter(mRemoteAdapter);
5507            if (mDeferNotifyDataSetChanged) {
5508                mRemoteAdapter.notifyDataSetChanged();
5509                mDeferNotifyDataSetChanged = false;
5510            }
5511            return false;
5512        } else if (mRemoteAdapter != null) {
5513            mRemoteAdapter.superNotifyDataSetChanged();
5514            return true;
5515        }
5516        return false;
5517    }
5518
5519    /**
5520     * Called back when the adapter disconnects from the RemoteViewsService.
5521     */
5522    public void onRemoteAdapterDisconnected() {
5523        // If the remote adapter disconnects, we keep it around
5524        // since the currently displayed items are still cached.
5525        // Further, we want the service to eventually reconnect
5526        // when necessary, as triggered by this view requesting
5527        // items from the Adapter.
5528    }
5529
5530    /**
5531     * Sets the recycler listener to be notified whenever a View is set aside in
5532     * the recycler for later reuse. This listener can be used to free resources
5533     * associated to the View.
5534     *
5535     * @param listener The recycler listener to be notified of views set aside
5536     *        in the recycler.
5537     *
5538     * @see android.widget.AbsListView.RecycleBin
5539     * @see android.widget.AbsListView.RecyclerListener
5540     */
5541    public void setRecyclerListener(RecyclerListener listener) {
5542        mRecycler.mRecyclerListener = listener;
5543    }
5544
5545    class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver {
5546        @Override
5547        public void onChanged() {
5548            super.onChanged();
5549            if (mFastScroller != null) {
5550                mFastScroller.onSectionsChanged();
5551            }
5552        }
5553
5554        @Override
5555        public void onInvalidated() {
5556            super.onInvalidated();
5557            if (mFastScroller != null) {
5558                mFastScroller.onSectionsChanged();
5559            }
5560        }
5561    }
5562
5563    /**
5564     * A MultiChoiceModeListener receives events for {@link AbsListView#CHOICE_MODE_MULTIPLE_MODAL}.
5565     * It acts as the {@link ActionMode.Callback} for the selection mode and also receives
5566     * {@link #onItemCheckedStateChanged(ActionMode, int, long, boolean)} events when the user
5567     * selects and deselects list items.
5568     */
5569    public interface MultiChoiceModeListener extends ActionMode.Callback {
5570        /**
5571         * Called when an item is checked or unchecked during selection mode.
5572         *
5573         * @param mode The {@link ActionMode} providing the selection mode
5574         * @param position Adapter position of the item that was checked or unchecked
5575         * @param id Adapter ID of the item that was checked or unchecked
5576         * @param checked <code>true</code> if the item is now checked, <code>false</code>
5577         *                if the item is now unchecked.
5578         */
5579        public void onItemCheckedStateChanged(ActionMode mode,
5580                int position, long id, boolean checked);
5581    }
5582
5583    class MultiChoiceModeWrapper implements MultiChoiceModeListener {
5584        private MultiChoiceModeListener mWrapped;
5585
5586        public void setWrapped(MultiChoiceModeListener wrapped) {
5587            mWrapped = wrapped;
5588        }
5589
5590        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
5591            if (mWrapped.onCreateActionMode(mode, menu)) {
5592                // Initialize checked graphic state?
5593                setLongClickable(false);
5594                return true;
5595            }
5596            return false;
5597        }
5598
5599        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
5600            return mWrapped.onPrepareActionMode(mode, menu);
5601        }
5602
5603        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
5604            return mWrapped.onActionItemClicked(mode, item);
5605        }
5606
5607        public void onDestroyActionMode(ActionMode mode) {
5608            mWrapped.onDestroyActionMode(mode);
5609            mChoiceActionMode = null;
5610
5611            // Ending selection mode means deselecting everything.
5612            clearChoices();
5613
5614            mDataChanged = true;
5615            rememberSyncState();
5616            requestLayout();
5617
5618            setLongClickable(true);
5619        }
5620
5621        public void onItemCheckedStateChanged(ActionMode mode,
5622                int position, long id, boolean checked) {
5623            mWrapped.onItemCheckedStateChanged(mode, position, id, checked);
5624
5625            // If there are no items selected we no longer need the selection mode.
5626            if (getCheckedItemCount() == 0) {
5627                mode.finish();
5628            }
5629        }
5630    }
5631
5632    /**
5633     * AbsListView extends LayoutParams to provide a place to hold the view type.
5634     */
5635    public static class LayoutParams extends ViewGroup.LayoutParams {
5636        /**
5637         * View type for this view, as returned by
5638         * {@link android.widget.Adapter#getItemViewType(int) }
5639         */
5640        @ViewDebug.ExportedProperty(category = "list", mapping = {
5641            @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_IGNORE, to = "ITEM_VIEW_TYPE_IGNORE"),
5642            @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_HEADER_OR_FOOTER, to = "ITEM_VIEW_TYPE_HEADER_OR_FOOTER")
5643        })
5644        int viewType;
5645
5646        /**
5647         * When this boolean is set, the view has been added to the AbsListView
5648         * at least once. It is used to know whether headers/footers have already
5649         * been added to the list view and whether they should be treated as
5650         * recycled views or not.
5651         */
5652        @ViewDebug.ExportedProperty(category = "list")
5653        boolean recycledHeaderFooter;
5654
5655        /**
5656         * When an AbsListView is measured with an AT_MOST measure spec, it needs
5657         * to obtain children views to measure itself. When doing so, the children
5658         * are not attached to the window, but put in the recycler which assumes
5659         * they've been attached before. Setting this flag will force the reused
5660         * view to be attached to the window rather than just attached to the
5661         * parent.
5662         */
5663        @ViewDebug.ExportedProperty(category = "list")
5664        boolean forceAdd;
5665
5666        /**
5667         * The position the view was removed from when pulled out of the
5668         * scrap heap.
5669         * @hide
5670         */
5671        int scrappedFromPosition;
5672
5673        public LayoutParams(Context c, AttributeSet attrs) {
5674            super(c, attrs);
5675        }
5676
5677        public LayoutParams(int w, int h) {
5678            super(w, h);
5679        }
5680
5681        public LayoutParams(int w, int h, int viewType) {
5682            super(w, h);
5683            this.viewType = viewType;
5684        }
5685
5686        public LayoutParams(ViewGroup.LayoutParams source) {
5687            super(source);
5688        }
5689    }
5690
5691    /**
5692     * A RecyclerListener is used to receive a notification whenever a View is placed
5693     * inside the RecycleBin's scrap heap. This listener is used to free resources
5694     * associated to Views placed in the RecycleBin.
5695     *
5696     * @see android.widget.AbsListView.RecycleBin
5697     * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
5698     */
5699    public static interface RecyclerListener {
5700        /**
5701         * Indicates that the specified View was moved into the recycler's scrap heap.
5702         * The view is not displayed on screen any more and any expensive resource
5703         * associated with the view should be discarded.
5704         *
5705         * @param view
5706         */
5707        void onMovedToScrapHeap(View view);
5708    }
5709
5710    /**
5711     * The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of
5712     * storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the
5713     * start of a layout. By construction, they are displaying current information. At the end of
5714     * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that
5715     * could potentially be used by the adapter to avoid allocating views unnecessarily.
5716     *
5717     * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
5718     * @see android.widget.AbsListView.RecyclerListener
5719     */
5720    class RecycleBin {
5721        private RecyclerListener mRecyclerListener;
5722
5723        /**
5724         * The position of the first view stored in mActiveViews.
5725         */
5726        private int mFirstActivePosition;
5727
5728        /**
5729         * Views that were on screen at the start of layout. This array is populated at the start of
5730         * layout, and at the end of layout all view in mActiveViews are moved to mScrapViews.
5731         * Views in mActiveViews represent a contiguous range of Views, with position of the first
5732         * view store in mFirstActivePosition.
5733         */
5734        private View[] mActiveViews = new View[0];
5735
5736        /**
5737         * Unsorted views that can be used by the adapter as a convert view.
5738         */
5739        private ArrayList<View>[] mScrapViews;
5740
5741        private int mViewTypeCount;
5742
5743        private ArrayList<View> mCurrentScrap;
5744
5745        public void setViewTypeCount(int viewTypeCount) {
5746            if (viewTypeCount < 1) {
5747                throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
5748            }
5749            //noinspection unchecked
5750            ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
5751            for (int i = 0; i < viewTypeCount; i++) {
5752                scrapViews[i] = new ArrayList<View>();
5753            }
5754            mViewTypeCount = viewTypeCount;
5755            mCurrentScrap = scrapViews[0];
5756            mScrapViews = scrapViews;
5757        }
5758
5759        public void markChildrenDirty() {
5760            if (mViewTypeCount == 1) {
5761                final ArrayList<View> scrap = mCurrentScrap;
5762                final int scrapCount = scrap.size();
5763                for (int i = 0; i < scrapCount; i++) {
5764                    scrap.get(i).forceLayout();
5765                }
5766            } else {
5767                final int typeCount = mViewTypeCount;
5768                for (int i = 0; i < typeCount; i++) {
5769                    final ArrayList<View> scrap = mScrapViews[i];
5770                    final int scrapCount = scrap.size();
5771                    for (int j = 0; j < scrapCount; j++) {
5772                        scrap.get(j).forceLayout();
5773                    }
5774                }
5775            }
5776        }
5777
5778        public boolean shouldRecycleViewType(int viewType) {
5779            return viewType >= 0;
5780        }
5781
5782        /**
5783         * Clears the scrap heap.
5784         */
5785        void clear() {
5786            if (mViewTypeCount == 1) {
5787                final ArrayList<View> scrap = mCurrentScrap;
5788                final int scrapCount = scrap.size();
5789                for (int i = 0; i < scrapCount; i++) {
5790                    removeDetachedView(scrap.remove(scrapCount - 1 - i), false);
5791                }
5792            } else {
5793                final int typeCount = mViewTypeCount;
5794                for (int i = 0; i < typeCount; i++) {
5795                    final ArrayList<View> scrap = mScrapViews[i];
5796                    final int scrapCount = scrap.size();
5797                    for (int j = 0; j < scrapCount; j++) {
5798                        removeDetachedView(scrap.remove(scrapCount - 1 - j), false);
5799                    }
5800                }
5801            }
5802        }
5803
5804        /**
5805         * Fill ActiveViews with all of the children of the AbsListView.
5806         *
5807         * @param childCount The minimum number of views mActiveViews should hold
5808         * @param firstActivePosition The position of the first view that will be stored in
5809         *        mActiveViews
5810         */
5811        void fillActiveViews(int childCount, int firstActivePosition) {
5812            if (mActiveViews.length < childCount) {
5813                mActiveViews = new View[childCount];
5814            }
5815            mFirstActivePosition = firstActivePosition;
5816
5817            final View[] activeViews = mActiveViews;
5818            for (int i = 0; i < childCount; i++) {
5819                View child = getChildAt(i);
5820                AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
5821                // Don't put header or footer views into the scrap heap
5822                if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
5823                    // Note:  We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views.
5824                    //        However, we will NOT place them into scrap views.
5825                    activeViews[i] = child;
5826                }
5827            }
5828        }
5829
5830        /**
5831         * Get the view corresponding to the specified position. The view will be removed from
5832         * mActiveViews if it is found.
5833         *
5834         * @param position The position to look up in mActiveViews
5835         * @return The view if it is found, null otherwise
5836         */
5837        View getActiveView(int position) {
5838            int index = position - mFirstActivePosition;
5839            final View[] activeViews = mActiveViews;
5840            if (index >=0 && index < activeViews.length) {
5841                final View match = activeViews[index];
5842                activeViews[index] = null;
5843                return match;
5844            }
5845            return null;
5846        }
5847
5848        /**
5849         * @return A view from the ScrapViews collection. These are unordered.
5850         */
5851        View getScrapView(int position) {
5852            if (mViewTypeCount == 1) {
5853                return retrieveFromScrap(mCurrentScrap, position);
5854            } else {
5855                int whichScrap = mAdapter.getItemViewType(position);
5856                if (whichScrap >= 0 && whichScrap < mScrapViews.length) {
5857                    return retrieveFromScrap(mScrapViews[whichScrap], position);
5858                }
5859            }
5860            return null;
5861        }
5862
5863        /**
5864         * Put a view into the ScapViews list. These views are unordered.
5865         *
5866         * @param scrap The view to add
5867         */
5868        void addScrapView(View scrap, int position) {
5869            AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
5870            if (lp == null) {
5871                return;
5872            }
5873
5874            // Don't put header or footer views or views that should be ignored
5875            // into the scrap heap
5876            int viewType = lp.viewType;
5877            if (!shouldRecycleViewType(viewType)) {
5878                if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
5879                    removeDetachedView(scrap, false);
5880                }
5881                return;
5882            }
5883
5884            lp.scrappedFromPosition = position;
5885
5886            if (mViewTypeCount == 1) {
5887                scrap.dispatchStartTemporaryDetach();
5888                mCurrentScrap.add(scrap);
5889            } else {
5890                scrap.dispatchStartTemporaryDetach();
5891                mScrapViews[viewType].add(scrap);
5892            }
5893
5894            if (mRecyclerListener != null) {
5895                mRecyclerListener.onMovedToScrapHeap(scrap);
5896            }
5897        }
5898
5899        /**
5900         * Move all views remaining in mActiveViews to mScrapViews.
5901         */
5902        void scrapActiveViews() {
5903            final View[] activeViews = mActiveViews;
5904            final boolean hasListener = mRecyclerListener != null;
5905            final boolean multipleScraps = mViewTypeCount > 1;
5906
5907            ArrayList<View> scrapViews = mCurrentScrap;
5908            final int count = activeViews.length;
5909            for (int i = count - 1; i >= 0; i--) {
5910                final View victim = activeViews[i];
5911                if (victim != null) {
5912                    final AbsListView.LayoutParams lp
5913                            = (AbsListView.LayoutParams) victim.getLayoutParams();
5914                    int whichScrap = lp.viewType;
5915
5916                    activeViews[i] = null;
5917
5918                    if (!shouldRecycleViewType(whichScrap)) {
5919                        // Do not move views that should be ignored
5920                        if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
5921                            removeDetachedView(victim, false);
5922                        }
5923                        continue;
5924                    }
5925
5926                    if (multipleScraps) {
5927                        scrapViews = mScrapViews[whichScrap];
5928                    }
5929                    victim.dispatchStartTemporaryDetach();
5930                    lp.scrappedFromPosition = mFirstActivePosition + i;
5931                    scrapViews.add(victim);
5932
5933                    if (hasListener) {
5934                        mRecyclerListener.onMovedToScrapHeap(victim);
5935                    }
5936
5937                    if (ViewDebug.TRACE_RECYCLER) {
5938                        ViewDebug.trace(victim,
5939                                ViewDebug.RecyclerTraceType.MOVE_FROM_ACTIVE_TO_SCRAP_HEAP,
5940                                mFirstActivePosition + i, -1);
5941                    }
5942                }
5943            }
5944
5945            pruneScrapViews();
5946        }
5947
5948        /**
5949         * Makes sure that the size of mScrapViews does not exceed the size of mActiveViews.
5950         * (This can happen if an adapter does not recycle its views).
5951         */
5952        private void pruneScrapViews() {
5953            final int maxViews = mActiveViews.length;
5954            final int viewTypeCount = mViewTypeCount;
5955            final ArrayList<View>[] scrapViews = mScrapViews;
5956            for (int i = 0; i < viewTypeCount; ++i) {
5957                final ArrayList<View> scrapPile = scrapViews[i];
5958                int size = scrapPile.size();
5959                final int extras = size - maxViews;
5960                size--;
5961                for (int j = 0; j < extras; j++) {
5962                    removeDetachedView(scrapPile.remove(size--), false);
5963                }
5964            }
5965        }
5966
5967        /**
5968         * Puts all views in the scrap heap into the supplied list.
5969         */
5970        void reclaimScrapViews(List<View> views) {
5971            if (mViewTypeCount == 1) {
5972                views.addAll(mCurrentScrap);
5973            } else {
5974                final int viewTypeCount = mViewTypeCount;
5975                final ArrayList<View>[] scrapViews = mScrapViews;
5976                for (int i = 0; i < viewTypeCount; ++i) {
5977                    final ArrayList<View> scrapPile = scrapViews[i];
5978                    views.addAll(scrapPile);
5979                }
5980            }
5981        }
5982
5983        /**
5984         * Updates the cache color hint of all known views.
5985         *
5986         * @param color The new cache color hint.
5987         */
5988        void setCacheColorHint(int color) {
5989            if (mViewTypeCount == 1) {
5990                final ArrayList<View> scrap = mCurrentScrap;
5991                final int scrapCount = scrap.size();
5992                for (int i = 0; i < scrapCount; i++) {
5993                    scrap.get(i).setDrawingCacheBackgroundColor(color);
5994                }
5995            } else {
5996                final int typeCount = mViewTypeCount;
5997                for (int i = 0; i < typeCount; i++) {
5998                    final ArrayList<View> scrap = mScrapViews[i];
5999                    final int scrapCount = scrap.size();
6000                    for (int j = 0; j < scrapCount; j++) {
6001                        scrap.get(j).setDrawingCacheBackgroundColor(color);
6002                    }
6003                }
6004            }
6005            // Just in case this is called during a layout pass
6006            final View[] activeViews = mActiveViews;
6007            final int count = activeViews.length;
6008            for (int i = 0; i < count; ++i) {
6009                final View victim = activeViews[i];
6010                if (victim != null) {
6011                    victim.setDrawingCacheBackgroundColor(color);
6012                }
6013            }
6014        }
6015    }
6016
6017    static View retrieveFromScrap(ArrayList<View> scrapViews, int position) {
6018        int size = scrapViews.size();
6019        if (size > 0) {
6020            // See if we still have a view for this position.
6021            for (int i=0; i<size; i++) {
6022                View view = scrapViews.get(i);
6023                if (((AbsListView.LayoutParams)view.getLayoutParams())
6024                        .scrappedFromPosition == position) {
6025                    scrapViews.remove(i);
6026                    return view;
6027                }
6028            }
6029            return scrapViews.remove(size - 1);
6030        } else {
6031            return null;
6032        }
6033    }
6034}
6035