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