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