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