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