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