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