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