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