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