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