AbsListView.java revision 090e0f08b9d8ea6328cd5210f1a27953fb0e4e67
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.res.TypedArray;
23import android.graphics.Canvas;
24import android.graphics.Rect;
25import android.graphics.drawable.Drawable;
26import android.graphics.drawable.TransitionDrawable;
27import android.os.Debug;
28import android.os.Handler;
29import android.os.Parcel;
30import android.os.Parcelable;
31import android.text.Editable;
32import android.text.TextUtils;
33import android.text.TextWatcher;
34import android.util.AttributeSet;
35import android.view.Gravity;
36import android.view.HapticFeedbackConstants;
37import android.view.KeyEvent;
38import android.view.LayoutInflater;
39import android.view.MotionEvent;
40import android.view.VelocityTracker;
41import android.view.View;
42import android.view.ViewConfiguration;
43import android.view.ViewDebug;
44import android.view.ViewGroup;
45import android.view.ViewTreeObserver;
46import android.view.ContextMenu.ContextMenuInfo;
47import android.view.inputmethod.BaseInputConnection;
48import android.view.inputmethod.EditorInfo;
49import android.view.inputmethod.InputConnection;
50import android.view.inputmethod.InputConnectionWrapper;
51import android.view.inputmethod.InputMethodManager;
52
53import java.util.ArrayList;
54import java.util.List;
55
56/**
57 * Base class that can be used to implement virtualized lists of items. A list does
58 * not have a spatial definition here. For instance, subclases of this class can
59 * display the content of the list in a grid, in a carousel, as stack, etc.
60 *
61 * @attr ref android.R.styleable#AbsListView_listSelector
62 * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop
63 * @attr ref android.R.styleable#AbsListView_stackFromBottom
64 * @attr ref android.R.styleable#AbsListView_scrollingCache
65 * @attr ref android.R.styleable#AbsListView_textFilterEnabled
66 * @attr ref android.R.styleable#AbsListView_transcriptMode
67 * @attr ref android.R.styleable#AbsListView_cacheColorHint
68 * @attr ref android.R.styleable#AbsListView_fastScrollEnabled
69 * @attr ref android.R.styleable#AbsListView_smoothScrollbar
70 */
71public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher,
72        ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener,
73        ViewTreeObserver.OnTouchModeChangeListener {
74
75    /**
76     * Disables the transcript mode.
77     *
78     * @see #setTranscriptMode(int)
79     */
80    public static final int TRANSCRIPT_MODE_DISABLED = 0;
81    /**
82     * The list will automatically scroll to the bottom when a data set change
83     * notification is received and only if the last item is already visible
84     * on screen.
85     *
86     * @see #setTranscriptMode(int)
87     */
88    public static final int TRANSCRIPT_MODE_NORMAL = 1;
89    /**
90     * The list will automatically scroll to the bottom, no matter what items
91     * are currently visible.
92     *
93     * @see #setTranscriptMode(int)
94     */
95    public static final int TRANSCRIPT_MODE_ALWAYS_SCROLL = 2;
96
97    /**
98     * Indicates that we are not in the middle of a touch gesture
99     */
100    static final int TOUCH_MODE_REST = -1;
101
102    /**
103     * Indicates we just received the touch event and we are waiting to see if the it is a tap or a
104     * scroll gesture.
105     */
106    static final int TOUCH_MODE_DOWN = 0;
107
108    /**
109     * Indicates the touch has been recognized as a tap and we are now waiting to see if the touch
110     * is a longpress
111     */
112    static final int TOUCH_MODE_TAP = 1;
113
114    /**
115     * Indicates we have waited for everything we can wait for, but the user's finger is still down
116     */
117    static final int TOUCH_MODE_DONE_WAITING = 2;
118
119    /**
120     * Indicates the touch gesture is a scroll
121     */
122    static final int TOUCH_MODE_SCROLL = 3;
123
124    /**
125     * Indicates the view is in the process of being flung
126     */
127    static final int TOUCH_MODE_FLING = 4;
128
129    /**
130     * Indicates the touch gesture is an overscroll - a scroll beyond the beginning or end.
131     */
132    static final int TOUCH_MODE_OVERSCROLL = 5;
133
134    /**
135     * Indicates the view is being flung outside of normal content bounds
136     * and will spring back.
137     */
138    static final int TOUCH_MODE_OVERFLING = 6;
139
140    /**
141     * Regular layout - usually an unsolicited layout from the view system
142     */
143    static final int LAYOUT_NORMAL = 0;
144
145    /**
146     * Show the first item
147     */
148    static final int LAYOUT_FORCE_TOP = 1;
149
150    /**
151     * Force the selected item to be on somewhere on the screen
152     */
153    static final int LAYOUT_SET_SELECTION = 2;
154
155    /**
156     * Show the last item
157     */
158    static final int LAYOUT_FORCE_BOTTOM = 3;
159
160    /**
161     * Make a mSelectedItem appear in a specific location and build the rest of
162     * the views from there. The top is specified by mSpecificTop.
163     */
164    static final int LAYOUT_SPECIFIC = 4;
165
166    /**
167     * Layout to sync as a result of a data change. Restore mSyncPosition to have its top
168     * at mSpecificTop
169     */
170    static final int LAYOUT_SYNC = 5;
171
172    /**
173     * Layout as a result of using the navigation keys
174     */
175    static final int LAYOUT_MOVE_SELECTION = 6;
176
177    /**
178     * Controls how the next layout will happen
179     */
180    int mLayoutMode = LAYOUT_NORMAL;
181
182    /**
183     * Should be used by subclasses to listen to changes in the dataset
184     */
185    AdapterDataSetObserver mDataSetObserver;
186
187    /**
188     * The adapter containing the data to be displayed by this view
189     */
190    ListAdapter mAdapter;
191
192    /**
193     * Indicates whether the list selector should be drawn on top of the children or behind
194     */
195    boolean mDrawSelectorOnTop = false;
196
197    /**
198     * The drawable used to draw the selector
199     */
200    Drawable mSelector;
201
202    /**
203     * Defines the selector's location and dimension at drawing time
204     */
205    Rect mSelectorRect = new Rect();
206
207    /**
208     * The data set used to store unused views that should be reused during the next layout
209     * to avoid creating new ones
210     */
211    final RecycleBin mRecycler = new RecycleBin();
212
213    /**
214     * The selection's left padding
215     */
216    int mSelectionLeftPadding = 0;
217
218    /**
219     * The selection's top padding
220     */
221    int mSelectionTopPadding = 0;
222
223    /**
224     * The selection's right padding
225     */
226    int mSelectionRightPadding = 0;
227
228    /**
229     * The selection's bottom padding
230     */
231    int mSelectionBottomPadding = 0;
232
233    /**
234     * This view's padding
235     */
236    Rect mListPadding = new Rect();
237
238    /**
239     * Subclasses must retain their measure spec from onMeasure() into this member
240     */
241    int mWidthMeasureSpec = 0;
242
243    /**
244     * The top scroll indicator
245     */
246    View mScrollUp;
247
248    /**
249     * The down scroll indicator
250     */
251    View mScrollDown;
252
253    /**
254     * When the view is scrolling, this flag is set to true to indicate subclasses that
255     * the drawing cache was enabled on the children
256     */
257    boolean mCachingStarted;
258
259    /**
260     * The position of the view that received the down motion event
261     */
262    int mMotionPosition;
263
264    /**
265     * The offset to the top of the mMotionPosition view when the down motion event was received
266     */
267    int mMotionViewOriginalTop;
268
269    /**
270     * The desired offset to the top of the mMotionPosition view after a scroll
271     */
272    int mMotionViewNewTop;
273
274    /**
275     * The X value associated with the the down motion event
276     */
277    int mMotionX;
278
279    /**
280     * The Y value associated with the the down motion event
281     */
282    int mMotionY;
283
284    /**
285     * One of TOUCH_MODE_REST, TOUCH_MODE_DOWN, TOUCH_MODE_TAP, TOUCH_MODE_SCROLL, or
286     * TOUCH_MODE_DONE_WAITING
287     */
288    int mTouchMode = TOUCH_MODE_REST;
289
290    /**
291     * Y value from on the previous motion event (if any)
292     */
293    int mLastY;
294
295    /**
296     * How far the finger moved before we started scrolling
297     */
298    int mMotionCorrection;
299
300    /**
301     * Determines speed during touch scrolling
302     */
303    private VelocityTracker mVelocityTracker;
304
305    /**
306     * Handles one frame of a fling
307     */
308    private FlingRunnable mFlingRunnable;
309
310    /**
311     * Handles scrolling between positions within the list.
312     */
313    private PositionScroller mPositionScroller;
314
315    /**
316     * The offset in pixels form the top of the AdapterView to the top
317     * of the currently selected view. Used to save and restore state.
318     */
319    int mSelectedTop = 0;
320
321    /**
322     * Indicates whether the list is stacked from the bottom edge or
323     * the top edge.
324     */
325    boolean mStackFromBottom;
326
327    /**
328     * When set to true, the list automatically discards the children's
329     * bitmap cache after scrolling.
330     */
331    boolean mScrollingCacheEnabled;
332
333    /**
334     * Whether or not to enable the fast scroll feature on this list
335     */
336    boolean mFastScrollEnabled;
337
338    /**
339     * Optional callback to notify client when scroll position has changed
340     */
341    private OnScrollListener mOnScrollListener;
342
343    /**
344     * Keeps track of our accessory window
345     */
346    PopupWindow mPopup;
347
348    /**
349     * Used with type filter window
350     */
351    EditText mTextFilter;
352
353    /**
354     * Indicates whether to use pixels-based or position-based scrollbar
355     * properties.
356     */
357    private boolean mSmoothScrollbarEnabled = true;
358
359    /**
360     * Indicates that this view supports filtering
361     */
362    private boolean mTextFilterEnabled;
363
364    /**
365     * Indicates that this view is currently displaying a filtered view of the data
366     */
367    private boolean mFiltered;
368
369    /**
370     * Rectangle used for hit testing children
371     */
372    private Rect mTouchFrame;
373
374    /**
375     * The position to resurrect the selected position to.
376     */
377    int mResurrectToPosition = INVALID_POSITION;
378
379    private ContextMenuInfo mContextMenuInfo = null;
380
381    /**
382     * Maximum distance to overscroll by
383     */
384    private int mOverscrollMax;
385
386    /**
387     * Content height divided by this is the overscroll limit.
388     */
389    private static final int OVERSCROLL_LIMIT_DIVISOR = 3;
390
391    /**
392     * Used to request a layout when we changed touch mode
393     */
394    private static final int TOUCH_MODE_UNKNOWN = -1;
395    private static final int TOUCH_MODE_ON = 0;
396    private static final int TOUCH_MODE_OFF = 1;
397
398    private int mLastTouchMode = TOUCH_MODE_UNKNOWN;
399
400    private static final boolean PROFILE_SCROLLING = false;
401    private boolean mScrollProfilingStarted = false;
402
403    private static final boolean PROFILE_FLINGING = false;
404    private boolean mFlingProfilingStarted = false;
405
406    /**
407     * The last CheckForLongPress runnable we posted, if any
408     */
409    private CheckForLongPress mPendingCheckForLongPress;
410
411    /**
412     * The last CheckForTap runnable we posted, if any
413     */
414    private Runnable mPendingCheckForTap;
415
416    /**
417     * The last CheckForKeyLongPress runnable we posted, if any
418     */
419    private CheckForKeyLongPress mPendingCheckForKeyLongPress;
420
421    /**
422     * Acts upon click
423     */
424    private AbsListView.PerformClick mPerformClick;
425
426    /**
427     * This view is in transcript mode -- it shows the bottom of the list when the data
428     * changes
429     */
430    private int mTranscriptMode;
431
432    /**
433     * Indicates that this list is always drawn on top of a solid, single-color, opaque
434     * background
435     */
436    private int mCacheColorHint;
437
438    /**
439     * The select child's view (from the adapter's getView) is enabled.
440     */
441    private boolean mIsChildViewEnabled;
442
443    /**
444     * The last scroll state reported to clients through {@link OnScrollListener}.
445     */
446    private int mLastScrollState = OnScrollListener.SCROLL_STATE_IDLE;
447
448    /**
449     * Helper object that renders and controls the fast scroll thumb.
450     */
451    private FastScroller mFastScroller;
452
453    private boolean mGlobalLayoutListenerAddedFilter;
454
455    private int mTouchSlop;
456    private float mDensityScale;
457
458    private InputConnection mDefInputConnection;
459    private InputConnectionWrapper mPublicInputConnection;
460
461    private Runnable mClearScrollingCache;
462    private int mMinimumVelocity;
463    private int mMaximumVelocity;
464
465    final boolean[] mIsScrap = new boolean[1];
466
467    // True when the popup should be hidden because of a call to
468    // dispatchDisplayHint()
469    private boolean mPopupHidden;
470
471    /**
472     * ID of the active pointer. This is used to retain consistency during
473     * drags/flings if multiple pointers are used.
474     */
475    private int mActivePointerId = INVALID_POINTER;
476
477    /**
478     * Sentinel value for no current active pointer.
479     * Used by {@link #mActivePointerId}.
480     */
481    private static final int INVALID_POINTER = -1;
482
483    /**
484     * Interface definition for a callback to be invoked when the list or grid
485     * has been scrolled.
486     */
487    public interface OnScrollListener {
488
489        /**
490         * The view is not scrolling. Note navigating the list using the trackball counts as
491         * being in the idle state since these transitions are not animated.
492         */
493        public static int SCROLL_STATE_IDLE = 0;
494
495        /**
496         * The user is scrolling using touch, and their finger is still on the screen
497         */
498        public static int SCROLL_STATE_TOUCH_SCROLL = 1;
499
500        /**
501         * The user had previously been scrolling using touch and had performed a fling. The
502         * animation is now coasting to a stop
503         */
504        public static int SCROLL_STATE_FLING = 2;
505
506        /**
507         * Callback method to be invoked while the list view or grid view is being scrolled. If the
508         * view is being scrolled, this method will be called before the next frame of the scroll is
509         * rendered. In particular, it will be called before any calls to
510         * {@link Adapter#getView(int, View, ViewGroup)}.
511         *
512         * @param view The view whose scroll state is being reported
513         *
514         * @param scrollState The current scroll state. One of {@link #SCROLL_STATE_IDLE},
515         * {@link #SCROLL_STATE_TOUCH_SCROLL} or {@link #SCROLL_STATE_IDLE}.
516         */
517        public void onScrollStateChanged(AbsListView view, int scrollState);
518
519        /**
520         * Callback method to be invoked when the list or grid has been scrolled. This will be
521         * called after the scroll has completed
522         * @param view The view whose scroll state is being reported
523         * @param firstVisibleItem the index of the first visible cell (ignore if
524         *        visibleItemCount == 0)
525         * @param visibleItemCount the number of visible cells
526         * @param totalItemCount the number of items in the list adaptor
527         */
528        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
529                int totalItemCount);
530    }
531
532    public AbsListView(Context context) {
533        super(context);
534        initAbsListView();
535
536        setVerticalScrollBarEnabled(true);
537        TypedArray a = context.obtainStyledAttributes(R.styleable.View);
538        initializeScrollbars(a);
539        a.recycle();
540    }
541
542    public AbsListView(Context context, AttributeSet attrs) {
543        this(context, attrs, com.android.internal.R.attr.absListViewStyle);
544    }
545
546    public AbsListView(Context context, AttributeSet attrs, int defStyle) {
547        super(context, attrs, defStyle);
548        initAbsListView();
549
550        TypedArray a = context.obtainStyledAttributes(attrs,
551                com.android.internal.R.styleable.AbsListView, defStyle, 0);
552
553        Drawable d = a.getDrawable(com.android.internal.R.styleable.AbsListView_listSelector);
554        if (d != null) {
555            setSelector(d);
556        }
557
558        mDrawSelectorOnTop = a.getBoolean(
559                com.android.internal.R.styleable.AbsListView_drawSelectorOnTop, false);
560
561        boolean stackFromBottom = a.getBoolean(R.styleable.AbsListView_stackFromBottom, false);
562        setStackFromBottom(stackFromBottom);
563
564        boolean scrollingCacheEnabled = a.getBoolean(R.styleable.AbsListView_scrollingCache, true);
565        setScrollingCacheEnabled(scrollingCacheEnabled);
566
567        boolean useTextFilter = a.getBoolean(R.styleable.AbsListView_textFilterEnabled, false);
568        setTextFilterEnabled(useTextFilter);
569
570        int transcriptMode = a.getInt(R.styleable.AbsListView_transcriptMode,
571                TRANSCRIPT_MODE_DISABLED);
572        setTranscriptMode(transcriptMode);
573
574        int color = a.getColor(R.styleable.AbsListView_cacheColorHint, 0);
575        setCacheColorHint(color);
576
577        boolean enableFastScroll = a.getBoolean(R.styleable.AbsListView_fastScrollEnabled, false);
578        setFastScrollEnabled(enableFastScroll);
579
580        boolean smoothScrollbar = a.getBoolean(R.styleable.AbsListView_smoothScrollbar, true);
581        setSmoothScrollbarEnabled(smoothScrollbar);
582
583        a.recycle();
584    }
585
586    private void initAbsListView() {
587        // Setting focusable in touch mode will set the focusable property to true
588        setClickable(true);
589        setFocusableInTouchMode(true);
590        setWillNotDraw(false);
591        setAlwaysDrawnWithCacheEnabled(false);
592        setScrollingCacheEnabled(true);
593
594        final ViewConfiguration configuration = ViewConfiguration.get(mContext);
595        mTouchSlop = configuration.getScaledTouchSlop();
596        mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
597        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
598        mDensityScale = getContext().getResources().getDisplayMetrics().density;
599    }
600
601    /**
602     * Enables fast scrolling by letting the user quickly scroll through lists by
603     * dragging the fast scroll thumb. The adapter attached to the list may want
604     * to implement {@link SectionIndexer} if it wishes to display alphabet preview and
605     * jump between sections of the list.
606     * @see SectionIndexer
607     * @see #isFastScrollEnabled()
608     * @param enabled whether or not to enable fast scrolling
609     */
610    public void setFastScrollEnabled(boolean enabled) {
611        mFastScrollEnabled = enabled;
612        if (enabled) {
613            if (mFastScroller == null) {
614                mFastScroller = new FastScroller(getContext(), this);
615            }
616        } else {
617            if (mFastScroller != null) {
618                mFastScroller.stop();
619                mFastScroller = null;
620            }
621        }
622    }
623
624    /**
625     * Returns the current state of the fast scroll feature.
626     * @see #setFastScrollEnabled(boolean)
627     * @return true if fast scroll is enabled, false otherwise
628     */
629    @ViewDebug.ExportedProperty
630    public boolean isFastScrollEnabled() {
631        return mFastScrollEnabled;
632    }
633
634    /**
635     * If fast scroll is visible, then don't draw the vertical scrollbar.
636     * @hide
637     */
638    @Override
639    protected boolean isVerticalScrollBarHidden() {
640        return mFastScroller != null && mFastScroller.isVisible();
641    }
642
643    /**
644     * When smooth scrollbar is enabled, the position and size of the scrollbar thumb
645     * is computed based on the number of visible pixels in the visible items. This
646     * however assumes that all list items have the same height. If you use a list in
647     * which items have different heights, the scrollbar will change appearance as the
648     * user scrolls through the list. To avoid this issue, you need to disable this
649     * property.
650     *
651     * When smooth scrollbar is disabled, the position and size of the scrollbar thumb
652     * is based solely on the number of items in the adapter and the position of the
653     * visible items inside the adapter. This provides a stable scrollbar as the user
654     * navigates through a list of items with varying heights.
655     *
656     * @param enabled Whether or not to enable smooth scrollbar.
657     *
658     * @see #setSmoothScrollbarEnabled(boolean)
659     * @attr ref android.R.styleable#AbsListView_smoothScrollbar
660     */
661    public void setSmoothScrollbarEnabled(boolean enabled) {
662        mSmoothScrollbarEnabled = enabled;
663    }
664
665    /**
666     * Returns the current state of the fast scroll feature.
667     *
668     * @return True if smooth scrollbar is enabled is enabled, false otherwise.
669     *
670     * @see #setSmoothScrollbarEnabled(boolean)
671     */
672    @ViewDebug.ExportedProperty
673    public boolean isSmoothScrollbarEnabled() {
674        return mSmoothScrollbarEnabled;
675    }
676
677    /**
678     * Set the listener that will receive notifications every time the list scrolls.
679     *
680     * @param l the scroll listener
681     */
682    public void setOnScrollListener(OnScrollListener l) {
683        mOnScrollListener = l;
684        invokeOnItemScrollListener();
685    }
686
687    /**
688     * Notify our scroll listener (if there is one) of a change in scroll state
689     */
690    void invokeOnItemScrollListener() {
691        if (mFastScroller != null) {
692            mFastScroller.onScroll(this, mFirstPosition, getChildCount(), mItemCount);
693        }
694        if (mOnScrollListener != null) {
695            mOnScrollListener.onScroll(this, mFirstPosition, getChildCount(), mItemCount);
696        }
697    }
698
699    /**
700     * Indicates whether the children's drawing cache is used during a scroll.
701     * By default, the drawing cache is enabled but this will consume more memory.
702     *
703     * @return true if the scrolling cache is enabled, false otherwise
704     *
705     * @see #setScrollingCacheEnabled(boolean)
706     * @see View#setDrawingCacheEnabled(boolean)
707     */
708    @ViewDebug.ExportedProperty
709    public boolean isScrollingCacheEnabled() {
710        return mScrollingCacheEnabled;
711    }
712
713    /**
714     * Enables or disables the children's drawing cache during a scroll.
715     * By default, the drawing cache is enabled but this will use more memory.
716     *
717     * When the scrolling cache is enabled, the caches are kept after the
718     * first scrolling. You can manually clear the cache by calling
719     * {@link android.view.ViewGroup#setChildrenDrawingCacheEnabled(boolean)}.
720     *
721     * @param enabled true to enable the scroll cache, false otherwise
722     *
723     * @see #isScrollingCacheEnabled()
724     * @see View#setDrawingCacheEnabled(boolean)
725     */
726    public void setScrollingCacheEnabled(boolean enabled) {
727        if (mScrollingCacheEnabled && !enabled) {
728            clearScrollingCache();
729        }
730        mScrollingCacheEnabled = enabled;
731    }
732
733    /**
734     * Enables or disables the type filter window. If enabled, typing when
735     * this view has focus will filter the children to match the users input.
736     * Note that the {@link Adapter} used by this view must implement the
737     * {@link Filterable} interface.
738     *
739     * @param textFilterEnabled true to enable type filtering, false otherwise
740     *
741     * @see Filterable
742     */
743    public void setTextFilterEnabled(boolean textFilterEnabled) {
744        mTextFilterEnabled = textFilterEnabled;
745    }
746
747    /**
748     * Indicates whether type filtering is enabled for this view
749     *
750     * @return true if type filtering is enabled, false otherwise
751     *
752     * @see #setTextFilterEnabled(boolean)
753     * @see Filterable
754     */
755    @ViewDebug.ExportedProperty
756    public boolean isTextFilterEnabled() {
757        return mTextFilterEnabled;
758    }
759
760    @Override
761    public void getFocusedRect(Rect r) {
762        View view = getSelectedView();
763        if (view != null && view.getParent() == this) {
764            // the focused rectangle of the selected view offset into the
765            // coordinate space of this view.
766            view.getFocusedRect(r);
767            offsetDescendantRectToMyCoords(view, r);
768        } else {
769            // otherwise, just the norm
770            super.getFocusedRect(r);
771        }
772    }
773
774    private void useDefaultSelector() {
775        setSelector(getResources().getDrawable(
776                com.android.internal.R.drawable.list_selector_background));
777    }
778
779    /**
780     * Indicates whether the content of this view is pinned to, or stacked from,
781     * the bottom edge.
782     *
783     * @return true if the content is stacked from the bottom edge, false otherwise
784     */
785    @ViewDebug.ExportedProperty
786    public boolean isStackFromBottom() {
787        return mStackFromBottom;
788    }
789
790    /**
791     * When stack from bottom is set to true, the list fills its content starting from
792     * the bottom of the view.
793     *
794     * @param stackFromBottom true to pin the view's content to the bottom edge,
795     *        false to pin the view's content to the top edge
796     */
797    public void setStackFromBottom(boolean stackFromBottom) {
798        if (mStackFromBottom != stackFromBottom) {
799            mStackFromBottom = stackFromBottom;
800            requestLayoutIfNecessary();
801        }
802    }
803
804    void requestLayoutIfNecessary() {
805        if (getChildCount() > 0) {
806            resetList();
807            requestLayout();
808            invalidate();
809        }
810    }
811
812    static class SavedState extends BaseSavedState {
813        long selectedId;
814        long firstId;
815        int viewTop;
816        int position;
817        int height;
818        String filter;
819
820        /**
821         * Constructor called from {@link AbsListView#onSaveInstanceState()}
822         */
823        SavedState(Parcelable superState) {
824            super(superState);
825        }
826
827        /**
828         * Constructor called from {@link #CREATOR}
829         */
830        private SavedState(Parcel in) {
831            super(in);
832            selectedId = in.readLong();
833            firstId = in.readLong();
834            viewTop = in.readInt();
835            position = in.readInt();
836            height = in.readInt();
837            filter = in.readString();
838        }
839
840        @Override
841        public void writeToParcel(Parcel out, int flags) {
842            super.writeToParcel(out, flags);
843            out.writeLong(selectedId);
844            out.writeLong(firstId);
845            out.writeInt(viewTop);
846            out.writeInt(position);
847            out.writeInt(height);
848            out.writeString(filter);
849        }
850
851        @Override
852        public String toString() {
853            return "AbsListView.SavedState{"
854                    + Integer.toHexString(System.identityHashCode(this))
855                    + " selectedId=" + selectedId
856                    + " firstId=" + firstId
857                    + " viewTop=" + viewTop
858                    + " position=" + position
859                    + " height=" + height
860                    + " filter=" + filter + "}";
861        }
862
863        public static final Parcelable.Creator<SavedState> CREATOR
864                = new Parcelable.Creator<SavedState>() {
865            public SavedState createFromParcel(Parcel in) {
866                return new SavedState(in);
867            }
868
869            public SavedState[] newArray(int size) {
870                return new SavedState[size];
871            }
872        };
873    }
874
875    @Override
876    public Parcelable onSaveInstanceState() {
877        /*
878         * This doesn't really make sense as the place to dismiss the
879         * popups, but there don't seem to be any other useful hooks
880         * that happen early enough to keep from getting complaints
881         * about having leaked the window.
882         */
883        dismissPopup();
884
885        Parcelable superState = super.onSaveInstanceState();
886
887        SavedState ss = new SavedState(superState);
888
889        boolean haveChildren = getChildCount() > 0;
890        long selectedId = getSelectedItemId();
891        ss.selectedId = selectedId;
892        ss.height = getHeight();
893
894        if (selectedId >= 0) {
895            // Remember the selection
896            ss.viewTop = mSelectedTop;
897            ss.position = getSelectedItemPosition();
898            ss.firstId = INVALID_POSITION;
899        } else {
900            if (haveChildren) {
901                // Remember the position of the first child
902                View v = getChildAt(0);
903                ss.viewTop = v.getTop();
904                ss.position = mFirstPosition;
905                ss.firstId = mAdapter.getItemId(mFirstPosition);
906            } else {
907                ss.viewTop = 0;
908                ss.firstId = INVALID_POSITION;
909                ss.position = 0;
910            }
911        }
912
913        ss.filter = null;
914        if (mFiltered) {
915            final EditText textFilter = mTextFilter;
916            if (textFilter != null) {
917                Editable filterText = textFilter.getText();
918                if (filterText != null) {
919                    ss.filter = filterText.toString();
920                }
921            }
922        }
923
924        return ss;
925    }
926
927    @Override
928    public void onRestoreInstanceState(Parcelable state) {
929        SavedState ss = (SavedState) state;
930
931        super.onRestoreInstanceState(ss.getSuperState());
932        mDataChanged = true;
933
934        mSyncHeight = ss.height;
935
936        if (ss.selectedId >= 0) {
937            mNeedSync = true;
938            mSyncRowId = ss.selectedId;
939            mSyncPosition = ss.position;
940            mSpecificTop = ss.viewTop;
941            mSyncMode = SYNC_SELECTED_POSITION;
942        } else if (ss.firstId >= 0) {
943            setSelectedPositionInt(INVALID_POSITION);
944            // Do this before setting mNeedSync since setNextSelectedPosition looks at mNeedSync
945            setNextSelectedPositionInt(INVALID_POSITION);
946            mNeedSync = true;
947            mSyncRowId = ss.firstId;
948            mSyncPosition = ss.position;
949            mSpecificTop = ss.viewTop;
950            mSyncMode = SYNC_FIRST_POSITION;
951        }
952
953        setFilterText(ss.filter);
954
955        requestLayout();
956    }
957
958    private boolean acceptFilter() {
959        return mTextFilterEnabled && getAdapter() instanceof Filterable &&
960                ((Filterable) getAdapter()).getFilter() != null;
961    }
962
963    /**
964     * Sets the initial value for the text filter.
965     * @param filterText The text to use for the filter.
966     *
967     * @see #setTextFilterEnabled
968     */
969    public void setFilterText(String filterText) {
970        // TODO: Should we check for acceptFilter()?
971        if (mTextFilterEnabled && !TextUtils.isEmpty(filterText)) {
972            createTextFilter(false);
973            // This is going to call our listener onTextChanged, but we might not
974            // be ready to bring up a window yet
975            mTextFilter.setText(filterText);
976            mTextFilter.setSelection(filterText.length());
977            if (mAdapter instanceof Filterable) {
978                // if mPopup is non-null, then onTextChanged will do the filtering
979                if (mPopup == null) {
980                    Filter f = ((Filterable) mAdapter).getFilter();
981                    f.filter(filterText);
982                }
983                // Set filtered to true so we will display the filter window when our main
984                // window is ready
985                mFiltered = true;
986                mDataSetObserver.clearSavedState();
987            }
988        }
989    }
990
991    /**
992     * Returns the list's text filter, if available.
993     * @return the list's text filter or null if filtering isn't enabled
994     */
995    public CharSequence getTextFilter() {
996        if (mTextFilterEnabled && mTextFilter != null) {
997            return mTextFilter.getText();
998        }
999        return null;
1000    }
1001
1002    @Override
1003    protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
1004        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
1005        if (gainFocus && mSelectedPosition < 0 && !isInTouchMode()) {
1006            resurrectSelection();
1007        }
1008    }
1009
1010    @Override
1011    public void requestLayout() {
1012        if (!mBlockLayoutRequests && !mInLayout) {
1013            super.requestLayout();
1014        }
1015    }
1016
1017    /**
1018     * The list is empty. Clear everything out.
1019     */
1020    void resetList() {
1021        removeAllViewsInLayout();
1022        mFirstPosition = 0;
1023        mDataChanged = false;
1024        mNeedSync = false;
1025        mOldSelectedPosition = INVALID_POSITION;
1026        mOldSelectedRowId = INVALID_ROW_ID;
1027        setSelectedPositionInt(INVALID_POSITION);
1028        setNextSelectedPositionInt(INVALID_POSITION);
1029        mSelectedTop = 0;
1030        mSelectorRect.setEmpty();
1031        invalidate();
1032    }
1033
1034    @Override
1035    protected int computeVerticalScrollExtent() {
1036        final int count = getChildCount();
1037        if (count > 0) {
1038            if (mSmoothScrollbarEnabled) {
1039                int extent = count * 100;
1040
1041                View view = getChildAt(0);
1042                final int top = view.getTop();
1043                int height = view.getHeight();
1044                if (height > 0) {
1045                    extent += (top * 100) / height;
1046                }
1047
1048                view = getChildAt(count - 1);
1049                final int bottom = view.getBottom();
1050                height = view.getHeight();
1051                if (height > 0) {
1052                    extent -= ((bottom - getHeight()) * 100) / height;
1053                }
1054
1055                return extent;
1056            } else {
1057                return 1;
1058            }
1059        }
1060        return 0;
1061    }
1062
1063    @Override
1064    protected int computeVerticalScrollOffset() {
1065        final int firstPosition = mFirstPosition;
1066        final int childCount = getChildCount();
1067        if (firstPosition >= 0 && childCount > 0) {
1068            if (mSmoothScrollbarEnabled) {
1069                final View view = getChildAt(0);
1070                final int top = view.getTop();
1071                int height = view.getHeight();
1072                if (height > 0) {
1073                    return Math.max(firstPosition * 100 - (top * 100) / height +
1074                            (int)((float)mScrollY / getHeight() * mItemCount * 100), 0);
1075                }
1076            } else {
1077                int index;
1078                final int count = mItemCount;
1079                if (firstPosition == 0) {
1080                    index = 0;
1081                } else if (firstPosition + childCount == count) {
1082                    index = count;
1083                } else {
1084                    index = firstPosition + childCount / 2;
1085                }
1086                return (int) (firstPosition + childCount * (index / (float) count));
1087            }
1088        }
1089        return 0;
1090    }
1091
1092    @Override
1093    protected int computeVerticalScrollRange() {
1094        int result;
1095        if (mSmoothScrollbarEnabled) {
1096            result = Math.max(mItemCount * 100, 0);
1097            if (mScrollY != 0) {
1098                // Compensate for overscroll
1099                result += Math.abs((int) ((float) mScrollY / getHeight() * mItemCount * 100));
1100            }
1101        } else {
1102            result = mItemCount;
1103        }
1104        return result;
1105    }
1106
1107    @Override
1108    protected float getTopFadingEdgeStrength() {
1109        final int count = getChildCount();
1110        final float fadeEdge = super.getTopFadingEdgeStrength();
1111        if (count == 0) {
1112            return fadeEdge;
1113        } else {
1114            if (mFirstPosition > 0) {
1115                return 1.0f;
1116            }
1117
1118            final int top = getChildAt(0).getTop();
1119            final float fadeLength = (float) getVerticalFadingEdgeLength();
1120            return top < mPaddingTop ? (float) -(top - mPaddingTop) / fadeLength : fadeEdge;
1121        }
1122    }
1123
1124    @Override
1125    protected float getBottomFadingEdgeStrength() {
1126        final int count = getChildCount();
1127        final float fadeEdge = super.getBottomFadingEdgeStrength();
1128        if (count == 0) {
1129            return fadeEdge;
1130        } else {
1131            if (mFirstPosition + count - 1 < mItemCount - 1) {
1132                return 1.0f;
1133            }
1134
1135            final int bottom = getChildAt(count - 1).getBottom();
1136            final int height = getHeight();
1137            final float fadeLength = (float) getVerticalFadingEdgeLength();
1138            return bottom > height - mPaddingBottom ?
1139                    (float) (bottom - height + mPaddingBottom) / fadeLength : fadeEdge;
1140        }
1141    }
1142
1143    @Override
1144    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1145        if (mSelector == null) {
1146            useDefaultSelector();
1147        }
1148        final Rect listPadding = mListPadding;
1149        listPadding.left = mSelectionLeftPadding + mPaddingLeft;
1150        listPadding.top = mSelectionTopPadding + mPaddingTop;
1151        listPadding.right = mSelectionRightPadding + mPaddingRight;
1152        listPadding.bottom = mSelectionBottomPadding + mPaddingBottom;
1153    }
1154
1155    /**
1156     * Subclasses should NOT override this method but
1157     *  {@link #layoutChildren()} instead.
1158     */
1159    @Override
1160    protected void onLayout(boolean changed, int l, int t, int r, int b) {
1161        super.onLayout(changed, l, t, r, b);
1162        mInLayout = true;
1163        layoutChildren();
1164        mInLayout = false;
1165
1166        mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;
1167    }
1168
1169    /**
1170     * @hide
1171     */
1172    @Override
1173    protected boolean setFrame(int left, int top, int right, int bottom) {
1174        final boolean changed = super.setFrame(left, top, right, bottom);
1175
1176        if (changed) {
1177            // Reposition the popup when the frame has changed. This includes
1178            // translating the widget, not just changing its dimension. The
1179            // filter popup needs to follow the widget.
1180            final boolean visible = getWindowVisibility() == View.VISIBLE;
1181            if (mFiltered && visible && mPopup != null && mPopup.isShowing()) {
1182                positionPopup();
1183            }
1184        }
1185
1186        return changed;
1187    }
1188
1189    /**
1190     * Subclasses must override this method to layout their children.
1191     */
1192    protected void layoutChildren() {
1193    }
1194
1195    void updateScrollIndicators() {
1196        if (mScrollUp != null) {
1197            boolean canScrollUp;
1198            // 0th element is not visible
1199            canScrollUp = mFirstPosition > 0;
1200
1201            // ... Or top of 0th element is not visible
1202            if (!canScrollUp) {
1203                if (getChildCount() > 0) {
1204                    View child = getChildAt(0);
1205                    canScrollUp = child.getTop() < mListPadding.top;
1206                }
1207            }
1208
1209            mScrollUp.setVisibility(canScrollUp ? View.VISIBLE : View.INVISIBLE);
1210        }
1211
1212        if (mScrollDown != null) {
1213            boolean canScrollDown;
1214            int count = getChildCount();
1215
1216            // Last item is not visible
1217            canScrollDown = (mFirstPosition + count) < mItemCount;
1218
1219            // ... Or bottom of the last element is not visible
1220            if (!canScrollDown && count > 0) {
1221                View child = getChildAt(count - 1);
1222                canScrollDown = child.getBottom() > mBottom - mListPadding.bottom;
1223            }
1224
1225            mScrollDown.setVisibility(canScrollDown ? View.VISIBLE : View.INVISIBLE);
1226        }
1227    }
1228
1229    @Override
1230    @ViewDebug.ExportedProperty
1231    public View getSelectedView() {
1232        if (mItemCount > 0 && mSelectedPosition >= 0) {
1233            return getChildAt(mSelectedPosition - mFirstPosition);
1234        } else {
1235            return null;
1236        }
1237    }
1238
1239    /**
1240     * List padding is the maximum of the normal view's padding and the padding of the selector.
1241     *
1242     * @see android.view.View#getPaddingTop()
1243     * @see #getSelector()
1244     *
1245     * @return The top list padding.
1246     */
1247    public int getListPaddingTop() {
1248        return mListPadding.top;
1249    }
1250
1251    /**
1252     * List padding is the maximum of the normal view's padding and the padding of the selector.
1253     *
1254     * @see android.view.View#getPaddingBottom()
1255     * @see #getSelector()
1256     *
1257     * @return The bottom list padding.
1258     */
1259    public int getListPaddingBottom() {
1260        return mListPadding.bottom;
1261    }
1262
1263    /**
1264     * List padding is the maximum of the normal view's padding and the padding of the selector.
1265     *
1266     * @see android.view.View#getPaddingLeft()
1267     * @see #getSelector()
1268     *
1269     * @return The left list padding.
1270     */
1271    public int getListPaddingLeft() {
1272        return mListPadding.left;
1273    }
1274
1275    /**
1276     * List padding is the maximum of the normal view's padding and the padding of the selector.
1277     *
1278     * @see android.view.View#getPaddingRight()
1279     * @see #getSelector()
1280     *
1281     * @return The right list padding.
1282     */
1283    public int getListPaddingRight() {
1284        return mListPadding.right;
1285    }
1286
1287    /**
1288     * Get a view and have it show the data associated with the specified
1289     * position. This is called when we have already discovered that the view is
1290     * not available for reuse in the recycle bin. The only choices left are
1291     * converting an old view or making a new one.
1292     *
1293     * @param position The position to display
1294     * @param isScrap Array of at least 1 boolean, the first entry will become true if
1295     *                the returned view was taken from the scrap heap, false if otherwise.
1296     *
1297     * @return A view displaying the data associated with the specified position
1298     */
1299    View obtainView(int position, boolean[] isScrap) {
1300        isScrap[0] = false;
1301        View scrapView;
1302
1303        scrapView = mRecycler.getScrapView(position);
1304
1305        View child;
1306        if (scrapView != null) {
1307            if (ViewDebug.TRACE_RECYCLER) {
1308                ViewDebug.trace(scrapView, ViewDebug.RecyclerTraceType.RECYCLE_FROM_SCRAP_HEAP,
1309                        position, -1);
1310            }
1311
1312            child = mAdapter.getView(position, scrapView, this);
1313
1314            if (ViewDebug.TRACE_RECYCLER) {
1315                ViewDebug.trace(child, ViewDebug.RecyclerTraceType.BIND_VIEW,
1316                        position, getChildCount());
1317            }
1318
1319            if (child != scrapView) {
1320                mRecycler.addScrapView(scrapView);
1321                if (mCacheColorHint != 0) {
1322                    child.setDrawingCacheBackgroundColor(mCacheColorHint);
1323                }
1324                if (ViewDebug.TRACE_RECYCLER) {
1325                    ViewDebug.trace(scrapView, ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP,
1326                            position, -1);
1327                }
1328            } else {
1329                isScrap[0] = true;
1330                child.dispatchFinishTemporaryDetach();
1331            }
1332        } else {
1333            child = mAdapter.getView(position, null, this);
1334            if (mCacheColorHint != 0) {
1335                child.setDrawingCacheBackgroundColor(mCacheColorHint);
1336            }
1337            if (ViewDebug.TRACE_RECYCLER) {
1338                ViewDebug.trace(child, ViewDebug.RecyclerTraceType.NEW_VIEW,
1339                        position, getChildCount());
1340            }
1341        }
1342
1343        return child;
1344    }
1345
1346    void positionSelector(View sel) {
1347        final Rect selectorRect = mSelectorRect;
1348        selectorRect.set(sel.getLeft(), sel.getTop(), sel.getRight(), sel.getBottom());
1349        positionSelector(selectorRect.left, selectorRect.top, selectorRect.right,
1350                selectorRect.bottom);
1351
1352        final boolean isChildViewEnabled = mIsChildViewEnabled;
1353        if (sel.isEnabled() != isChildViewEnabled) {
1354            mIsChildViewEnabled = !isChildViewEnabled;
1355            refreshDrawableState();
1356        }
1357    }
1358
1359    private void positionSelector(int l, int t, int r, int b) {
1360        mSelectorRect.set(l - mSelectionLeftPadding, t - mSelectionTopPadding, r
1361                + mSelectionRightPadding, b + mSelectionBottomPadding);
1362    }
1363
1364    @Override
1365    protected void dispatchDraw(Canvas canvas) {
1366        int saveCount = 0;
1367        final boolean clipToPadding = (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
1368        if (clipToPadding) {
1369            saveCount = canvas.save();
1370            final int scrollX = mScrollX;
1371            final int scrollY = mScrollY;
1372            canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,
1373                    scrollX + mRight - mLeft - mPaddingRight,
1374                    scrollY + mBottom - mTop - mPaddingBottom);
1375            mGroupFlags &= ~CLIP_TO_PADDING_MASK;
1376        }
1377
1378        final boolean drawSelectorOnTop = mDrawSelectorOnTop;
1379        if (!drawSelectorOnTop) {
1380            drawSelector(canvas);
1381        }
1382
1383        super.dispatchDraw(canvas);
1384
1385        if (drawSelectorOnTop) {
1386            drawSelector(canvas);
1387        }
1388
1389        if (clipToPadding) {
1390            canvas.restoreToCount(saveCount);
1391            mGroupFlags |= CLIP_TO_PADDING_MASK;
1392        }
1393    }
1394
1395    @Override
1396    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
1397        if (getChildCount() > 0) {
1398            mDataChanged = true;
1399            rememberSyncState();
1400        }
1401
1402        if (mFastScroller != null) {
1403            mFastScroller.onSizeChanged(w, h, oldw, oldh);
1404        }
1405    }
1406
1407    /**
1408     * @return True if the current touch mode requires that we draw the selector in the pressed
1409     *         state.
1410     */
1411    boolean touchModeDrawsInPressedState() {
1412        // FIXME use isPressed for this
1413        switch (mTouchMode) {
1414        case TOUCH_MODE_TAP:
1415        case TOUCH_MODE_DONE_WAITING:
1416            return true;
1417        default:
1418            return false;
1419        }
1420    }
1421
1422    /**
1423     * Indicates whether this view is in a state where the selector should be drawn. This will
1424     * happen if we have focus but are not in touch mode, or we are in the middle of displaying
1425     * the pressed state for an item.
1426     *
1427     * @return True if the selector should be shown
1428     */
1429    boolean shouldShowSelector() {
1430        return (hasFocus() && !isInTouchMode()) || touchModeDrawsInPressedState();
1431    }
1432
1433    private void drawSelector(Canvas canvas) {
1434        if (shouldShowSelector() && mSelectorRect != null && !mSelectorRect.isEmpty()) {
1435            final Drawable selector = mSelector;
1436            selector.setBounds(mSelectorRect);
1437            selector.draw(canvas);
1438        }
1439    }
1440
1441    /**
1442     * Controls whether the selection highlight drawable should be drawn on top of the item or
1443     * behind it.
1444     *
1445     * @param onTop If true, the selector will be drawn on the item it is highlighting. The default
1446     *        is false.
1447     *
1448     * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop
1449     */
1450    public void setDrawSelectorOnTop(boolean onTop) {
1451        mDrawSelectorOnTop = onTop;
1452    }
1453
1454    /**
1455     * Set a Drawable that should be used to highlight the currently selected item.
1456     *
1457     * @param resID A Drawable resource to use as the selection highlight.
1458     *
1459     * @attr ref android.R.styleable#AbsListView_listSelector
1460     */
1461    public void setSelector(int resID) {
1462        setSelector(getResources().getDrawable(resID));
1463    }
1464
1465    public void setSelector(Drawable sel) {
1466        if (mSelector != null) {
1467            mSelector.setCallback(null);
1468            unscheduleDrawable(mSelector);
1469        }
1470        mSelector = sel;
1471        Rect padding = new Rect();
1472        sel.getPadding(padding);
1473        mSelectionLeftPadding = padding.left;
1474        mSelectionTopPadding = padding.top;
1475        mSelectionRightPadding = padding.right;
1476        mSelectionBottomPadding = padding.bottom;
1477        sel.setCallback(this);
1478        sel.setState(getDrawableState());
1479    }
1480
1481    /**
1482     * Returns the selector {@link android.graphics.drawable.Drawable} that is used to draw the
1483     * selection in the list.
1484     *
1485     * @return the drawable used to display the selector
1486     */
1487    public Drawable getSelector() {
1488        return mSelector;
1489    }
1490
1491    /**
1492     * Sets the selector state to "pressed" and posts a CheckForKeyLongPress to see if
1493     * this is a long press.
1494     */
1495    void keyPressed() {
1496        if (!isEnabled() || !isClickable()) {
1497            return;
1498        }
1499
1500        Drawable selector = mSelector;
1501        Rect selectorRect = mSelectorRect;
1502        if (selector != null && (isFocused() || touchModeDrawsInPressedState())
1503                && selectorRect != null && !selectorRect.isEmpty()) {
1504
1505            final View v = getChildAt(mSelectedPosition - mFirstPosition);
1506
1507            if (v != null) {
1508                if (v.hasFocusable()) return;
1509                v.setPressed(true);
1510            }
1511            setPressed(true);
1512
1513            final boolean longClickable = isLongClickable();
1514            Drawable d = selector.getCurrent();
1515            if (d != null && d instanceof TransitionDrawable) {
1516                if (longClickable) {
1517                    ((TransitionDrawable) d).startTransition(
1518                            ViewConfiguration.getLongPressTimeout());
1519                } else {
1520                    ((TransitionDrawable) d).resetTransition();
1521                }
1522            }
1523            if (longClickable && !mDataChanged) {
1524                if (mPendingCheckForKeyLongPress == null) {
1525                    mPendingCheckForKeyLongPress = new CheckForKeyLongPress();
1526                }
1527                mPendingCheckForKeyLongPress.rememberWindowAttachCount();
1528                postDelayed(mPendingCheckForKeyLongPress, ViewConfiguration.getLongPressTimeout());
1529            }
1530        }
1531    }
1532
1533    public void setScrollIndicators(View up, View down) {
1534        mScrollUp = up;
1535        mScrollDown = down;
1536    }
1537
1538    @Override
1539    protected void drawableStateChanged() {
1540        super.drawableStateChanged();
1541        if (mSelector != null) {
1542            mSelector.setState(getDrawableState());
1543        }
1544    }
1545
1546    @Override
1547    protected int[] onCreateDrawableState(int extraSpace) {
1548        // If the child view is enabled then do the default behavior.
1549        if (mIsChildViewEnabled) {
1550            // Common case
1551            return super.onCreateDrawableState(extraSpace);
1552        }
1553
1554        // The selector uses this View's drawable state. The selected child view
1555        // is disabled, so we need to remove the enabled state from the drawable
1556        // states.
1557        final int enabledState = ENABLED_STATE_SET[0];
1558
1559        // If we don't have any extra space, it will return one of the static state arrays,
1560        // and clearing the enabled state on those arrays is a bad thing!  If we specify
1561        // we need extra space, it will create+copy into a new array that safely mutable.
1562        int[] state = super.onCreateDrawableState(extraSpace + 1);
1563        int enabledPos = -1;
1564        for (int i = state.length - 1; i >= 0; i--) {
1565            if (state[i] == enabledState) {
1566                enabledPos = i;
1567                break;
1568            }
1569        }
1570
1571        // Remove the enabled state
1572        if (enabledPos >= 0) {
1573            System.arraycopy(state, enabledPos + 1, state, enabledPos,
1574                    state.length - enabledPos - 1);
1575        }
1576
1577        return state;
1578    }
1579
1580    @Override
1581    public boolean verifyDrawable(Drawable dr) {
1582        return mSelector == dr || super.verifyDrawable(dr);
1583    }
1584
1585    @Override
1586    protected void onAttachedToWindow() {
1587        super.onAttachedToWindow();
1588
1589        final ViewTreeObserver treeObserver = getViewTreeObserver();
1590        if (treeObserver != null) {
1591            treeObserver.addOnTouchModeChangeListener(this);
1592            if (mTextFilterEnabled && mPopup != null && !mGlobalLayoutListenerAddedFilter) {
1593                treeObserver.addOnGlobalLayoutListener(this);
1594            }
1595        }
1596    }
1597
1598    @Override
1599    protected void onDetachedFromWindow() {
1600        super.onDetachedFromWindow();
1601
1602        // Dismiss the popup in case onSaveInstanceState() was not invoked
1603        dismissPopup();
1604
1605        // Detach any view left in the scrap heap
1606        mRecycler.clear();
1607
1608        final ViewTreeObserver treeObserver = getViewTreeObserver();
1609        if (treeObserver != null) {
1610            treeObserver.removeOnTouchModeChangeListener(this);
1611            if (mTextFilterEnabled && mPopup != null) {
1612                treeObserver.removeGlobalOnLayoutListener(this);
1613                mGlobalLayoutListenerAddedFilter = false;
1614            }
1615        }
1616    }
1617
1618    @Override
1619    public void onWindowFocusChanged(boolean hasWindowFocus) {
1620        super.onWindowFocusChanged(hasWindowFocus);
1621
1622        final int touchMode = isInTouchMode() ? TOUCH_MODE_ON : TOUCH_MODE_OFF;
1623
1624        if (!hasWindowFocus) {
1625            setChildrenDrawingCacheEnabled(false);
1626            if (mFlingRunnable != null) {
1627                removeCallbacks(mFlingRunnable);
1628                // let the fling runnable report it's new state which
1629                // should be idle
1630                mFlingRunnable.endFling();
1631                if (mScrollY != 0) {
1632                    mScrollY = 0;
1633                    invalidate();
1634                }
1635            }
1636            // Always hide the type filter
1637            dismissPopup();
1638
1639            if (touchMode == TOUCH_MODE_OFF) {
1640                // Remember the last selected element
1641                mResurrectToPosition = mSelectedPosition;
1642            }
1643        } else {
1644            if (mFiltered) {
1645                // Show the type filter only if a filter is in effect
1646                showPopup();
1647            }
1648
1649            // If we changed touch mode since the last time we had focus
1650            if (touchMode != mLastTouchMode && mLastTouchMode != TOUCH_MODE_UNKNOWN) {
1651                // If we come back in trackball mode, we bring the selection back
1652                if (touchMode == TOUCH_MODE_OFF) {
1653                    // This will trigger a layout
1654                    resurrectSelection();
1655
1656                // If we come back in touch mode, then we want to hide the selector
1657                } else {
1658                    hideSelector();
1659                    mLayoutMode = LAYOUT_NORMAL;
1660                    layoutChildren();
1661                }
1662            }
1663        }
1664
1665        mLastTouchMode = touchMode;
1666    }
1667
1668    /**
1669     * Creates the ContextMenuInfo returned from {@link #getContextMenuInfo()}. This
1670     * methods knows the view, position and ID of the item that received the
1671     * long press.
1672     *
1673     * @param view The view that received the long press.
1674     * @param position The position of the item that received the long press.
1675     * @param id The ID of the item that received the long press.
1676     * @return The extra information that should be returned by
1677     *         {@link #getContextMenuInfo()}.
1678     */
1679    ContextMenuInfo createContextMenuInfo(View view, int position, long id) {
1680        return new AdapterContextMenuInfo(view, position, id);
1681    }
1682
1683    /**
1684     * A base class for Runnables that will check that their view is still attached to
1685     * the original window as when the Runnable was created.
1686     *
1687     */
1688    private class WindowRunnnable {
1689        private int mOriginalAttachCount;
1690
1691        public void rememberWindowAttachCount() {
1692            mOriginalAttachCount = getWindowAttachCount();
1693        }
1694
1695        public boolean sameWindow() {
1696            return hasWindowFocus() && getWindowAttachCount() == mOriginalAttachCount;
1697        }
1698    }
1699
1700    private class PerformClick extends WindowRunnnable implements Runnable {
1701        View mChild;
1702        int mClickMotionPosition;
1703
1704        public void run() {
1705            // The data has changed since we posted this action in the event queue,
1706            // bail out before bad things happen
1707            if (mDataChanged) return;
1708
1709            if (mAdapter != null && mItemCount > 0 &&
1710                    mClickMotionPosition != INVALID_POSITION &&
1711                    mClickMotionPosition < mAdapter.getCount() && sameWindow()) {
1712                performItemClick(mChild, mClickMotionPosition, getAdapter().getItemId(
1713                        mClickMotionPosition));
1714            }
1715        }
1716    }
1717
1718    private class CheckForLongPress extends WindowRunnnable implements Runnable {
1719        public void run() {
1720            final int motionPosition = mMotionPosition;
1721            final View child = getChildAt(motionPosition - mFirstPosition);
1722            if (child != null) {
1723                final int longPressPosition = mMotionPosition;
1724                final long longPressId = mAdapter.getItemId(mMotionPosition);
1725
1726                boolean handled = false;
1727                if (sameWindow() && !mDataChanged) {
1728                    handled = performLongPress(child, longPressPosition, longPressId);
1729                }
1730                if (handled) {
1731                    mTouchMode = TOUCH_MODE_REST;
1732                    setPressed(false);
1733                    child.setPressed(false);
1734                } else {
1735                    mTouchMode = TOUCH_MODE_DONE_WAITING;
1736                }
1737
1738            }
1739        }
1740    }
1741
1742    private class CheckForKeyLongPress extends WindowRunnnable implements Runnable {
1743        public void run() {
1744            if (isPressed() && mSelectedPosition >= 0) {
1745                int index = mSelectedPosition - mFirstPosition;
1746                View v = getChildAt(index);
1747
1748                if (!mDataChanged) {
1749                    boolean handled = false;
1750                    if (sameWindow()) {
1751                        handled = performLongPress(v, mSelectedPosition, mSelectedRowId);
1752                    }
1753                    if (handled) {
1754                        setPressed(false);
1755                        v.setPressed(false);
1756                    }
1757                } else {
1758                    setPressed(false);
1759                    if (v != null) v.setPressed(false);
1760                }
1761            }
1762        }
1763    }
1764
1765    private boolean performLongPress(final View child,
1766            final int longPressPosition, final long longPressId) {
1767        boolean handled = false;
1768
1769        if (mOnItemLongClickListener != null) {
1770            handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, child,
1771                    longPressPosition, longPressId);
1772        }
1773        if (!handled) {
1774            mContextMenuInfo = createContextMenuInfo(child, longPressPosition, longPressId);
1775            handled = super.showContextMenuForChild(AbsListView.this);
1776        }
1777        if (handled) {
1778            performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
1779        }
1780        return handled;
1781    }
1782
1783    @Override
1784    protected ContextMenuInfo getContextMenuInfo() {
1785        return mContextMenuInfo;
1786    }
1787
1788    @Override
1789    public boolean showContextMenuForChild(View originalView) {
1790        final int longPressPosition = getPositionForView(originalView);
1791        if (longPressPosition >= 0) {
1792            final long longPressId = mAdapter.getItemId(longPressPosition);
1793            boolean handled = false;
1794
1795            if (mOnItemLongClickListener != null) {
1796                handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, originalView,
1797                        longPressPosition, longPressId);
1798            }
1799            if (!handled) {
1800                mContextMenuInfo = createContextMenuInfo(
1801                        getChildAt(longPressPosition - mFirstPosition),
1802                        longPressPosition, longPressId);
1803                handled = super.showContextMenuForChild(originalView);
1804            }
1805
1806            return handled;
1807        }
1808        return false;
1809    }
1810
1811    @Override
1812    public boolean onKeyDown(int keyCode, KeyEvent event) {
1813        return false;
1814    }
1815
1816    @Override
1817    public boolean onKeyUp(int keyCode, KeyEvent event) {
1818        switch (keyCode) {
1819        case KeyEvent.KEYCODE_DPAD_CENTER:
1820        case KeyEvent.KEYCODE_ENTER:
1821            if (!isEnabled()) {
1822                return true;
1823            }
1824            if (isClickable() && isPressed() &&
1825                    mSelectedPosition >= 0 && mAdapter != null &&
1826                    mSelectedPosition < mAdapter.getCount()) {
1827
1828                final View view = getChildAt(mSelectedPosition - mFirstPosition);
1829                performItemClick(view, mSelectedPosition, mSelectedRowId);
1830                setPressed(false);
1831                if (view != null) view.setPressed(false);
1832                return true;
1833            }
1834            break;
1835        }
1836        return super.onKeyUp(keyCode, event);
1837    }
1838
1839    @Override
1840    protected void dispatchSetPressed(boolean pressed) {
1841        // Don't dispatch setPressed to our children. We call setPressed on ourselves to
1842        // get the selector in the right state, but we don't want to press each child.
1843    }
1844
1845    /**
1846     * Maps a point to a position in the list.
1847     *
1848     * @param x X in local coordinate
1849     * @param y Y in local coordinate
1850     * @return The position of the item which contains the specified point, or
1851     *         {@link #INVALID_POSITION} if the point does not intersect an item.
1852     */
1853    public int pointToPosition(int x, int y) {
1854        Rect frame = mTouchFrame;
1855        if (frame == null) {
1856            mTouchFrame = new Rect();
1857            frame = mTouchFrame;
1858        }
1859
1860        final int count = getChildCount();
1861        for (int i = count - 1; i >= 0; i--) {
1862            final View child = getChildAt(i);
1863            if (child.getVisibility() == View.VISIBLE) {
1864                child.getHitRect(frame);
1865                if (frame.contains(x, y)) {
1866                    return mFirstPosition + i;
1867                }
1868            }
1869        }
1870        return INVALID_POSITION;
1871    }
1872
1873
1874    /**
1875     * Maps a point to a the rowId of the item which intersects that point.
1876     *
1877     * @param x X in local coordinate
1878     * @param y Y in local coordinate
1879     * @return The rowId of the item which contains the specified point, or {@link #INVALID_ROW_ID}
1880     *         if the point does not intersect an item.
1881     */
1882    public long pointToRowId(int x, int y) {
1883        int position = pointToPosition(x, y);
1884        if (position >= 0) {
1885            return mAdapter.getItemId(position);
1886        }
1887        return INVALID_ROW_ID;
1888    }
1889
1890    final class CheckForTap implements Runnable {
1891        public void run() {
1892            if (mTouchMode == TOUCH_MODE_DOWN) {
1893                mTouchMode = TOUCH_MODE_TAP;
1894                final View child = getChildAt(mMotionPosition - mFirstPosition);
1895                if (child != null && !child.hasFocusable()) {
1896                    mLayoutMode = LAYOUT_NORMAL;
1897
1898                    if (!mDataChanged) {
1899                        layoutChildren();
1900                        child.setPressed(true);
1901                        positionSelector(child);
1902                        setPressed(true);
1903
1904                        final int longPressTimeout = ViewConfiguration.getLongPressTimeout();
1905                        final boolean longClickable = isLongClickable();
1906
1907                        if (mSelector != null) {
1908                            Drawable d = mSelector.getCurrent();
1909                            if (d != null && d instanceof TransitionDrawable) {
1910                                if (longClickable) {
1911                                    ((TransitionDrawable) d).startTransition(longPressTimeout);
1912                                } else {
1913                                    ((TransitionDrawable) d).resetTransition();
1914                                }
1915                            }
1916                        }
1917
1918                        if (longClickable) {
1919                            if (mPendingCheckForLongPress == null) {
1920                                mPendingCheckForLongPress = new CheckForLongPress();
1921                            }
1922                            mPendingCheckForLongPress.rememberWindowAttachCount();
1923                            postDelayed(mPendingCheckForLongPress, longPressTimeout);
1924                        } else {
1925                            mTouchMode = TOUCH_MODE_DONE_WAITING;
1926                        }
1927                    } else {
1928                        mTouchMode = TOUCH_MODE_DONE_WAITING;
1929                    }
1930                }
1931            }
1932        }
1933    }
1934
1935    private boolean startScrollIfNeeded(int deltaY) {
1936        // Check if we have moved far enough that it looks more like a
1937        // scroll than a tap
1938        final int distance = Math.abs(deltaY);
1939        final boolean overscroll = mScrollY != 0;
1940        if (overscroll || distance > mTouchSlop) {
1941            createScrollingCache();
1942            mTouchMode = overscroll ? TOUCH_MODE_OVERSCROLL : TOUCH_MODE_SCROLL;
1943            mMotionCorrection = deltaY;
1944            final Handler handler = getHandler();
1945            // Handler should not be null unless the AbsListView is not attached to a
1946            // window, which would make it very hard to scroll it... but the monkeys
1947            // say it's possible.
1948            if (handler != null) {
1949                handler.removeCallbacks(mPendingCheckForLongPress);
1950            }
1951            setPressed(false);
1952            View motionView = getChildAt(mMotionPosition - mFirstPosition);
1953            if (motionView != null) {
1954                motionView.setPressed(false);
1955            }
1956            reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
1957            // Time to start stealing events! Once we've stolen them, don't let anyone
1958            // steal from us
1959            requestDisallowInterceptTouchEvent(true);
1960            return true;
1961        }
1962
1963        return false;
1964    }
1965
1966    public void onTouchModeChanged(boolean isInTouchMode) {
1967        if (isInTouchMode) {
1968            // Get rid of the selection when we enter touch mode
1969            hideSelector();
1970            // Layout, but only if we already have done so previously.
1971            // (Otherwise may clobber a LAYOUT_SYNC layout that was requested to restore
1972            // state.)
1973            if (getHeight() > 0 && getChildCount() > 0) {
1974                // We do not lose focus initiating a touch (since AbsListView is focusable in
1975                // touch mode). Force an initial layout to get rid of the selection.
1976                layoutChildren();
1977            }
1978        } else {
1979            int touchMode = mTouchMode;
1980            if (touchMode == TOUCH_MODE_OVERSCROLL || touchMode == TOUCH_MODE_OVERFLING) {
1981                if (mFlingRunnable != null) {
1982                    mFlingRunnable.endFling();
1983
1984                    if (mScrollY != 0) {
1985                        mScrollY = 0;
1986                        invalidate();
1987                    }
1988                }
1989            }
1990        }
1991        mLastTouchMode = isInTouchMode ? TOUCH_MODE_ON : TOUCH_MODE_OFF;
1992    }
1993
1994    @Override
1995    public boolean onTouchEvent(MotionEvent ev) {
1996        if (!isEnabled()) {
1997            // A disabled view that is clickable still consumes the touch
1998            // events, it just doesn't respond to them.
1999            return isClickable() || isLongClickable();
2000        }
2001
2002        if (mFastScroller != null) {
2003            boolean intercepted = mFastScroller.onTouchEvent(ev);
2004            if (intercepted) {
2005                return true;
2006            }
2007        }
2008
2009        final int action = ev.getAction();
2010
2011        View v;
2012        int deltaY;
2013
2014        if (mVelocityTracker == null) {
2015            mVelocityTracker = VelocityTracker.obtain();
2016        }
2017        mVelocityTracker.addMovement(ev);
2018
2019        switch (action & MotionEvent.ACTION_MASK) {
2020        case MotionEvent.ACTION_DOWN: {
2021            switch (mTouchMode) {
2022            case TOUCH_MODE_OVERFLING: {
2023                mFlingRunnable.endFling();
2024                mTouchMode = TOUCH_MODE_OVERSCROLL;
2025                mLastY = (int) ev.getY();
2026                mMotionCorrection = 0;
2027                mActivePointerId = ev.getPointerId(0);
2028                break;
2029            }
2030
2031            default: {
2032                mActivePointerId = ev.getPointerId(0);
2033                final int x = (int) ev.getX();
2034                final int y = (int) ev.getY();
2035                int motionPosition = pointToPosition(x, y);
2036                if (!mDataChanged) {
2037                    if ((mTouchMode != TOUCH_MODE_FLING) && (motionPosition >= 0)
2038                            && (getAdapter().isEnabled(motionPosition))) {
2039                        // User clicked on an actual view (and was not stopping a fling). It might be a
2040                        // click or a scroll. Assume it is a click until proven otherwise
2041                        mTouchMode = TOUCH_MODE_DOWN;
2042                        // FIXME Debounce
2043                        if (mPendingCheckForTap == null) {
2044                            mPendingCheckForTap = new CheckForTap();
2045                        }
2046                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
2047                    } else {
2048                        if (ev.getEdgeFlags() != 0 && motionPosition < 0) {
2049                            // If we couldn't find a view to click on, but the down event was touching
2050                            // the edge, we will bail out and try again. This allows the edge correcting
2051                            // code in ViewRoot to try to find a nearby view to select
2052                            return false;
2053                        }
2054
2055                        if (mTouchMode == TOUCH_MODE_FLING) {
2056                            // Stopped a fling. It is a scroll.
2057                            createScrollingCache();
2058                            mTouchMode = TOUCH_MODE_SCROLL;
2059                            mMotionCorrection = 0;
2060                            motionPosition = findMotionRow(y);
2061                            reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
2062                        }
2063                    }
2064                }
2065
2066                if (motionPosition >= 0) {
2067                    // Remember where the motion event started
2068                    v = getChildAt(motionPosition - mFirstPosition);
2069                    mMotionViewOriginalTop = v.getTop();
2070                }
2071                mMotionX = x;
2072                mMotionY = y;
2073                mMotionPosition = motionPosition;
2074                mLastY = Integer.MIN_VALUE;
2075                break;
2076            }
2077            }
2078            break;
2079        }
2080
2081        case MotionEvent.ACTION_MOVE: {
2082            final int pointerIndex = ev.findPointerIndex(mActivePointerId);
2083            final int y = (int) ev.getY(pointerIndex);
2084            deltaY = y - mMotionY;
2085            switch (mTouchMode) {
2086            case TOUCH_MODE_DOWN:
2087            case TOUCH_MODE_TAP:
2088            case TOUCH_MODE_DONE_WAITING:
2089                // Check if we have moved far enough that it looks more like a
2090                // scroll than a tap
2091                startScrollIfNeeded(deltaY);
2092                break;
2093            case TOUCH_MODE_SCROLL:
2094                if (PROFILE_SCROLLING) {
2095                    if (!mScrollProfilingStarted) {
2096                        Debug.startMethodTracing("AbsListViewScroll");
2097                        mScrollProfilingStarted = true;
2098                    }
2099                }
2100
2101                if (y != mLastY) {
2102                    deltaY -= mMotionCorrection;
2103                    int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY;
2104
2105                    int motionViewPrevTop = 0;
2106                    View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
2107                    if (motionView != null) {
2108                        motionViewPrevTop = motionView.getTop();
2109                    }
2110
2111                    // No need to do all this work if we're not going to move anyway
2112                    boolean atEdge = false;
2113                    if (incrementalDeltaY != 0) {
2114                        atEdge = trackMotionScroll(deltaY, incrementalDeltaY);
2115                    }
2116
2117                    // Check to see if we have bumped into the scroll limit
2118                    motionView = this.getChildAt(mMotionPosition - mFirstPosition);
2119                    if (motionView != null) {
2120                        // Check if the top of the motion view is where it is
2121                        // supposed to be
2122                        final int motionViewRealTop = motionView.getTop();
2123                        if (atEdge) {
2124                            // Apply overscroll
2125
2126                            int overscroll = -incrementalDeltaY -
2127                                    (motionViewRealTop - motionViewPrevTop);
2128                            overscrollBy(0, overscroll, 0, mScrollY, 0, 0,
2129                                    0, getOverscrollMax());
2130                            mTouchMode = TOUCH_MODE_OVERSCROLL;
2131                            invalidate();
2132                        }
2133                    }
2134                    mLastY = y;
2135                }
2136                break;
2137
2138            case TOUCH_MODE_OVERSCROLL:
2139                if (y != mLastY) {
2140                    deltaY -= mMotionCorrection;
2141                    int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY;
2142
2143                    final int oldScroll = mScrollY;
2144                    final int newScroll = oldScroll - incrementalDeltaY;
2145
2146                    if ((oldScroll >= 0 && newScroll <= 0) ||
2147                            (oldScroll <= 0 && newScroll >= 0)) {
2148                        // Coming back to 'real' list scrolling
2149                        incrementalDeltaY = -newScroll;
2150                        mScrollY = 0;
2151
2152                        // No need to do all this work if we're not going to move anyway
2153                        if (incrementalDeltaY != 0) {
2154                            trackMotionScroll(incrementalDeltaY, incrementalDeltaY);
2155                        }
2156
2157                        // Check to see if we are back in
2158                        View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
2159                        if (motionView != null) {
2160                            mTouchMode = TOUCH_MODE_SCROLL;
2161
2162                            // We did not scroll the full amount. Treat this essentially like the
2163                            // start of a new touch scroll
2164                            final int motionPosition = findClosestMotionRow(y);
2165
2166                            mMotionCorrection = 0;
2167                            motionView = getChildAt(motionPosition - mFirstPosition);
2168                            mMotionViewOriginalTop = motionView.getTop();
2169                            mMotionY = y;
2170                            mMotionPosition = motionPosition;
2171                        }
2172                    } else {
2173                        overscrollBy(0, -incrementalDeltaY, 0, mScrollY, 0, 0,
2174                                0, getOverscrollMax());
2175                        invalidate();
2176                    }
2177                    mLastY = y;
2178                }
2179                break;
2180            }
2181
2182            break;
2183        }
2184
2185        case MotionEvent.ACTION_UP: {
2186            switch (mTouchMode) {
2187            case TOUCH_MODE_DOWN:
2188            case TOUCH_MODE_TAP:
2189            case TOUCH_MODE_DONE_WAITING:
2190                final int motionPosition = mMotionPosition;
2191                final View child = getChildAt(motionPosition - mFirstPosition);
2192                if (child != null && !child.hasFocusable()) {
2193                    if (mTouchMode != TOUCH_MODE_DOWN) {
2194                        child.setPressed(false);
2195                    }
2196
2197                    if (mPerformClick == null) {
2198                        mPerformClick = new PerformClick();
2199                    }
2200
2201                    final AbsListView.PerformClick performClick = mPerformClick;
2202                    performClick.mChild = child;
2203                    performClick.mClickMotionPosition = motionPosition;
2204                    performClick.rememberWindowAttachCount();
2205
2206                    mResurrectToPosition = motionPosition;
2207
2208                    if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) {
2209                        final Handler handler = getHandler();
2210                        if (handler != null) {
2211                            handler.removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
2212                                    mPendingCheckForTap : mPendingCheckForLongPress);
2213                        }
2214                        mLayoutMode = LAYOUT_NORMAL;
2215                        mTouchMode = TOUCH_MODE_TAP;
2216                        if (!mDataChanged) {
2217                            setSelectedPositionInt(mMotionPosition);
2218                            layoutChildren();
2219                            child.setPressed(true);
2220                            positionSelector(child);
2221                            setPressed(true);
2222                            if (mSelector != null) {
2223                                Drawable d = mSelector.getCurrent();
2224                                if (d != null && d instanceof TransitionDrawable) {
2225                                    ((TransitionDrawable) d).resetTransition();
2226                                }
2227                            }
2228                            postDelayed(new Runnable() {
2229                                public void run() {
2230                                    child.setPressed(false);
2231                                    setPressed(false);
2232                                    if (!mDataChanged) {
2233                                        post(performClick);
2234                                    }
2235                                    mTouchMode = TOUCH_MODE_REST;
2236                                }
2237                            }, ViewConfiguration.getPressedStateDuration());
2238                        }
2239                        return true;
2240                    } else {
2241                        if (!mDataChanged) {
2242                            post(performClick);
2243                        }
2244                    }
2245                }
2246                mTouchMode = TOUCH_MODE_REST;
2247                break;
2248            case TOUCH_MODE_SCROLL:
2249                final int childCount = getChildCount();
2250                if (childCount > 0) {
2251                    if (mFirstPosition == 0 && getChildAt(0).getTop() >= mListPadding.top &&
2252                            mFirstPosition + childCount < mItemCount &&
2253                            getChildAt(childCount - 1).getBottom() <=
2254                                    getHeight() - mListPadding.bottom) {
2255                        mTouchMode = TOUCH_MODE_REST;
2256                        reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
2257                    } else {
2258                        final VelocityTracker velocityTracker = mVelocityTracker;
2259                        velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
2260                        final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
2261
2262                        if (Math.abs(initialVelocity) > mMinimumVelocity) {
2263                            if (mFlingRunnable == null) {
2264                                mFlingRunnable = new FlingRunnable();
2265                            }
2266                            reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
2267
2268                            mFlingRunnable.start(-initialVelocity);
2269                        } else {
2270                            mTouchMode = TOUCH_MODE_REST;
2271                            reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
2272                        }
2273                    }
2274                } else {
2275                    mTouchMode = TOUCH_MODE_REST;
2276                    reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
2277                }
2278                break;
2279
2280            case TOUCH_MODE_OVERSCROLL:
2281                if (mFlingRunnable == null) {
2282                    mFlingRunnable = new FlingRunnable();
2283                }
2284                final VelocityTracker velocityTracker = mVelocityTracker;
2285                velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
2286                final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
2287
2288                reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
2289                if (Math.abs(initialVelocity) > mMinimumVelocity) {
2290                    mFlingRunnable.startOverfling(-initialVelocity);
2291                } else {
2292                    mFlingRunnable.startSpringback();
2293                }
2294
2295                break;
2296            }
2297
2298            setPressed(false);
2299
2300            // Need to redraw since we probably aren't drawing the selector anymore
2301            invalidate();
2302
2303            final Handler handler = getHandler();
2304            if (handler != null) {
2305                handler.removeCallbacks(mPendingCheckForLongPress);
2306            }
2307
2308            if (mVelocityTracker != null) {
2309                mVelocityTracker.recycle();
2310                mVelocityTracker = null;
2311            }
2312
2313            mActivePointerId = INVALID_POINTER;
2314
2315            if (PROFILE_SCROLLING) {
2316                if (mScrollProfilingStarted) {
2317                    Debug.stopMethodTracing();
2318                    mScrollProfilingStarted = false;
2319                }
2320            }
2321            break;
2322        }
2323
2324        case MotionEvent.ACTION_CANCEL: {
2325            switch (mTouchMode) {
2326            case TOUCH_MODE_OVERSCROLL:
2327                if (mFlingRunnable == null) {
2328                    mFlingRunnable = new FlingRunnable();
2329                }
2330                mFlingRunnable.startSpringback();
2331                break;
2332
2333            case TOUCH_MODE_OVERFLING:
2334                // Do nothing - let it play out.
2335                break;
2336
2337            default:
2338                mTouchMode = TOUCH_MODE_REST;
2339                setPressed(false);
2340                View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
2341                if (motionView != null) {
2342                    motionView.setPressed(false);
2343                }
2344                clearScrollingCache();
2345
2346                final Handler handler = getHandler();
2347                if (handler != null) {
2348                    handler.removeCallbacks(mPendingCheckForLongPress);
2349                }
2350
2351                if (mVelocityTracker != null) {
2352                    mVelocityTracker.recycle();
2353                    mVelocityTracker = null;
2354                }
2355            }
2356
2357            mActivePointerId = INVALID_POINTER;
2358            break;
2359        }
2360
2361        case MotionEvent.ACTION_POINTER_UP: {
2362            onSecondaryPointerUp(ev);
2363            final int x = mMotionX;
2364            final int y = mMotionY;
2365            final int motionPosition = pointToPosition(x, y);
2366            if (motionPosition >= 0) {
2367                // Remember where the motion event started
2368                v = getChildAt(motionPosition - mFirstPosition);
2369                mMotionViewOriginalTop = v.getTop();
2370                mMotionPosition = motionPosition;
2371            }
2372            mLastY = y;
2373            break;
2374        }
2375        }
2376
2377        return true;
2378    }
2379
2380    @Override
2381    protected void onOverscrolled(int scrollX, int scrollY,
2382            boolean clampedX, boolean clampedY) {
2383        mScrollY = scrollY;
2384        if (clampedY) {
2385            // Velocity is broken by hitting the limit; don't start a fling off of this.
2386            if (mVelocityTracker != null) {
2387                mVelocityTracker.clear();
2388            }
2389        }
2390        awakenScrollBars();
2391    }
2392
2393    private int getOverscrollMax() {
2394        final int childCount = getChildCount();
2395        if (childCount > 0) {
2396            return Math.min(mOverscrollMax,
2397                    getChildAt(childCount - 1).getBottom() / OVERSCROLL_LIMIT_DIVISOR);
2398        } else {
2399            return mOverscrollMax;
2400        }
2401    }
2402
2403    @Override
2404    public void draw(Canvas canvas) {
2405        super.draw(canvas);
2406        if (mFastScroller != null) {
2407            final int scrollY = mScrollY;
2408            if (scrollY != 0) {
2409                // Pin the fast scroll thumb to the top/bottom during overscroll.
2410                int restoreCount = canvas.save();
2411                canvas.translate(0, (float) scrollY);
2412                mFastScroller.draw(canvas);
2413                canvas.restoreToCount(restoreCount);
2414            } else {
2415                mFastScroller.draw(canvas);
2416            }
2417        }
2418    }
2419
2420    @Override
2421    public boolean onInterceptTouchEvent(MotionEvent ev) {
2422        int action = ev.getAction();
2423        View v;
2424
2425        if (mFastScroller != null) {
2426            boolean intercepted = mFastScroller.onInterceptTouchEvent(ev);
2427            if (intercepted) {
2428                return true;
2429            }
2430        }
2431
2432        switch (action & MotionEvent.ACTION_MASK) {
2433        case MotionEvent.ACTION_DOWN: {
2434            int touchMode = mTouchMode;
2435            if (touchMode == TOUCH_MODE_OVERFLING || touchMode == TOUCH_MODE_OVERSCROLL) {
2436                return true;
2437            }
2438
2439            final int x = (int) ev.getX();
2440            final int y = (int) ev.getY();
2441            mActivePointerId = ev.getPointerId(0);
2442
2443            int motionPosition = findMotionRow(y);
2444            if (touchMode != TOUCH_MODE_FLING && motionPosition >= 0) {
2445                // User clicked on an actual view (and was not stopping a fling).
2446                // Remember where the motion event started
2447                v = getChildAt(motionPosition - mFirstPosition);
2448                mMotionViewOriginalTop = v.getTop();
2449                mMotionX = x;
2450                mMotionY = y;
2451                mMotionPosition = motionPosition;
2452                mTouchMode = TOUCH_MODE_DOWN;
2453                clearScrollingCache();
2454            }
2455            mLastY = Integer.MIN_VALUE;
2456            if (touchMode == TOUCH_MODE_FLING) {
2457                return true;
2458            }
2459            break;
2460        }
2461
2462        case MotionEvent.ACTION_MOVE: {
2463            switch (mTouchMode) {
2464            case TOUCH_MODE_DOWN:
2465                final int pointerIndex = ev.findPointerIndex(mActivePointerId);
2466                final int y = (int) ev.getY(pointerIndex);
2467                if (startScrollIfNeeded(y - mMotionY)) {
2468                    return true;
2469                }
2470                break;
2471            }
2472            break;
2473        }
2474
2475        case MotionEvent.ACTION_UP: {
2476            mTouchMode = TOUCH_MODE_REST;
2477            mActivePointerId = INVALID_POINTER;
2478            reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
2479            break;
2480        }
2481
2482        case MotionEvent.ACTION_POINTER_UP: {
2483            onSecondaryPointerUp(ev);
2484            break;
2485        }
2486        }
2487
2488        return false;
2489    }
2490
2491    private void onSecondaryPointerUp(MotionEvent ev) {
2492        final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
2493                MotionEvent.ACTION_POINTER_INDEX_SHIFT;
2494        final int pointerId = ev.getPointerId(pointerIndex);
2495        if (pointerId == mActivePointerId) {
2496            // This was our active pointer going up. Choose a new
2497            // active pointer and adjust accordingly.
2498            // TODO: Make this decision more intelligent.
2499            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
2500            mMotionX = (int) ev.getX(newPointerIndex);
2501            mMotionY = (int) ev.getY(newPointerIndex);
2502            mActivePointerId = ev.getPointerId(newPointerIndex);
2503            if (mVelocityTracker != null) {
2504                mVelocityTracker.clear();
2505            }
2506        }
2507    }
2508
2509    /**
2510     * {@inheritDoc}
2511     */
2512    @Override
2513    public void addTouchables(ArrayList<View> views) {
2514        final int count = getChildCount();
2515        final int firstPosition = mFirstPosition;
2516        final ListAdapter adapter = mAdapter;
2517
2518        if (adapter == null) {
2519            return;
2520        }
2521
2522        for (int i = 0; i < count; i++) {
2523            final View child = getChildAt(i);
2524            if (adapter.isEnabled(firstPosition + i)) {
2525                views.add(child);
2526            }
2527            child.addTouchables(views);
2528        }
2529    }
2530
2531    /**
2532     * Fires an "on scroll state changed" event to the registered
2533     * {@link android.widget.AbsListView.OnScrollListener}, if any. The state change
2534     * is fired only if the specified state is different from the previously known state.
2535     *
2536     * @param newState The new scroll state.
2537     */
2538    void reportScrollStateChange(int newState) {
2539        if (newState != mLastScrollState) {
2540            if (mOnScrollListener != null) {
2541                mOnScrollListener.onScrollStateChanged(this, newState);
2542                mLastScrollState = newState;
2543            }
2544        }
2545    }
2546
2547    /**
2548     * Responsible for fling behavior. Use {@link #start(int)} to
2549     * initiate a fling. Each frame of the fling is handled in {@link #run()}.
2550     * A FlingRunnable will keep re-posting itself until the fling is done.
2551     *
2552     */
2553    private class FlingRunnable implements Runnable {
2554        /**
2555         * Tracks the decay of a fling scroll
2556         */
2557        private OverScroller mScroller;
2558
2559        /**
2560         * Y value reported by mScroller on the previous fling
2561         */
2562        private int mLastFlingY;
2563
2564        FlingRunnable() {
2565            mScroller = new OverScroller(getContext());
2566        }
2567
2568        void start(int initialVelocity) {
2569            int initialY = initialVelocity < 0 ? Integer.MAX_VALUE : 0;
2570            mLastFlingY = initialY;
2571            mScroller.fling(0, initialY, 0, initialVelocity,
2572                    0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE);
2573            mTouchMode = TOUCH_MODE_FLING;
2574            post(this);
2575
2576            if (PROFILE_FLINGING) {
2577                if (!mFlingProfilingStarted) {
2578                    Debug.startMethodTracing("AbsListViewFling");
2579                    mFlingProfilingStarted = true;
2580                }
2581            }
2582        }
2583
2584        void startSpringback() {
2585            if (mScroller.springback(0, mScrollY, 0, 0, 0, 0)) {
2586                mTouchMode = TOUCH_MODE_OVERFLING;
2587                invalidate();
2588                post(this);
2589            }
2590        }
2591
2592        void startOverfling(int initialVelocity) {
2593            mScroller.fling(0, mScrollY, 0, initialVelocity, 0, 0, 0, 0, 0, getHeight());
2594            mTouchMode = TOUCH_MODE_OVERFLING;
2595            invalidate();
2596            post(this);
2597        }
2598
2599        void startScroll(int distance, int duration) {
2600            int initialY = distance < 0 ? Integer.MAX_VALUE : 0;
2601            mLastFlingY = initialY;
2602            mScroller.startScroll(0, initialY, 0, distance, duration);
2603            mTouchMode = TOUCH_MODE_FLING;
2604            post(this);
2605        }
2606
2607        private void endFling() {
2608            mTouchMode = TOUCH_MODE_REST;
2609
2610            reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
2611            clearScrollingCache();
2612
2613            removeCallbacks(this);
2614
2615            if (mPositionScroller != null) {
2616                removeCallbacks(mPositionScroller);
2617            }
2618        }
2619
2620        public void run() {
2621            switch (mTouchMode) {
2622            default:
2623                return;
2624
2625            case TOUCH_MODE_FLING: {
2626                if (mItemCount == 0 || getChildCount() == 0) {
2627                    endFling();
2628                    return;
2629                }
2630
2631                final OverScroller scroller = mScroller;
2632                boolean more = scroller.computeScrollOffset();
2633                final int y = scroller.getCurrY();
2634
2635                // Flip sign to convert finger direction to list items direction
2636                // (e.g. finger moving down means list is moving towards the top)
2637                int delta = mLastFlingY - y;
2638
2639                // Pretend that each frame of a fling scroll is a touch scroll
2640                if (delta > 0) {
2641                    // List is moving towards the top. Use first view as mMotionPosition
2642                    mMotionPosition = mFirstPosition;
2643                    final View firstView = getChildAt(0);
2644                    mMotionViewOriginalTop = firstView.getTop();
2645
2646                    // Don't fling more than 1 screen
2647                    delta = Math.min(getHeight() - mPaddingBottom - mPaddingTop - 1, delta);
2648                } else {
2649                    // List is moving towards the bottom. Use last view as mMotionPosition
2650                    int offsetToLast = getChildCount() - 1;
2651                    mMotionPosition = mFirstPosition + offsetToLast;
2652
2653                    final View lastView = getChildAt(offsetToLast);
2654                    mMotionViewOriginalTop = lastView.getTop();
2655
2656                    // Don't fling more than 1 screen
2657                    delta = Math.max(-(getHeight() - mPaddingBottom - mPaddingTop - 1), delta);
2658                }
2659
2660                // Check to see if we have bumped into the scroll limit
2661                View motionView = getChildAt(mMotionPosition - mFirstPosition);
2662                int oldTop = 0;
2663                if (motionView != null) {
2664                    oldTop = motionView.getTop();
2665                }
2666                if (trackMotionScroll(delta, delta)) {
2667                    if (motionView != null) {
2668                        // Tweak the scroll for how far we overshot
2669                        int overshoot = -(delta - (motionView.getTop() - oldTop));
2670                        overscrollBy(0, overshoot, 0, mScrollY, 0, 0, 0, getOverscrollMax());
2671                    }
2672                    float vel = scroller.getCurrVelocity();
2673                    if (delta > 0) {
2674                        vel = -vel;
2675                    }
2676                    startOverfling(Math.round(vel));
2677                    break;
2678                }
2679
2680                if (more) {
2681                    invalidate();
2682                    mLastFlingY = y;
2683                    post(this);
2684                } else {
2685                    endFling();
2686
2687                    if (PROFILE_FLINGING) {
2688                        if (mFlingProfilingStarted) {
2689                            Debug.stopMethodTracing();
2690                            mFlingProfilingStarted = false;
2691                        }
2692                    }
2693                }
2694                break;
2695            }
2696
2697            case TOUCH_MODE_OVERFLING: {
2698                final OverScroller scroller = mScroller;
2699                if (scroller.computeScrollOffset()) {
2700                    final int scrollY = mScrollY;
2701                    final int deltaY = scroller.getCurrY() - scrollY;
2702                    if (overscrollBy(0, deltaY, 0, scrollY, 0, 0, 0, getOverscrollMax())) {
2703                        startSpringback();
2704                    } else {
2705                        invalidate();
2706                        post(this);
2707                    }
2708                } else {
2709                    endFling();
2710                }
2711                break;
2712            }
2713            }
2714
2715        }
2716    }
2717
2718
2719    class PositionScroller implements Runnable {
2720        private static final int SCROLL_DURATION = 400;
2721
2722        private static final int MOVE_DOWN_POS = 1;
2723        private static final int MOVE_UP_POS = 2;
2724        private static final int MOVE_DOWN_BOUND = 3;
2725        private static final int MOVE_UP_BOUND = 4;
2726
2727        private int mMode;
2728        private int mTargetPos;
2729        private int mBoundPos;
2730        private int mLastSeenPos;
2731        private int mScrollDuration;
2732        private int mExtraScroll;
2733
2734        PositionScroller() {
2735            mExtraScroll = ViewConfiguration.get(mContext).getScaledFadingEdgeLength();
2736        }
2737
2738        void start(int position) {
2739            final int firstPos = mFirstPosition;
2740            final int lastPos = firstPos + getChildCount() - 1;
2741
2742            int viewTravelCount = 0;
2743            if (position <= firstPos) {
2744                viewTravelCount = firstPos - position + 1;
2745                mMode = MOVE_UP_POS;
2746            } else if (position >= lastPos) {
2747                viewTravelCount = position - lastPos + 1;
2748                mMode = MOVE_DOWN_POS;
2749            } else {
2750                // Already on screen, nothing to do
2751                return;
2752            }
2753
2754            if (viewTravelCount > 0) {
2755                mScrollDuration = SCROLL_DURATION / viewTravelCount;
2756            } else {
2757                mScrollDuration = SCROLL_DURATION;
2758            }
2759            mTargetPos = position;
2760            mBoundPos = INVALID_POSITION;
2761            mLastSeenPos = INVALID_POSITION;
2762
2763            post(this);
2764        }
2765
2766        void start(int position, int boundPosition) {
2767            if (boundPosition == INVALID_POSITION) {
2768                start(position);
2769                return;
2770            }
2771
2772            final int firstPos = mFirstPosition;
2773            final int lastPos = firstPos + getChildCount() - 1;
2774
2775            int viewTravelCount = 0;
2776            if (position < firstPos) {
2777                final int boundPosFromLast = lastPos - boundPosition;
2778                if (boundPosFromLast < 1) {
2779                    // Moving would shift our bound position off the screen. Abort.
2780                    return;
2781                }
2782
2783                final int posTravel = firstPos - position + 1;
2784                final int boundTravel = boundPosFromLast - 1;
2785                if (boundTravel < posTravel) {
2786                    viewTravelCount = boundTravel;
2787                    mMode = MOVE_UP_BOUND;
2788                } else {
2789                    viewTravelCount = posTravel;
2790                    mMode = MOVE_UP_POS;
2791                }
2792            } else if (position > lastPos) {
2793                final int boundPosFromFirst = boundPosition - firstPos;
2794                if (boundPosFromFirst < 1) {
2795                    // Moving would shift our bound position off the screen. Abort.
2796                    return;
2797                }
2798
2799                final int posTravel = position - lastPos + 1;
2800                final int boundTravel = boundPosFromFirst - 1;
2801                if (boundTravel < posTravel) {
2802                    viewTravelCount = boundTravel;
2803                    mMode = MOVE_DOWN_BOUND;
2804                } else {
2805                    viewTravelCount = posTravel;
2806                    mMode = MOVE_DOWN_POS;
2807                }
2808            } else {
2809                // Already on screen, nothing to do
2810                return;
2811            }
2812
2813            if (viewTravelCount > 0) {
2814                mScrollDuration = SCROLL_DURATION / viewTravelCount;
2815            } else {
2816                mScrollDuration = SCROLL_DURATION;
2817            }
2818            mTargetPos = position;
2819            mBoundPos = boundPosition;
2820            mLastSeenPos = INVALID_POSITION;
2821
2822            post(this);
2823        }
2824
2825        void stop() {
2826            removeCallbacks(this);
2827        }
2828
2829        public void run() {
2830            final int listHeight = getHeight();
2831            final int firstPos = mFirstPosition;
2832
2833            switch (mMode) {
2834            case MOVE_DOWN_POS: {
2835                final int lastViewIndex = getChildCount() - 1;
2836                final int lastPos = firstPos + lastViewIndex;
2837
2838                if (lastViewIndex < 0) {
2839                    return;
2840                }
2841
2842                if (lastPos == mLastSeenPos) {
2843                    // No new views, let things keep going.
2844                    post(this);
2845                    return;
2846                }
2847
2848                final View lastView = getChildAt(lastViewIndex);
2849                final int lastViewHeight = lastView.getHeight();
2850                final int lastViewTop = lastView.getTop();
2851                final int lastViewPixelsShowing = listHeight - lastViewTop;
2852
2853                smoothScrollBy(lastViewHeight - lastViewPixelsShowing + mExtraScroll,
2854                        mScrollDuration);
2855
2856                mLastSeenPos = lastPos;
2857                if (lastPos != mTargetPos) {
2858                    post(this);
2859                }
2860                break;
2861            }
2862
2863            case MOVE_DOWN_BOUND: {
2864                final int nextViewIndex = 1;
2865                if (firstPos == mBoundPos || getChildCount() <= nextViewIndex) {
2866                    return;
2867                }
2868                final int nextPos = firstPos + nextViewIndex;
2869
2870                if (nextPos == mLastSeenPos) {
2871                    // No new views, let things keep going.
2872                    post(this);
2873                    return;
2874                }
2875
2876                final View nextView = getChildAt(nextViewIndex);
2877                final int nextViewHeight = nextView.getHeight();
2878                final int nextViewTop = nextView.getTop();
2879                final int extraScroll = mExtraScroll;
2880                if (nextPos != mBoundPos) {
2881                    smoothScrollBy(Math.max(0, nextViewHeight + nextViewTop - extraScroll),
2882                            mScrollDuration);
2883
2884                    mLastSeenPos = nextPos;
2885
2886                    post(this);
2887                } else  {
2888                    if (nextViewTop > extraScroll) {
2889                        smoothScrollBy(nextViewTop - extraScroll, mScrollDuration);
2890                    }
2891                }
2892                break;
2893            }
2894
2895            case MOVE_UP_POS: {
2896                if (firstPos == mLastSeenPos) {
2897                    // No new views, let things keep going.
2898                    post(this);
2899                    return;
2900                }
2901
2902                final View firstView = getChildAt(0);
2903                if (firstView == null) {
2904                    return;
2905                }
2906                final int firstViewTop = firstView.getTop();
2907
2908                smoothScrollBy(firstViewTop - mExtraScroll, mScrollDuration);
2909
2910                mLastSeenPos = firstPos;
2911
2912                if (firstPos != mTargetPos) {
2913                    post(this);
2914                }
2915                break;
2916            }
2917
2918            case MOVE_UP_BOUND: {
2919                final int lastViewIndex = getChildCount() - 2;
2920                if (lastViewIndex < 0) {
2921                    return;
2922                }
2923                final int lastPos = firstPos + lastViewIndex;
2924
2925                if (lastPos == mLastSeenPos) {
2926                    // No new views, let things keep going.
2927                    post(this);
2928                    return;
2929                }
2930
2931                final View lastView = getChildAt(lastViewIndex);
2932                final int lastViewHeight = lastView.getHeight();
2933                final int lastViewTop = lastView.getTop();
2934                final int lastViewPixelsShowing = listHeight - lastViewTop;
2935                mLastSeenPos = lastPos;
2936                if (lastPos != mBoundPos) {
2937                    smoothScrollBy(-(lastViewPixelsShowing - mExtraScroll), mScrollDuration);
2938                    post(this);
2939                } else {
2940                    final int bottom = listHeight - mExtraScroll;
2941                    final int lastViewBottom = lastViewTop + lastViewHeight;
2942                    if (bottom > lastViewBottom) {
2943                        smoothScrollBy(-(bottom - lastViewBottom), mScrollDuration);
2944                    }
2945                }
2946                break;
2947            }
2948
2949            default:
2950                break;
2951            }
2952        }
2953    }
2954
2955    /**
2956     * Smoothly scroll to the specified adapter position. The view will
2957     * scroll such that the indicated position is displayed.
2958     * @param position Scroll to this adapter position.
2959     */
2960    public void smoothScrollToPosition(int position) {
2961        if (mPositionScroller == null) {
2962            mPositionScroller = new PositionScroller();
2963        }
2964        mPositionScroller.start(position);
2965    }
2966
2967    /**
2968     * Smoothly scroll to the specified adapter position. The view will
2969     * scroll such that the indicated position is displayed, but it will
2970     * stop early if scrolling further would scroll boundPosition out of
2971     * view.
2972     * @param position Scroll to this adapter position.
2973     * @param boundPosition Do not scroll if it would move this adapter
2974     *          position out of view.
2975     */
2976    public void smoothScrollToPosition(int position, int boundPosition) {
2977        if (mPositionScroller == null) {
2978            mPositionScroller = new PositionScroller();
2979        }
2980        mPositionScroller.start(position, boundPosition);
2981    }
2982
2983    /**
2984     * Smoothly scroll by distance pixels over duration milliseconds.
2985     * @param distance Distance to scroll in pixels.
2986     * @param duration Duration of the scroll animation in milliseconds.
2987     */
2988    public void smoothScrollBy(int distance, int duration) {
2989        if (mFlingRunnable == null) {
2990            mFlingRunnable = new FlingRunnable();
2991        } else {
2992            mFlingRunnable.endFling();
2993        }
2994        mFlingRunnable.startScroll(distance, duration);
2995    }
2996
2997    private void createScrollingCache() {
2998        if (mScrollingCacheEnabled && !mCachingStarted) {
2999            setChildrenDrawnWithCacheEnabled(true);
3000            setChildrenDrawingCacheEnabled(true);
3001            mCachingStarted = true;
3002        }
3003    }
3004
3005    private void clearScrollingCache() {
3006        if (mClearScrollingCache == null) {
3007            mClearScrollingCache = new Runnable() {
3008                public void run() {
3009                    if (mCachingStarted) {
3010                        mCachingStarted = false;
3011                        setChildrenDrawnWithCacheEnabled(false);
3012                        if ((mPersistentDrawingCache & PERSISTENT_SCROLLING_CACHE) == 0) {
3013                            setChildrenDrawingCacheEnabled(false);
3014                        }
3015                        if (!isAlwaysDrawnWithCacheEnabled()) {
3016                            invalidate();
3017                        }
3018                    }
3019                }
3020            };
3021        }
3022        post(mClearScrollingCache);
3023    }
3024
3025    /**
3026     * Track a motion scroll
3027     *
3028     * @param deltaY Amount to offset mMotionView. This is the accumulated delta since the motion
3029     *        began. Positive numbers mean the user's finger is moving down the screen.
3030     * @param incrementalDeltaY Change in deltaY from the previous event.
3031     * @return true if we're already at the beginning/end of the list and have nothing to do.
3032     */
3033    boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
3034        final int childCount = getChildCount();
3035        if (childCount == 0) {
3036            return true;
3037        }
3038
3039        final int firstTop = getChildAt(0).getTop();
3040        final int lastBottom = getChildAt(childCount - 1).getBottom();
3041
3042        final Rect listPadding = mListPadding;
3043
3044         // FIXME account for grid vertical spacing too?
3045        final int spaceAbove = listPadding.top - firstTop;
3046        final int end = getHeight() - listPadding.bottom;
3047        final int spaceBelow = lastBottom - end;
3048
3049        final int height = getHeight() - mPaddingBottom - mPaddingTop;
3050        if (deltaY < 0) {
3051            deltaY = Math.max(-(height - 1), deltaY);
3052        } else {
3053            deltaY = Math.min(height - 1, deltaY);
3054        }
3055
3056        if (incrementalDeltaY < 0) {
3057            incrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY);
3058        } else {
3059            incrementalDeltaY = Math.min(height - 1, incrementalDeltaY);
3060        }
3061
3062        final int firstPosition = mFirstPosition;
3063
3064        if (firstPosition == 0 && firstTop >= listPadding.top && deltaY > 0) {
3065            // Don't need to move views down if the top of the first position
3066            // is already visible
3067            return true;
3068        }
3069
3070        if (firstPosition + childCount == mItemCount && lastBottom <= end && deltaY < 0) {
3071            // Don't need to move views up if the bottom of the last position
3072            // is already visible
3073            return true;
3074        }
3075
3076        final boolean down = incrementalDeltaY < 0;
3077
3078        hideSelector();
3079
3080        final int headerViewsCount = getHeaderViewsCount();
3081        final int footerViewsStart = mItemCount - getFooterViewsCount();
3082
3083        int start = 0;
3084        int count = 0;
3085
3086        if (down) {
3087            final int top = listPadding.top - incrementalDeltaY;
3088            for (int i = 0; i < childCount; i++) {
3089                final View child = getChildAt(i);
3090                if (child.getBottom() >= top) {
3091                    break;
3092                } else {
3093                    count++;
3094                    int position = firstPosition + i;
3095                    if (position >= headerViewsCount && position < footerViewsStart) {
3096                        mRecycler.addScrapView(child);
3097
3098                        if (ViewDebug.TRACE_RECYCLER) {
3099                            ViewDebug.trace(child,
3100                                    ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP,
3101                                    firstPosition + i, -1);
3102                        }
3103                    }
3104                }
3105            }
3106        } else {
3107            final int bottom = getHeight() - listPadding.bottom - incrementalDeltaY;
3108            for (int i = childCount - 1; i >= 0; i--) {
3109                final View child = getChildAt(i);
3110                if (child.getTop() <= bottom) {
3111                    break;
3112                } else {
3113                    start = i;
3114                    count++;
3115                    int position = firstPosition + i;
3116                    if (position >= headerViewsCount && position < footerViewsStart) {
3117                        mRecycler.addScrapView(child);
3118
3119                        if (ViewDebug.TRACE_RECYCLER) {
3120                            ViewDebug.trace(child,
3121                                    ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP,
3122                                    firstPosition + i, -1);
3123                        }
3124                    }
3125                }
3126            }
3127        }
3128
3129        mMotionViewNewTop = mMotionViewOriginalTop + deltaY;
3130
3131        mBlockLayoutRequests = true;
3132
3133        if (count > 0) {
3134            detachViewsFromParent(start, count);
3135        }
3136        offsetChildrenTopAndBottom(incrementalDeltaY);
3137
3138        if (down) {
3139            mFirstPosition += count;
3140        }
3141
3142        invalidate();
3143
3144        final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
3145        if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
3146            fillGap(down);
3147        }
3148
3149        mBlockLayoutRequests = false;
3150
3151        invokeOnItemScrollListener();
3152        awakenScrollBars();
3153
3154        return false;
3155    }
3156
3157    /**
3158     * Returns the number of header views in the list. Header views are special views
3159     * at the top of the list that should not be recycled during a layout.
3160     *
3161     * @return The number of header views, 0 in the default implementation.
3162     */
3163    int getHeaderViewsCount() {
3164        return 0;
3165    }
3166
3167    /**
3168     * Returns the number of footer views in the list. Footer views are special views
3169     * at the bottom of the list that should not be recycled during a layout.
3170     *
3171     * @return The number of footer views, 0 in the default implementation.
3172     */
3173    int getFooterViewsCount() {
3174        return 0;
3175    }
3176
3177    /**
3178     * Fills the gap left open by a touch-scroll. During a touch scroll, children that
3179     * remain on screen are shifted and the other ones are discarded. The role of this
3180     * method is to fill the gap thus created by performing a partial layout in the
3181     * empty space.
3182     *
3183     * @param down true if the scroll is going down, false if it is going up
3184     */
3185    abstract void fillGap(boolean down);
3186
3187    void hideSelector() {
3188        if (mSelectedPosition != INVALID_POSITION) {
3189            if (mLayoutMode != LAYOUT_SPECIFIC) {
3190                mResurrectToPosition = mSelectedPosition;
3191            }
3192            if (mNextSelectedPosition >= 0 && mNextSelectedPosition != mSelectedPosition) {
3193                mResurrectToPosition = mNextSelectedPosition;
3194            }
3195            setSelectedPositionInt(INVALID_POSITION);
3196            setNextSelectedPositionInt(INVALID_POSITION);
3197            mSelectedTop = 0;
3198            mSelectorRect.setEmpty();
3199        }
3200    }
3201
3202    /**
3203     * @return A position to select. First we try mSelectedPosition. If that has been clobbered by
3204     * entering touch mode, we then try mResurrectToPosition. Values are pinned to the range
3205     * of items available in the adapter
3206     */
3207    int reconcileSelectedPosition() {
3208        int position = mSelectedPosition;
3209        if (position < 0) {
3210            position = mResurrectToPosition;
3211        }
3212        position = Math.max(0, position);
3213        position = Math.min(position, mItemCount - 1);
3214        return position;
3215    }
3216
3217    /**
3218     * Find the row closest to y. This row will be used as the motion row when scrolling
3219     *
3220     * @param y Where the user touched
3221     * @return The position of the first (or only) item in the row containing y
3222     */
3223    abstract int findMotionRow(int y);
3224
3225    /**
3226     * Find the row closest to y. This row will be used as the motion row when scrolling.
3227     *
3228     * @param y Where the user touched
3229     * @return The position of the first (or only) item in the row closest to y
3230     */
3231    int findClosestMotionRow(int y) {
3232        final int childCount = getChildCount();
3233        if (childCount == 0) {
3234            return INVALID_POSITION;
3235        }
3236
3237        final int motionRow = findMotionRow(y);
3238        return motionRow != INVALID_POSITION ? motionRow : mFirstPosition + childCount - 1;
3239    }
3240
3241    /**
3242     * Causes all the views to be rebuilt and redrawn.
3243     */
3244    public void invalidateViews() {
3245        mDataChanged = true;
3246        rememberSyncState();
3247        requestLayout();
3248        invalidate();
3249    }
3250
3251    /**
3252     * Makes the item at the supplied position selected.
3253     *
3254     * @param position the position of the new selection
3255     */
3256    abstract void setSelectionInt(int position);
3257
3258    /**
3259     * Attempt to bring the selection back if the user is switching from touch
3260     * to trackball mode
3261     * @return Whether selection was set to something.
3262     */
3263    boolean resurrectSelection() {
3264        final int childCount = getChildCount();
3265
3266        if (childCount <= 0) {
3267            return false;
3268        }
3269
3270        int selectedTop = 0;
3271        int selectedPos;
3272        int childrenTop = mListPadding.top;
3273        int childrenBottom = mBottom - mTop - mListPadding.bottom;
3274        final int firstPosition = mFirstPosition;
3275        final int toPosition = mResurrectToPosition;
3276        boolean down = true;
3277
3278        if (toPosition >= firstPosition && toPosition < firstPosition + childCount) {
3279            selectedPos = toPosition;
3280
3281            final View selected = getChildAt(selectedPos - mFirstPosition);
3282            selectedTop = selected.getTop();
3283            int selectedBottom = selected.getBottom();
3284
3285            // We are scrolled, don't get in the fade
3286            if (selectedTop < childrenTop) {
3287                selectedTop = childrenTop + getVerticalFadingEdgeLength();
3288            } else if (selectedBottom > childrenBottom) {
3289                selectedTop = childrenBottom - selected.getMeasuredHeight()
3290                        - getVerticalFadingEdgeLength();
3291            }
3292        } else {
3293            if (toPosition < firstPosition) {
3294                // Default to selecting whatever is first
3295                selectedPos = firstPosition;
3296                for (int i = 0; i < childCount; i++) {
3297                    final View v = getChildAt(i);
3298                    final int top = v.getTop();
3299
3300                    if (i == 0) {
3301                        // Remember the position of the first item
3302                        selectedTop = top;
3303                        // See if we are scrolled at all
3304                        if (firstPosition > 0 || top < childrenTop) {
3305                            // If we are scrolled, don't select anything that is
3306                            // in the fade region
3307                            childrenTop += getVerticalFadingEdgeLength();
3308                        }
3309                    }
3310                    if (top >= childrenTop) {
3311                        // Found a view whose top is fully visisble
3312                        selectedPos = firstPosition + i;
3313                        selectedTop = top;
3314                        break;
3315                    }
3316                }
3317            } else {
3318                final int itemCount = mItemCount;
3319                down = false;
3320                selectedPos = firstPosition + childCount - 1;
3321
3322                for (int i = childCount - 1; i >= 0; i--) {
3323                    final View v = getChildAt(i);
3324                    final int top = v.getTop();
3325                    final int bottom = v.getBottom();
3326
3327                    if (i == childCount - 1) {
3328                        selectedTop = top;
3329                        if (firstPosition + childCount < itemCount || bottom > childrenBottom) {
3330                            childrenBottom -= getVerticalFadingEdgeLength();
3331                        }
3332                    }
3333
3334                    if (bottom <= childrenBottom) {
3335                        selectedPos = firstPosition + i;
3336                        selectedTop = top;
3337                        break;
3338                    }
3339                }
3340            }
3341        }
3342
3343        mResurrectToPosition = INVALID_POSITION;
3344        removeCallbacks(mFlingRunnable);
3345        mTouchMode = TOUCH_MODE_REST;
3346        clearScrollingCache();
3347        mSpecificTop = selectedTop;
3348        selectedPos = lookForSelectablePosition(selectedPos, down);
3349        if (selectedPos >= firstPosition && selectedPos <= getLastVisiblePosition()) {
3350            mLayoutMode = LAYOUT_SPECIFIC;
3351            setSelectionInt(selectedPos);
3352            invokeOnItemScrollListener();
3353        } else {
3354            selectedPos = INVALID_POSITION;
3355        }
3356        reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
3357
3358        return selectedPos >= 0;
3359    }
3360
3361    @Override
3362    protected void handleDataChanged() {
3363        int count = mItemCount;
3364        if (count > 0) {
3365
3366            int newPos;
3367
3368            int selectablePos;
3369
3370            // Find the row we are supposed to sync to
3371            if (mNeedSync) {
3372                // Update this first, since setNextSelectedPositionInt inspects it
3373                mNeedSync = false;
3374
3375                if (mTranscriptMode == TRANSCRIPT_MODE_ALWAYS_SCROLL ||
3376                        (mTranscriptMode == TRANSCRIPT_MODE_NORMAL &&
3377                                mFirstPosition + getChildCount() >= mOldItemCount)) {
3378                    mLayoutMode = LAYOUT_FORCE_BOTTOM;
3379                    return;
3380                }
3381
3382                switch (mSyncMode) {
3383                case SYNC_SELECTED_POSITION:
3384                    if (isInTouchMode()) {
3385                        // We saved our state when not in touch mode. (We know this because
3386                        // mSyncMode is SYNC_SELECTED_POSITION.) Now we are trying to
3387                        // restore in touch mode. Just leave mSyncPosition as it is (possibly
3388                        // adjusting if the available range changed) and return.
3389                        mLayoutMode = LAYOUT_SYNC;
3390                        mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
3391
3392                        return;
3393                    } else {
3394                        // See if we can find a position in the new data with the same
3395                        // id as the old selection. This will change mSyncPosition.
3396                        newPos = findSyncPosition();
3397                        if (newPos >= 0) {
3398                            // Found it. Now verify that new selection is still selectable
3399                            selectablePos = lookForSelectablePosition(newPos, true);
3400                            if (selectablePos == newPos) {
3401                                // Same row id is selected
3402                                mSyncPosition = newPos;
3403
3404                                if (mSyncHeight == getHeight()) {
3405                                    // If we are at the same height as when we saved state, try
3406                                    // to restore the scroll position too.
3407                                    mLayoutMode = LAYOUT_SYNC;
3408                                } else {
3409                                    // We are not the same height as when the selection was saved, so
3410                                    // don't try to restore the exact position
3411                                    mLayoutMode = LAYOUT_SET_SELECTION;
3412                                }
3413
3414                                // Restore selection
3415                                setNextSelectedPositionInt(newPos);
3416                                return;
3417                            }
3418                        }
3419                    }
3420                    break;
3421                case SYNC_FIRST_POSITION:
3422                    // Leave mSyncPosition as it is -- just pin to available range
3423                    mLayoutMode = LAYOUT_SYNC;
3424                    mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
3425
3426                    return;
3427                }
3428            }
3429
3430            if (!isInTouchMode()) {
3431                // We couldn't find matching data -- try to use the same position
3432                newPos = getSelectedItemPosition();
3433
3434                // Pin position to the available range
3435                if (newPos >= count) {
3436                    newPos = count - 1;
3437                }
3438                if (newPos < 0) {
3439                    newPos = 0;
3440                }
3441
3442                // Make sure we select something selectable -- first look down
3443                selectablePos = lookForSelectablePosition(newPos, true);
3444
3445                if (selectablePos >= 0) {
3446                    setNextSelectedPositionInt(selectablePos);
3447                    return;
3448                } else {
3449                    // Looking down didn't work -- try looking up
3450                    selectablePos = lookForSelectablePosition(newPos, false);
3451                    if (selectablePos >= 0) {
3452                        setNextSelectedPositionInt(selectablePos);
3453                        return;
3454                    }
3455                }
3456            } else {
3457
3458                // We already know where we want to resurrect the selection
3459                if (mResurrectToPosition >= 0) {
3460                    return;
3461                }
3462            }
3463
3464        }
3465
3466        // Nothing is selected. Give up and reset everything.
3467        mLayoutMode = mStackFromBottom ? LAYOUT_FORCE_BOTTOM : LAYOUT_FORCE_TOP;
3468        mSelectedPosition = INVALID_POSITION;
3469        mSelectedRowId = INVALID_ROW_ID;
3470        mNextSelectedPosition = INVALID_POSITION;
3471        mNextSelectedRowId = INVALID_ROW_ID;
3472        mNeedSync = false;
3473        checkSelectionChanged();
3474    }
3475
3476    @Override
3477    protected void onDisplayHint(int hint) {
3478        super.onDisplayHint(hint);
3479        switch (hint) {
3480            case INVISIBLE:
3481                if (mPopup != null && mPopup.isShowing()) {
3482                    dismissPopup();
3483                }
3484                break;
3485            case VISIBLE:
3486                if (mFiltered && mPopup != null && !mPopup.isShowing()) {
3487                    showPopup();
3488                }
3489                break;
3490        }
3491        mPopupHidden = hint == INVISIBLE;
3492    }
3493
3494    /**
3495     * Removes the filter window
3496     */
3497    private void dismissPopup() {
3498        if (mPopup != null) {
3499            mPopup.dismiss();
3500        }
3501        mPopupHidden = false;
3502    }
3503
3504    /**
3505     * Shows the filter window
3506     */
3507    private void showPopup() {
3508        // Make sure we have a window before showing the popup
3509        if (getWindowVisibility() == View.VISIBLE) {
3510            createTextFilter(true);
3511            positionPopup();
3512            // Make sure we get focus if we are showing the popup
3513            checkFocus();
3514        }
3515    }
3516
3517    private void positionPopup() {
3518        int screenHeight = getResources().getDisplayMetrics().heightPixels;
3519        final int[] xy = new int[2];
3520        getLocationOnScreen(xy);
3521        // TODO: The 20 below should come from the theme
3522        // TODO: And the gravity should be defined in the theme as well
3523        final int bottomGap = screenHeight - xy[1] - getHeight() + (int) (mDensityScale * 20);
3524        if (!mPopup.isShowing()) {
3525            mPopup.showAtLocation(this, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL,
3526                    xy[0], bottomGap);
3527        } else {
3528            mPopup.update(xy[0], bottomGap, -1, -1);
3529        }
3530    }
3531
3532    /**
3533     * What is the distance between the source and destination rectangles given the direction of
3534     * focus navigation between them? The direction basically helps figure out more quickly what is
3535     * self evident by the relationship between the rects...
3536     *
3537     * @param source the source rectangle
3538     * @param dest the destination rectangle
3539     * @param direction the direction
3540     * @return the distance between the rectangles
3541     */
3542    static int getDistance(Rect source, Rect dest, int direction) {
3543        int sX, sY; // source x, y
3544        int dX, dY; // dest x, y
3545        switch (direction) {
3546        case View.FOCUS_RIGHT:
3547            sX = source.right;
3548            sY = source.top + source.height() / 2;
3549            dX = dest.left;
3550            dY = dest.top + dest.height() / 2;
3551            break;
3552        case View.FOCUS_DOWN:
3553            sX = source.left + source.width() / 2;
3554            sY = source.bottom;
3555            dX = dest.left + dest.width() / 2;
3556            dY = dest.top;
3557            break;
3558        case View.FOCUS_LEFT:
3559            sX = source.left;
3560            sY = source.top + source.height() / 2;
3561            dX = dest.right;
3562            dY = dest.top + dest.height() / 2;
3563            break;
3564        case View.FOCUS_UP:
3565            sX = source.left + source.width() / 2;
3566            sY = source.top;
3567            dX = dest.left + dest.width() / 2;
3568            dY = dest.bottom;
3569            break;
3570        default:
3571            throw new IllegalArgumentException("direction must be one of "
3572                    + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
3573        }
3574        int deltaX = dX - sX;
3575        int deltaY = dY - sY;
3576        return deltaY * deltaY + deltaX * deltaX;
3577    }
3578
3579    @Override
3580    protected boolean isInFilterMode() {
3581        return mFiltered;
3582    }
3583
3584    /**
3585     * Sends a key to the text filter window
3586     *
3587     * @param keyCode The keycode for the event
3588     * @param event The actual key event
3589     *
3590     * @return True if the text filter handled the event, false otherwise.
3591     */
3592    boolean sendToTextFilter(int keyCode, int count, KeyEvent event) {
3593        if (!acceptFilter()) {
3594            return false;
3595        }
3596
3597        boolean handled = false;
3598        boolean okToSend = true;
3599        switch (keyCode) {
3600        case KeyEvent.KEYCODE_DPAD_UP:
3601        case KeyEvent.KEYCODE_DPAD_DOWN:
3602        case KeyEvent.KEYCODE_DPAD_LEFT:
3603        case KeyEvent.KEYCODE_DPAD_RIGHT:
3604        case KeyEvent.KEYCODE_DPAD_CENTER:
3605        case KeyEvent.KEYCODE_ENTER:
3606            okToSend = false;
3607            break;
3608        case KeyEvent.KEYCODE_BACK:
3609            if (mFiltered && mPopup != null && mPopup.isShowing()) {
3610                if (event.getAction() == KeyEvent.ACTION_DOWN
3611                        && event.getRepeatCount() == 0) {
3612                    getKeyDispatcherState().startTracking(event, this);
3613                    handled = true;
3614                } else if (event.getAction() == KeyEvent.ACTION_UP
3615                        && event.isTracking() && !event.isCanceled()) {
3616                    handled = true;
3617                    mTextFilter.setText("");
3618                }
3619            }
3620            okToSend = false;
3621            break;
3622        case KeyEvent.KEYCODE_SPACE:
3623            // Only send spaces once we are filtered
3624            okToSend = mFiltered;
3625            break;
3626        }
3627
3628        if (okToSend) {
3629            createTextFilter(true);
3630
3631            KeyEvent forwardEvent = event;
3632            if (forwardEvent.getRepeatCount() > 0) {
3633                forwardEvent = KeyEvent.changeTimeRepeat(event, event.getEventTime(), 0);
3634            }
3635
3636            int action = event.getAction();
3637            switch (action) {
3638                case KeyEvent.ACTION_DOWN:
3639                    handled = mTextFilter.onKeyDown(keyCode, forwardEvent);
3640                    break;
3641
3642                case KeyEvent.ACTION_UP:
3643                    handled = mTextFilter.onKeyUp(keyCode, forwardEvent);
3644                    break;
3645
3646                case KeyEvent.ACTION_MULTIPLE:
3647                    handled = mTextFilter.onKeyMultiple(keyCode, count, event);
3648                    break;
3649            }
3650        }
3651        return handled;
3652    }
3653
3654    /**
3655     * Return an InputConnection for editing of the filter text.
3656     */
3657    @Override
3658    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
3659        if (isTextFilterEnabled()) {
3660            // XXX we need to have the text filter created, so we can get an
3661            // InputConnection to proxy to.  Unfortunately this means we pretty
3662            // much need to make it as soon as a list view gets focus.
3663            createTextFilter(false);
3664            if (mPublicInputConnection == null) {
3665                mDefInputConnection = new BaseInputConnection(this, false);
3666                mPublicInputConnection = new InputConnectionWrapper(
3667                        mTextFilter.onCreateInputConnection(outAttrs), true) {
3668                    @Override
3669                    public boolean reportFullscreenMode(boolean enabled) {
3670                        // Use our own input connection, since it is
3671                        // the "real" one the IME is talking with.
3672                        return mDefInputConnection.reportFullscreenMode(enabled);
3673                    }
3674
3675                    @Override
3676                    public boolean performEditorAction(int editorAction) {
3677                        // The editor is off in its own window; we need to be
3678                        // the one that does this.
3679                        if (editorAction == EditorInfo.IME_ACTION_DONE) {
3680                            InputMethodManager imm = (InputMethodManager)
3681                                    getContext().getSystemService(
3682                                            Context.INPUT_METHOD_SERVICE);
3683                            if (imm != null) {
3684                                imm.hideSoftInputFromWindow(getWindowToken(), 0);
3685                            }
3686                            return true;
3687                        }
3688                        return false;
3689                    }
3690
3691                    @Override
3692                    public boolean sendKeyEvent(KeyEvent event) {
3693                        // Use our own input connection, since the filter
3694                        // text view may not be shown in a window so has
3695                        // no ViewRoot to dispatch events with.
3696                        return mDefInputConnection.sendKeyEvent(event);
3697                    }
3698                };
3699            }
3700            outAttrs.inputType = EditorInfo.TYPE_CLASS_TEXT
3701                    | EditorInfo.TYPE_TEXT_VARIATION_FILTER;
3702            outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE;
3703            return mPublicInputConnection;
3704        }
3705        return null;
3706    }
3707
3708    /**
3709     * For filtering we proxy an input connection to an internal text editor,
3710     * and this allows the proxying to happen.
3711     */
3712    @Override
3713    public boolean checkInputConnectionProxy(View view) {
3714        return view == mTextFilter;
3715    }
3716
3717    /**
3718     * Creates the window for the text filter and populates it with an EditText field;
3719     *
3720     * @param animateEntrance true if the window should appear with an animation
3721     */
3722    private void createTextFilter(boolean animateEntrance) {
3723        if (mPopup == null) {
3724            Context c = getContext();
3725            PopupWindow p = new PopupWindow(c);
3726            LayoutInflater layoutInflater = (LayoutInflater)
3727                    c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
3728            mTextFilter = (EditText) layoutInflater.inflate(
3729                    com.android.internal.R.layout.typing_filter, null);
3730            // For some reason setting this as the "real" input type changes
3731            // the text view in some way that it doesn't work, and I don't
3732            // want to figure out why this is.
3733            mTextFilter.setRawInputType(EditorInfo.TYPE_CLASS_TEXT
3734                    | EditorInfo.TYPE_TEXT_VARIATION_FILTER);
3735            mTextFilter.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI);
3736            mTextFilter.addTextChangedListener(this);
3737            p.setFocusable(false);
3738            p.setTouchable(false);
3739            p.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
3740            p.setContentView(mTextFilter);
3741            p.setWidth(LayoutParams.WRAP_CONTENT);
3742            p.setHeight(LayoutParams.WRAP_CONTENT);
3743            p.setBackgroundDrawable(null);
3744            mPopup = p;
3745            getViewTreeObserver().addOnGlobalLayoutListener(this);
3746            mGlobalLayoutListenerAddedFilter = true;
3747        }
3748        if (animateEntrance) {
3749            mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilter);
3750        } else {
3751            mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilterRestore);
3752        }
3753    }
3754
3755    /**
3756     * Clear the text filter.
3757     */
3758    public void clearTextFilter() {
3759        if (mFiltered) {
3760            mTextFilter.setText("");
3761            mFiltered = false;
3762            if (mPopup != null && mPopup.isShowing()) {
3763                dismissPopup();
3764            }
3765        }
3766    }
3767
3768    /**
3769     * Returns if the ListView currently has a text filter.
3770     */
3771    public boolean hasTextFilter() {
3772        return mFiltered;
3773    }
3774
3775    public void onGlobalLayout() {
3776        if (isShown()) {
3777            // Show the popup if we are filtered
3778            if (mFiltered && mPopup != null && !mPopup.isShowing() && !mPopupHidden) {
3779                showPopup();
3780            }
3781        } else {
3782            // Hide the popup when we are no longer visible
3783            if (mPopup != null && mPopup.isShowing()) {
3784                dismissPopup();
3785            }
3786        }
3787
3788    }
3789
3790    /**
3791     * For our text watcher that is associated with the text filter.  Does
3792     * nothing.
3793     */
3794    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
3795    }
3796
3797    /**
3798     * For our text watcher that is associated with the text filter. Performs
3799     * the actual filtering as the text changes, and takes care of hiding and
3800     * showing the popup displaying the currently entered filter text.
3801     */
3802    public void onTextChanged(CharSequence s, int start, int before, int count) {
3803        if (mPopup != null && isTextFilterEnabled()) {
3804            int length = s.length();
3805            boolean showing = mPopup.isShowing();
3806            if (!showing && length > 0) {
3807                // Show the filter popup if necessary
3808                showPopup();
3809                mFiltered = true;
3810            } else if (showing && length == 0) {
3811                // Remove the filter popup if the user has cleared all text
3812                dismissPopup();
3813                mFiltered = false;
3814            }
3815            if (mAdapter instanceof Filterable) {
3816                Filter f = ((Filterable) mAdapter).getFilter();
3817                // Filter should not be null when we reach this part
3818                if (f != null) {
3819                    f.filter(s, this);
3820                } else {
3821                    throw new IllegalStateException("You cannot call onTextChanged with a non "
3822                            + "filterable adapter");
3823                }
3824            }
3825        }
3826    }
3827
3828    /**
3829     * For our text watcher that is associated with the text filter.  Does
3830     * nothing.
3831     */
3832    public void afterTextChanged(Editable s) {
3833    }
3834
3835    public void onFilterComplete(int count) {
3836        if (mSelectedPosition < 0 && count > 0) {
3837            mResurrectToPosition = INVALID_POSITION;
3838            resurrectSelection();
3839        }
3840    }
3841
3842    @Override
3843    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
3844        return new LayoutParams(p);
3845    }
3846
3847    @Override
3848    public LayoutParams generateLayoutParams(AttributeSet attrs) {
3849        return new AbsListView.LayoutParams(getContext(), attrs);
3850    }
3851
3852    @Override
3853    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
3854        return p instanceof AbsListView.LayoutParams;
3855    }
3856
3857    /**
3858     * Puts the list or grid into transcript mode. In this mode the list or grid will always scroll
3859     * to the bottom to show new items.
3860     *
3861     * @param mode the transcript mode to set
3862     *
3863     * @see #TRANSCRIPT_MODE_DISABLED
3864     * @see #TRANSCRIPT_MODE_NORMAL
3865     * @see #TRANSCRIPT_MODE_ALWAYS_SCROLL
3866     */
3867    public void setTranscriptMode(int mode) {
3868        mTranscriptMode = mode;
3869    }
3870
3871    /**
3872     * Returns the current transcript mode.
3873     *
3874     * @return {@link #TRANSCRIPT_MODE_DISABLED}, {@link #TRANSCRIPT_MODE_NORMAL} or
3875     *         {@link #TRANSCRIPT_MODE_ALWAYS_SCROLL}
3876     */
3877    public int getTranscriptMode() {
3878        return mTranscriptMode;
3879    }
3880
3881    @Override
3882    public int getSolidColor() {
3883        return mCacheColorHint;
3884    }
3885
3886    /**
3887     * When set to a non-zero value, the cache color hint indicates that this list is always drawn
3888     * on top of a solid, single-color, opaque background
3889     *
3890     * @param color The background color
3891     */
3892    public void setCacheColorHint(int color) {
3893        if (color != mCacheColorHint) {
3894            mCacheColorHint = color;
3895            int count = getChildCount();
3896            for (int i = 0; i < count; i++) {
3897                getChildAt(i).setDrawingCacheBackgroundColor(color);
3898            }
3899            mRecycler.setCacheColorHint(color);
3900        }
3901    }
3902
3903    /**
3904     * When set to a non-zero value, the cache color hint indicates that this list is always drawn
3905     * on top of a solid, single-color, opaque background
3906     *
3907     * @return The cache color hint
3908     */
3909    public int getCacheColorHint() {
3910        return mCacheColorHint;
3911    }
3912
3913    /**
3914     * Move all views (excluding headers and footers) held by this AbsListView into the supplied
3915     * List. This includes views displayed on the screen as well as views stored in AbsListView's
3916     * internal view recycler.
3917     *
3918     * @param views A list into which to put the reclaimed views
3919     */
3920    public void reclaimViews(List<View> views) {
3921        int childCount = getChildCount();
3922        RecyclerListener listener = mRecycler.mRecyclerListener;
3923
3924        // Reclaim views on screen
3925        for (int i = 0; i < childCount; i++) {
3926            View child = getChildAt(i);
3927            AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
3928            // Don't reclaim header or footer views, or views that should be ignored
3929            if (lp != null && mRecycler.shouldRecycleViewType(lp.viewType)) {
3930                views.add(child);
3931                if (listener != null) {
3932                    // Pretend they went through the scrap heap
3933                    listener.onMovedToScrapHeap(child);
3934                }
3935            }
3936        }
3937        mRecycler.reclaimScrapViews(views);
3938        removeAllViewsInLayout();
3939    }
3940
3941    /**
3942     * @hide
3943     */
3944    @Override
3945    protected boolean onConsistencyCheck(int consistency) {
3946        boolean result = super.onConsistencyCheck(consistency);
3947
3948        final boolean checkLayout = (consistency & ViewDebug.CONSISTENCY_LAYOUT) != 0;
3949
3950        if (checkLayout) {
3951            // The active recycler must be empty
3952            final View[] activeViews = mRecycler.mActiveViews;
3953            int count = activeViews.length;
3954            for (int i = 0; i < count; i++) {
3955                if (activeViews[i] != null) {
3956                    result = false;
3957                    android.util.Log.d(ViewDebug.CONSISTENCY_LOG_TAG,
3958                            "AbsListView " + this + " has a view in its active recycler: " +
3959                                    activeViews[i]);
3960                }
3961            }
3962
3963            // All views in the recycler must NOT be on screen and must NOT have a parent
3964            final ArrayList<View> scrap = mRecycler.mCurrentScrap;
3965            if (!checkScrap(scrap)) result = false;
3966            final ArrayList<View>[] scraps = mRecycler.mScrapViews;
3967            count = scraps.length;
3968            for (int i = 0; i < count; i++) {
3969                if (!checkScrap(scraps[i])) result = false;
3970            }
3971        }
3972
3973        return result;
3974    }
3975
3976    private boolean checkScrap(ArrayList<View> scrap) {
3977        if (scrap == null) return true;
3978        boolean result = true;
3979
3980        final int count = scrap.size();
3981        for (int i = 0; i < count; i++) {
3982            final View view = scrap.get(i);
3983            if (view.getParent() != null) {
3984                result = false;
3985                android.util.Log.d(ViewDebug.CONSISTENCY_LOG_TAG, "AbsListView " + this +
3986                        " has a view in its scrap heap still attached to a parent: " + view);
3987            }
3988            if (indexOfChild(view) >= 0) {
3989                result = false;
3990                android.util.Log.d(ViewDebug.CONSISTENCY_LOG_TAG, "AbsListView " + this +
3991                        " has a view in its scrap heap that is also a direct child: " + view);
3992            }
3993        }
3994
3995        return result;
3996    }
3997
3998    /**
3999     * Sets the recycler listener to be notified whenever a View is set aside in
4000     * the recycler for later reuse. This listener can be used to free resources
4001     * associated to the View.
4002     *
4003     * @param listener The recycler listener to be notified of views set aside
4004     *        in the recycler.
4005     *
4006     * @see android.widget.AbsListView.RecycleBin
4007     * @see android.widget.AbsListView.RecyclerListener
4008     */
4009    public void setRecyclerListener(RecyclerListener listener) {
4010        mRecycler.mRecyclerListener = listener;
4011    }
4012
4013    /**
4014     * AbsListView extends LayoutParams to provide a place to hold the view type.
4015     */
4016    public static class LayoutParams extends ViewGroup.LayoutParams {
4017        /**
4018         * View type for this view, as returned by
4019         * {@link android.widget.Adapter#getItemViewType(int) }
4020         */
4021        @ViewDebug.ExportedProperty(mapping = {
4022            @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_IGNORE, to = "ITEM_VIEW_TYPE_IGNORE"),
4023            @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_HEADER_OR_FOOTER, to = "ITEM_VIEW_TYPE_HEADER_OR_FOOTER")
4024        })
4025        int viewType;
4026
4027        /**
4028         * When this boolean is set, the view has been added to the AbsListView
4029         * at least once. It is used to know whether headers/footers have already
4030         * been added to the list view and whether they should be treated as
4031         * recycled views or not.
4032         */
4033        @ViewDebug.ExportedProperty
4034        boolean recycledHeaderFooter;
4035
4036        /**
4037         * When an AbsListView is measured with an AT_MOST measure spec, it needs
4038         * to obtain children views to measure itself. When doing so, the children
4039         * are not attached to the window, but put in the recycler which assumes
4040         * they've been attached before. Setting this flag will force the reused
4041         * view to be attached to the window rather than just attached to the
4042         * parent.
4043         */
4044        @ViewDebug.ExportedProperty
4045        boolean forceAdd;
4046
4047        public LayoutParams(Context c, AttributeSet attrs) {
4048            super(c, attrs);
4049        }
4050
4051        public LayoutParams(int w, int h) {
4052            super(w, h);
4053        }
4054
4055        public LayoutParams(int w, int h, int viewType) {
4056            super(w, h);
4057            this.viewType = viewType;
4058        }
4059
4060        public LayoutParams(ViewGroup.LayoutParams source) {
4061            super(source);
4062        }
4063    }
4064
4065    /**
4066     * A RecyclerListener is used to receive a notification whenever a View is placed
4067     * inside the RecycleBin's scrap heap. This listener is used to free resources
4068     * associated to Views placed in the RecycleBin.
4069     *
4070     * @see android.widget.AbsListView.RecycleBin
4071     * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
4072     */
4073    public static interface RecyclerListener {
4074        /**
4075         * Indicates that the specified View was moved into the recycler's scrap heap.
4076         * The view is not displayed on screen any more and any expensive resource
4077         * associated with the view should be discarded.
4078         *
4079         * @param view
4080         */
4081        void onMovedToScrapHeap(View view);
4082    }
4083
4084    /**
4085     * The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of
4086     * storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the
4087     * start of a layout. By construction, they are displaying current information. At the end of
4088     * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that
4089     * could potentially be used by the adapter to avoid allocating views unnecessarily.
4090     *
4091     * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
4092     * @see android.widget.AbsListView.RecyclerListener
4093     */
4094    class RecycleBin {
4095        private RecyclerListener mRecyclerListener;
4096
4097        /**
4098         * The position of the first view stored in mActiveViews.
4099         */
4100        private int mFirstActivePosition;
4101
4102        /**
4103         * Views that were on screen at the start of layout. This array is populated at the start of
4104         * layout, and at the end of layout all view in mActiveViews are moved to mScrapViews.
4105         * Views in mActiveViews represent a contiguous range of Views, with position of the first
4106         * view store in mFirstActivePosition.
4107         */
4108        private View[] mActiveViews = new View[0];
4109
4110        /**
4111         * Unsorted views that can be used by the adapter as a convert view.
4112         */
4113        private ArrayList<View>[] mScrapViews;
4114
4115        private int mViewTypeCount;
4116
4117        private ArrayList<View> mCurrentScrap;
4118
4119        public void setViewTypeCount(int viewTypeCount) {
4120            if (viewTypeCount < 1) {
4121                throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
4122            }
4123            //noinspection unchecked
4124            ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
4125            for (int i = 0; i < viewTypeCount; i++) {
4126                scrapViews[i] = new ArrayList<View>();
4127            }
4128            mViewTypeCount = viewTypeCount;
4129            mCurrentScrap = scrapViews[0];
4130            mScrapViews = scrapViews;
4131        }
4132
4133        public boolean shouldRecycleViewType(int viewType) {
4134            return viewType >= 0;
4135        }
4136
4137        /**
4138         * Clears the scrap heap.
4139         */
4140        void clear() {
4141            if (mViewTypeCount == 1) {
4142                final ArrayList<View> scrap = mCurrentScrap;
4143                final int scrapCount = scrap.size();
4144                for (int i = 0; i < scrapCount; i++) {
4145                    removeDetachedView(scrap.remove(scrapCount - 1 - i), false);
4146                }
4147            } else {
4148                final int typeCount = mViewTypeCount;
4149                for (int i = 0; i < typeCount; i++) {
4150                    final ArrayList<View> scrap = mScrapViews[i];
4151                    final int scrapCount = scrap.size();
4152                    for (int j = 0; j < scrapCount; j++) {
4153                        removeDetachedView(scrap.remove(scrapCount - 1 - j), false);
4154                    }
4155                }
4156            }
4157        }
4158
4159        /**
4160         * Fill ActiveViews with all of the children of the AbsListView.
4161         *
4162         * @param childCount The minimum number of views mActiveViews should hold
4163         * @param firstActivePosition The position of the first view that will be stored in
4164         *        mActiveViews
4165         */
4166        void fillActiveViews(int childCount, int firstActivePosition) {
4167            if (mActiveViews.length < childCount) {
4168                mActiveViews = new View[childCount];
4169            }
4170            mFirstActivePosition = firstActivePosition;
4171
4172            final View[] activeViews = mActiveViews;
4173            for (int i = 0; i < childCount; i++) {
4174                View child = getChildAt(i);
4175                AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
4176                // Don't put header or footer views into the scrap heap
4177                if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
4178                    // Note:  We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views.
4179                    //        However, we will NOT place them into scrap views.
4180                    activeViews[i] = child;
4181                }
4182            }
4183        }
4184
4185        /**
4186         * Get the view corresponding to the specified position. The view will be removed from
4187         * mActiveViews if it is found.
4188         *
4189         * @param position The position to look up in mActiveViews
4190         * @return The view if it is found, null otherwise
4191         */
4192        View getActiveView(int position) {
4193            int index = position - mFirstActivePosition;
4194            final View[] activeViews = mActiveViews;
4195            if (index >=0 && index < activeViews.length) {
4196                final View match = activeViews[index];
4197                activeViews[index] = null;
4198                return match;
4199            }
4200            return null;
4201        }
4202
4203        /**
4204         * @return A view from the ScrapViews collection. These are unordered.
4205         */
4206        View getScrapView(int position) {
4207            ArrayList<View> scrapViews;
4208            if (mViewTypeCount == 1) {
4209                scrapViews = mCurrentScrap;
4210                int size = scrapViews.size();
4211                if (size > 0) {
4212                    return scrapViews.remove(size - 1);
4213                } else {
4214                    return null;
4215                }
4216            } else {
4217                int whichScrap = mAdapter.getItemViewType(position);
4218                if (whichScrap >= 0 && whichScrap < mScrapViews.length) {
4219                    scrapViews = mScrapViews[whichScrap];
4220                    int size = scrapViews.size();
4221                    if (size > 0) {
4222                        return scrapViews.remove(size - 1);
4223                    }
4224                }
4225            }
4226            return null;
4227        }
4228
4229        /**
4230         * Put a view into the ScapViews list. These views are unordered.
4231         *
4232         * @param scrap The view to add
4233         */
4234        void addScrapView(View scrap) {
4235            AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
4236            if (lp == null) {
4237                return;
4238            }
4239
4240            // Don't put header or footer views or views that should be ignored
4241            // into the scrap heap
4242            int viewType = lp.viewType;
4243            if (!shouldRecycleViewType(viewType)) {
4244                if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
4245                    removeDetachedView(scrap, false);
4246                }
4247                return;
4248            }
4249
4250            if (mViewTypeCount == 1) {
4251                scrap.dispatchStartTemporaryDetach();
4252                mCurrentScrap.add(scrap);
4253            } else {
4254                scrap.dispatchStartTemporaryDetach();
4255                mScrapViews[viewType].add(scrap);
4256            }
4257
4258            if (mRecyclerListener != null) {
4259                mRecyclerListener.onMovedToScrapHeap(scrap);
4260            }
4261        }
4262
4263        /**
4264         * Move all views remaining in mActiveViews to mScrapViews.
4265         */
4266        void scrapActiveViews() {
4267            final View[] activeViews = mActiveViews;
4268            final boolean hasListener = mRecyclerListener != null;
4269            final boolean multipleScraps = mViewTypeCount > 1;
4270
4271            ArrayList<View> scrapViews = mCurrentScrap;
4272            final int count = activeViews.length;
4273            for (int i = count - 1; i >= 0; i--) {
4274                final View victim = activeViews[i];
4275                if (victim != null) {
4276                    int whichScrap = ((AbsListView.LayoutParams) victim.getLayoutParams()).viewType;
4277
4278                    activeViews[i] = null;
4279
4280                    if (!shouldRecycleViewType(whichScrap)) {
4281                        // Do not move views that should be ignored
4282                        if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
4283                            removeDetachedView(victim, false);
4284                        }
4285                        continue;
4286                    }
4287
4288                    if (multipleScraps) {
4289                        scrapViews = mScrapViews[whichScrap];
4290                    }
4291                    victim.dispatchStartTemporaryDetach();
4292                    scrapViews.add(victim);
4293
4294                    if (hasListener) {
4295                        mRecyclerListener.onMovedToScrapHeap(victim);
4296                    }
4297
4298                    if (ViewDebug.TRACE_RECYCLER) {
4299                        ViewDebug.trace(victim,
4300                                ViewDebug.RecyclerTraceType.MOVE_FROM_ACTIVE_TO_SCRAP_HEAP,
4301                                mFirstActivePosition + i, -1);
4302                    }
4303                }
4304            }
4305
4306            pruneScrapViews();
4307        }
4308
4309        /**
4310         * Makes sure that the size of mScrapViews does not exceed the size of mActiveViews.
4311         * (This can happen if an adapter does not recycle its views).
4312         */
4313        private void pruneScrapViews() {
4314            final int maxViews = mActiveViews.length;
4315            final int viewTypeCount = mViewTypeCount;
4316            final ArrayList<View>[] scrapViews = mScrapViews;
4317            for (int i = 0; i < viewTypeCount; ++i) {
4318                final ArrayList<View> scrapPile = scrapViews[i];
4319                int size = scrapPile.size();
4320                final int extras = size - maxViews;
4321                size--;
4322                for (int j = 0; j < extras; j++) {
4323                    removeDetachedView(scrapPile.remove(size--), false);
4324                }
4325            }
4326        }
4327
4328        /**
4329         * Puts all views in the scrap heap into the supplied list.
4330         */
4331        void reclaimScrapViews(List<View> views) {
4332            if (mViewTypeCount == 1) {
4333                views.addAll(mCurrentScrap);
4334            } else {
4335                final int viewTypeCount = mViewTypeCount;
4336                final ArrayList<View>[] scrapViews = mScrapViews;
4337                for (int i = 0; i < viewTypeCount; ++i) {
4338                    final ArrayList<View> scrapPile = scrapViews[i];
4339                    views.addAll(scrapPile);
4340                }
4341            }
4342        }
4343
4344        /**
4345         * Updates the cache color hint of all known views.
4346         *
4347         * @param color The new cache color hint.
4348         */
4349        void setCacheColorHint(int color) {
4350            if (mViewTypeCount == 1) {
4351                final ArrayList<View> scrap = mCurrentScrap;
4352                final int scrapCount = scrap.size();
4353                for (int i = 0; i < scrapCount; i++) {
4354                    scrap.get(i).setDrawingCacheBackgroundColor(color);
4355                }
4356            } else {
4357                final int typeCount = mViewTypeCount;
4358                for (int i = 0; i < typeCount; i++) {
4359                    final ArrayList<View> scrap = mScrapViews[i];
4360                    final int scrapCount = scrap.size();
4361                    for (int j = 0; j < scrapCount; j++) {
4362                        scrap.get(i).setDrawingCacheBackgroundColor(color);
4363                    }
4364                }
4365            }
4366            // Just in case this is called during a layout pass
4367            final View[] activeViews = mActiveViews;
4368            final int count = activeViews.length;
4369            for (int i = 0; i < count; ++i) {
4370                final View victim = activeViews[i];
4371                if (victim != null) {
4372                    victim.setDrawingCacheBackgroundColor(color);
4373                }
4374            }
4375        }
4376    }
4377}
4378