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