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