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