AbsListView.java revision fea57edf0dd27bc21c34e7f96cd6383d6f2dff42
1/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.widget;
18
19import android.content.Context;
20import android.content.res.TypedArray;
21import android.graphics.Canvas;
22import android.graphics.Rect;
23import android.graphics.drawable.Drawable;
24import android.graphics.drawable.TransitionDrawable;
25import android.os.Debug;
26import android.os.Handler;
27import android.os.Parcel;
28import android.os.Parcelable;
29import android.text.Editable;
30import android.text.TextUtils;
31import android.text.TextWatcher;
32import android.util.AttributeSet;
33import android.view.Gravity;
34import android.view.HapticFeedbackConstants;
35import android.view.KeyEvent;
36import android.view.LayoutInflater;
37import android.view.MotionEvent;
38import android.view.VelocityTracker;
39import android.view.View;
40import android.view.ViewConfiguration;
41import android.view.ViewDebug;
42import android.view.ViewGroup;
43import android.view.ViewTreeObserver;
44import android.view.KeyCharacterMap;
45import android.view.inputmethod.BaseInputConnection;
46import android.view.inputmethod.EditorInfo;
47import android.view.inputmethod.InputConnection;
48import android.view.inputmethod.InputConnectionWrapper;
49import android.view.inputmethod.InputMethodManager;
50import android.view.ContextMenu.ContextMenuInfo;
51import android.gesture.GestureOverlayView;
52import android.gesture.Gesture;
53import android.gesture.LetterRecognizer;
54import android.gesture.Prediction;
55
56import com.android.internal.R;
57
58import java.util.ArrayList;
59import java.util.List;
60
61/**
62 * Base class that can be used to implement virtualized lists of items. A list does
63 * not have a spatial definition here. For instance, subclases of this class can
64 * display the content of the list in a grid, in a carousel, as stack, etc.
65 *
66 * @attr ref android.R.styleable#AbsListView_listSelector
67 * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop
68 * @attr ref android.R.styleable#AbsListView_stackFromBottom
69 * @attr ref android.R.styleable#AbsListView_scrollingCache
70 * @attr ref android.R.styleable#AbsListView_textFilterEnabled
71 * @attr ref android.R.styleable#AbsListView_transcriptMode
72 * @attr ref android.R.styleable#AbsListView_cacheColorHint
73 * @attr ref android.R.styleable#AbsListView_fastScrollEnabled
74 * @attr ref android.R.styleable#AbsListView_smoothScrollbar
75 * @attr ref android.R.styleable#AbsListView_gestures
76 */
77public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher,
78        ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener,
79        ViewTreeObserver.OnTouchModeChangeListener {
80
81    /**
82     * Disables the transcript mode.
83     *
84     * @see #setTranscriptMode(int)
85     */
86    public static final int TRANSCRIPT_MODE_DISABLED = 0;
87    /**
88     * The list will automatically scroll to the bottom when a data set change
89     * notification is received and only if the last item is already visible
90     * on screen.
91     *
92     * @see #setTranscriptMode(int)
93     */
94    public static final int TRANSCRIPT_MODE_NORMAL = 1;
95    /**
96     * The list will automatically scroll to the bottom, no matter what items
97     * are currently visible.
98     *
99     * @see #setTranscriptMode(int)
100     */
101    public static final int TRANSCRIPT_MODE_ALWAYS_SCROLL = 2;
102
103    /**
104     * Disables gestures.
105     *
106     * @see #setGestures(int)
107     * @see #GESTURES_JUMP
108     * @see #GESTURES_FILTER
109     */
110    public static final int GESTURES_NONE = 0;
111    /**
112     * When a letter gesture is recognized the list jumps to a matching position.
113     *
114     * @see #setGestures(int)
115     * @see #GESTURES_NONE
116     * @see #GESTURES_FILTER
117     */
118    public static final int GESTURES_JUMP = 1;
119    /**
120     * When a letter gesture is recognized the letter is added to the filter.
121     *
122     * @see #setGestures(int)
123     * @see #GESTURES_NONE
124     * @see #GESTURES_JUMP
125     */
126    public static final int GESTURES_FILTER = 2;
127
128    /**
129     * Indicates that we are not in the middle of a touch gesture
130     */
131    static final int TOUCH_MODE_REST = -1;
132
133    /**
134     * Indicates we just received the touch event and we are waiting to see if the it is a tap or a
135     * scroll gesture.
136     */
137    static final int TOUCH_MODE_DOWN = 0;
138
139    /**
140     * Indicates the touch has been recognized as a tap and we are now waiting to see if the touch
141     * is a longpress
142     */
143    static final int TOUCH_MODE_TAP = 1;
144
145    /**
146     * Indicates we have waited for everything we can wait for, but the user's finger is still down
147     */
148    static final int TOUCH_MODE_DONE_WAITING = 2;
149
150    /**
151     * Indicates the touch gesture is a scroll
152     */
153    static final int TOUCH_MODE_SCROLL = 3;
154
155    /**
156     * Indicates the view is in the process of being flung
157     */
158    static final int TOUCH_MODE_FLING = 4;
159
160    /**
161     * Indicates that the user is currently dragging the fast scroll thumb
162     */
163    static final int TOUCH_MODE_FAST_SCROLL = 5;
164
165    /**
166     * Regular layout - usually an unsolicited layout from the view system
167     */
168    static final int LAYOUT_NORMAL = 0;
169
170    /**
171     * Show the first item
172     */
173    static final int LAYOUT_FORCE_TOP = 1;
174
175    /**
176     * Force the selected item to be on somewhere on the screen
177     */
178    static final int LAYOUT_SET_SELECTION = 2;
179
180    /**
181     * Show the last item
182     */
183    static final int LAYOUT_FORCE_BOTTOM = 3;
184
185    /**
186     * Make a mSelectedItem appear in a specific location and build the rest of
187     * the views from there. The top is specified by mSpecificTop.
188     */
189    static final int LAYOUT_SPECIFIC = 4;
190
191    /**
192     * Layout to sync as a result of a data change. Restore mSyncPosition to have its top
193     * at mSpecificTop
194     */
195    static final int LAYOUT_SYNC = 5;
196
197    /**
198     * Layout as a result of using the navigation keys
199     */
200    static final int LAYOUT_MOVE_SELECTION = 6;
201
202    /**
203     * Controls how the next layout will happen
204     */
205    int mLayoutMode = LAYOUT_NORMAL;
206
207    /**
208     * Should be used by subclasses to listen to changes in the dataset
209     */
210    AdapterDataSetObserver mDataSetObserver;
211
212    /**
213     * The adapter containing the data to be displayed by this view
214     */
215    ListAdapter mAdapter;
216
217    /**
218     * Indicates whether the list selector should be drawn on top of the children or behind
219     */
220    boolean mDrawSelectorOnTop = false;
221
222    /**
223     * The drawable used to draw the selector
224     */
225    Drawable mSelector;
226
227    /**
228     * Defines the selector's location and dimension at drawing time
229     */
230    Rect mSelectorRect = new Rect();
231
232    /**
233     * The data set used to store unused views that should be reused during the next layout
234     * to avoid creating new ones
235     */
236    final RecycleBin mRecycler = new RecycleBin();
237
238    /**
239     * The selection's left padding
240     */
241    int mSelectionLeftPadding = 0;
242
243    /**
244     * The selection's top padding
245     */
246    int mSelectionTopPadding = 0;
247
248    /**
249     * The selection's right padding
250     */
251    int mSelectionRightPadding = 0;
252
253    /**
254     * The selection's bottom padding
255     */
256    int mSelectionBottomPadding = 0;
257
258    /**
259     * This view's padding
260     */
261    Rect mListPadding = new Rect();
262
263    /**
264     * Subclasses must retain their measure spec from onMeasure() into this member
265     */
266    int mWidthMeasureSpec = 0;
267
268    /**
269     * The top scroll indicator
270     */
271    View mScrollUp;
272
273    /**
274     * The down scroll indicator
275     */
276    View mScrollDown;
277
278    /**
279     * When the view is scrolling, this flag is set to true to indicate subclasses that
280     * the drawing cache was enabled on the children
281     */
282    boolean mCachingStarted;
283
284    /**
285     * The position of the view that received the down motion event
286     */
287    int mMotionPosition;
288
289    /**
290     * The offset to the top of the mMotionPosition view when the down motion event was received
291     */
292    int mMotionViewOriginalTop;
293
294    /**
295     * The desired offset to the top of the mMotionPosition view after a scroll
296     */
297    int mMotionViewNewTop;
298
299    /**
300     * The X value associated with the the down motion event
301     */
302    int mMotionX;
303
304    /**
305     * The Y value associated with the the down motion event
306     */
307    int mMotionY;
308
309    /**
310     * One of TOUCH_MODE_REST, TOUCH_MODE_DOWN, TOUCH_MODE_TAP, TOUCH_MODE_SCROLL, or
311     * TOUCH_MODE_DONE_WAITING
312     */
313    int mTouchMode = TOUCH_MODE_REST;
314
315    /**
316     * Y value from on the previous motion event (if any)
317     */
318    int mLastY;
319
320    /**
321     * How far the finger moved before we started scrolling
322     */
323    int mMotionCorrection;
324
325    /**
326     * Determines speed during touch scrolling
327     */
328    private VelocityTracker mVelocityTracker;
329
330    /**
331     * Handles one frame of a fling
332     */
333    private FlingRunnable mFlingRunnable;
334
335    /**
336     * The offset in pixels form the top of the AdapterView to the top
337     * of the currently selected view. Used to save and restore state.
338     */
339    int mSelectedTop = 0;
340
341    /**
342     * Indicates whether the list is stacked from the bottom edge or
343     * the top edge.
344     */
345    boolean mStackFromBottom;
346
347    /**
348     * When set to true, the list automatically discards the children's
349     * bitmap cache after scrolling.
350     */
351    boolean mScrollingCacheEnabled;
352
353    /**
354     * Whether or not to enable the fast scroll feature on this list
355     */
356    boolean mFastScrollEnabled;
357
358    /**
359     * Optional callback to notify client when scroll position has changed
360     */
361    private OnScrollListener mOnScrollListener;
362
363    /**
364     * Keeps track of our accessory window
365     */
366    PopupWindow mPopup;
367
368    /**
369     * Used with type filter window
370     */
371    EditText mTextFilter;
372
373    /**
374     * Indicates whether to use pixels-based or position-based scrollbar
375     * properties.
376     */
377    private boolean mSmoothScrollbarEnabled = true;
378
379    /**
380     * Indicates that this view supports filtering
381     */
382    private boolean mTextFilterEnabled;
383
384    /**
385     * Indicates that this view is currently displaying a filtered view of the data
386     */
387    private boolean mFiltered;
388
389    /**
390     * Rectangle used for hit testing children
391     */
392    private Rect mTouchFrame;
393
394    /**
395     * The position to resurrect the selected position to.
396     */
397    int mResurrectToPosition = INVALID_POSITION;
398
399    private ContextMenuInfo mContextMenuInfo = null;
400
401    /**
402     * Used to request a layout when we changed touch mode
403     */
404    private static final int TOUCH_MODE_UNKNOWN = -1;
405    private static final int TOUCH_MODE_ON = 0;
406    private static final int TOUCH_MODE_OFF = 1;
407
408    private int mLastTouchMode = TOUCH_MODE_UNKNOWN;
409
410    private static final boolean PROFILE_SCROLLING = false;
411    private boolean mScrollProfilingStarted = false;
412
413    private static final boolean PROFILE_FLINGING = false;
414    private boolean mFlingProfilingStarted = false;
415
416    /**
417     * The last CheckForLongPress runnable we posted, if any
418     */
419    private CheckForLongPress mPendingCheckForLongPress;
420
421    /**
422     * The last CheckForTap runnable we posted, if any
423     */
424    private Runnable mPendingCheckForTap;
425
426    /**
427     * The last CheckForKeyLongPress runnable we posted, if any
428     */
429    private CheckForKeyLongPress mPendingCheckForKeyLongPress;
430
431    /**
432     * Acts upon click
433     */
434    private AbsListView.PerformClick mPerformClick;
435
436    /**
437     * This view is in transcript mode -- it shows the bottom of the list when the data
438     * changes
439     */
440    private int mTranscriptMode;
441
442    /**
443     * Indicates that this list is always drawn on top of a solid, single-color, opaque
444     * background
445     */
446    private int mCacheColorHint;
447
448    /**
449     * The select child's view (from the adapter's getView) is enabled.
450     */
451    private boolean mIsChildViewEnabled;
452
453    /**
454     * The last scroll state reported to clients through {@link OnScrollListener}.
455     */
456    private int mLastScrollState = OnScrollListener.SCROLL_STATE_IDLE;
457
458    /**
459     * Helper object that renders and controls the fast scroll thumb.
460     */
461    private FastScroller mFastScroller;
462
463    /**
464     * Indicates the type of gestures to use: GESTURES_NONE, GESTURES_FILTER or GESTURES_NONE
465     */
466    private int mGestures;
467
468    // Used to implement the gestures overlay
469    private GestureOverlayView mGesturesOverlay;
470    private PopupWindow mGesturesPopup;
471    private ViewTreeObserver.OnGlobalLayoutListener mGesturesLayoutListener;
472    private boolean mGlobalLayoutListenerAddedGestures;
473    private boolean mInstallGesturesOverlay;
474    private boolean mPreviousGesturing;
475
476    private boolean mGlobalLayoutListenerAddedFilter;
477
478    private int mTouchSlop;
479    private float mDensityScale;
480
481    private InputConnection mDefInputConnection;
482    private InputConnectionWrapper mPublicInputConnection;
483
484    private Runnable mClearScrollingCache;
485
486    /**
487     * Interface definition for a callback to be invoked when the list or grid
488     * has been scrolled.
489     */
490    public interface OnScrollListener {
491
492        /**
493         * The view is not scrolling. Note navigating the list using the trackball counts as
494         * being in the idle state since these transitions are not animated.
495         */
496        public static int SCROLL_STATE_IDLE = 0;
497
498        /**
499         * The user is scrolling using touch, and their finger is still on the screen
500         */
501        public static int SCROLL_STATE_TOUCH_SCROLL = 1;
502
503        /**
504         * The user had previously been scrolling using touch and had performed a fling. The
505         * animation is now coasting to a stop
506         */
507        public static int SCROLL_STATE_FLING = 2;
508
509        /**
510         * Callback method to be invoked while the list view or grid view is being scrolled. If the
511         * view is being scrolled, this method will be called before the next frame of the scroll is
512         * rendered. In particular, it will be called before any calls to
513         * {@link Adapter#getView(int, View, ViewGroup)}.
514         *
515         * @param view The view whose scroll state is being reported
516         *
517         * @param scrollState The current scroll state. One of {@link #SCROLL_STATE_IDLE},
518         * {@link #SCROLL_STATE_TOUCH_SCROLL} or {@link #SCROLL_STATE_IDLE}.
519         */
520        public void onScrollStateChanged(AbsListView view, int scrollState);
521
522        /**
523         * Callback method to be invoked when the list or grid has been scrolled. This will be
524         * called after the scroll has completed
525         * @param view The view whose scroll state is being reported
526         * @param firstVisibleItem the index of the first visible cell (ignore if
527         *        visibleItemCount == 0)
528         * @param visibleItemCount the number of visible cells
529         * @param totalItemCount the number of items in the list adaptor
530         */
531        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
532                int totalItemCount);
533    }
534
535    public AbsListView(Context context) {
536        super(context);
537        initAbsListView();
538
539        setVerticalScrollBarEnabled(true);
540        TypedArray a = context.obtainStyledAttributes(R.styleable.View);
541        initializeScrollbars(a);
542        a.recycle();
543    }
544
545    public AbsListView(Context context, AttributeSet attrs) {
546        this(context, attrs, com.android.internal.R.attr.absListViewStyle);
547    }
548
549    public AbsListView(Context context, AttributeSet attrs, int defStyle) {
550        super(context, attrs, defStyle);
551        initAbsListView();
552
553        TypedArray a = context.obtainStyledAttributes(attrs,
554                com.android.internal.R.styleable.AbsListView, defStyle, 0);
555
556        Drawable d = a.getDrawable(com.android.internal.R.styleable.AbsListView_listSelector);
557        if (d != null) {
558            setSelector(d);
559        }
560
561        mDrawSelectorOnTop = a.getBoolean(
562                com.android.internal.R.styleable.AbsListView_drawSelectorOnTop, false);
563
564        boolean stackFromBottom = a.getBoolean(R.styleable.AbsListView_stackFromBottom, false);
565        setStackFromBottom(stackFromBottom);
566
567        boolean scrollingCacheEnabled = a.getBoolean(R.styleable.AbsListView_scrollingCache, true);
568        setScrollingCacheEnabled(scrollingCacheEnabled);
569
570        boolean useTextFilter = a.getBoolean(R.styleable.AbsListView_textFilterEnabled, false);
571        setTextFilterEnabled(useTextFilter);
572
573        int transcriptMode = a.getInt(R.styleable.AbsListView_transcriptMode,
574                TRANSCRIPT_MODE_DISABLED);
575        setTranscriptMode(transcriptMode);
576
577        int color = a.getColor(R.styleable.AbsListView_cacheColorHint, 0);
578        setCacheColorHint(color);
579
580        boolean enableFastScroll = a.getBoolean(R.styleable.AbsListView_fastScrollEnabled, false);
581        setFastScrollEnabled(enableFastScroll);
582
583        boolean smoothScrollbar = a.getBoolean(R.styleable.AbsListView_smoothScrollbar, true);
584        setSmoothScrollbarEnabled(smoothScrollbar);
585
586        int defaultGestures = GESTURES_NONE;
587        if (useTextFilter) {
588            defaultGestures = GESTURES_FILTER;
589        } else if (enableFastScroll) {
590            defaultGestures = GESTURES_JUMP;
591        }
592        int gestures = a.getInt(R.styleable.AbsListView_gestures, defaultGestures);
593        setGestures(gestures);
594
595        a.recycle();
596    }
597
598    private void initAbsListView() {
599        // Setting focusable in touch mode will set the focusable property to true
600        setFocusableInTouchMode(true);
601        setWillNotDraw(false);
602        setAlwaysDrawnWithCacheEnabled(false);
603        setScrollingCacheEnabled(true);
604
605        mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
606        mDensityScale = getContext().getResources().getDisplayMetrics().density;
607    }
608
609    /**
610     * <p>Sets the type of gestures to use with this list. When gestures are enabled,
611     * that is if the <code>gestures</code> parameter is not {@link #GESTURES_NONE},
612     * the user can draw characters on top of this view. When a character is
613     * recognized and matches a known character, the list will either:</p>
614     * <ul>
615     * <li>Jump to the appropriate position ({@link #GESTURES_JUMP})</li>
616     * <li>Add the character to the current filter ({@link #GESTURES_FILTER})</li>
617     * </ul>
618     * <p>Using {@link #GESTURES_JUMP} requires {@link #isFastScrollEnabled()} to
619     * be true. Using {@link #GESTURES_FILTER} requires {@link #isTextFilterEnabled()}
620     * to be true.</p>
621     *
622     * @param gestures The type of gestures to enable for this list:
623     *        {@link #GESTURES_NONE}, {@link #GESTURES_JUMP} or {@link #GESTURES_FILTER}
624     *
625     * @see #GESTURES_NONE
626     * @see #GESTURES_JUMP
627     * @see #GESTURES_FILTER
628     * @see #getGestures()
629     */
630    public void setGestures(int gestures) {
631        switch (gestures) {
632            case GESTURES_JUMP:
633                if (!mFastScrollEnabled) {
634                    throw new IllegalStateException("Jump gestures can only be used with "
635                            + "fast scroll enabled");
636                }
637                break;
638            case GESTURES_FILTER:
639                if (!mTextFilterEnabled) {
640                    throw new IllegalStateException("Filter gestures can only be used with "
641                            + "text filtering enabled");
642                }
643                break;
644        }
645
646        final int oldGestures = mGestures;
647        mGestures = gestures;
648
649        // Install overlay later
650        if (oldGestures == GESTURES_NONE && gestures != GESTURES_NONE) {
651            mInstallGesturesOverlay = true;
652        // Uninstall overlay
653        } else if (oldGestures != GESTURES_NONE && gestures == GESTURES_NONE) {
654            uninstallGesturesOverlay();
655        }
656    }
657
658    /**
659     * Indicates what gestures are enabled on this view.
660     *
661     * @return {@link #GESTURES_NONE}, {@link #GESTURES_JUMP} or {@link #GESTURES_FILTER}
662     *
663     * @see #GESTURES_NONE
664     * @see #GESTURES_JUMP
665     * @see #GESTURES_FILTER
666     * @see #setGestures(int)
667     */
668    @ViewDebug.ExportedProperty(mapping = {
669        @ViewDebug.IntToString(from = GESTURES_NONE, to = "NONE"),
670        @ViewDebug.IntToString(from = GESTURES_JUMP, to = "JUMP"),
671        @ViewDebug.IntToString(from = GESTURES_FILTER, to = "FILTER")
672    })
673    public int getGestures() {
674        return mGestures;
675    }
676
677    private void dismissGesturesPopup() {
678        if (mGesturesPopup != null) {
679            mGesturesPopup.dismiss();
680        }
681    }
682
683    private void showGesturesPopup() {
684        // Make sure we have a window before showing the popup
685        if (getWindowVisibility() == View.VISIBLE) {
686            installGesturesOverlay();
687            positionGesturesPopup();
688        }
689    }
690
691    private void positionGesturesPopup() {
692        final int[] xy = new int[2];
693        getLocationOnScreen(xy);
694        if (!mGesturesPopup.isShowing()) {
695            mGesturesPopup.showAtLocation(this, Gravity.LEFT | Gravity.TOP, xy[0], xy[1]);
696        } else {
697            mGesturesPopup.update(xy[0], xy[1], -1, -1);
698        }
699    }
700
701    private void installGesturesOverlay() {
702        mInstallGesturesOverlay = false;
703
704        if (mGesturesPopup == null) {
705            final Context c = getContext();
706            final LayoutInflater layoutInflater = (LayoutInflater)
707                    c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
708            mGesturesOverlay = (GestureOverlayView)
709                    layoutInflater.inflate(R.layout.list_gestures_overlay, null);
710
711            final PopupWindow p = new PopupWindow(c);
712            p.setFocusable(false);
713            p.setTouchable(false);
714            p.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
715            p.setContentView(mGesturesOverlay);
716            p.setWidth(getWidth());
717            p.setHeight(getHeight());
718            p.setBackgroundDrawable(null);
719
720            if (mGesturesLayoutListener == null) {
721                mGesturesLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
722                    public void onGlobalLayout() {
723                        if (isShown()) {
724                            showGesturesPopup();
725                        } else if (mGesturesPopup.isShowing()) {
726                            dismissGesturesPopup();
727                        }
728                    }
729                };
730            }
731            getViewTreeObserver().addOnGlobalLayoutListener(mGesturesLayoutListener);
732            mGlobalLayoutListenerAddedGestures = true;
733
734            mGesturesPopup = p;
735
736            mGesturesOverlay.removeAllOnGestureListeners();
737            mGesturesOverlay.setGestureStrokeType(GestureOverlayView.GESTURE_STROKE_TYPE_MULTIPLE);
738            mGesturesOverlay.addOnGesturePerformedListener(new GesturesProcessor());
739
740            mPreviousGesturing = false;
741        }
742    }
743
744    private void uninstallGesturesOverlay() {
745        dismissGesturesPopup();
746        mGesturesPopup = null;
747        if (mGesturesLayoutListener != null) {
748            getViewTreeObserver().removeGlobalOnLayoutListener(mGesturesLayoutListener);
749        }
750    }
751
752    @Override
753    public boolean dispatchTouchEvent(MotionEvent ev) {
754        if (mGestures != GESTURES_NONE) {
755            if (ev.getAction() != MotionEvent.ACTION_DOWN || mFastScroller == null ||
756                    !mFastScroller.isPointInside(ev.getX(), ev.getY())) {
757
758                if (mGesturesPopup.isShowing()) {
759                    mGesturesOverlay.dispatchTouchEvent(ev);
760
761                    final boolean isGesturing = mGesturesOverlay.isGesturing();
762
763                    if (!isGesturing) {
764                        mPreviousGesturing = isGesturing;
765                        return super.dispatchTouchEvent(ev);
766                    } else if (!mPreviousGesturing){
767                        mPreviousGesturing = isGesturing;
768                        final MotionEvent event = MotionEvent.obtain(ev);
769                        event.setAction(MotionEvent.ACTION_CANCEL);
770                        super.dispatchTouchEvent(event);
771                        return true;
772                    }
773                }
774            }
775        }
776
777        return super.dispatchTouchEvent(ev);
778    }
779
780    /**
781     * Enables fast scrolling by letting the user quickly scroll through lists by
782     * dragging the fast scroll thumb. The adapter attached to the list may want
783     * to implement {@link SectionIndexer} if it wishes to display alphabet preview and
784     * jump between sections of the list.
785     * @see SectionIndexer
786     * @see #isFastScrollEnabled()
787     * @param enabled whether or not to enable fast scrolling
788     */
789    public void setFastScrollEnabled(boolean enabled) {
790        mFastScrollEnabled = enabled;
791        if (enabled) {
792            if (mFastScroller == null) {
793                mFastScroller = new FastScroller(getContext(), this);
794            }
795        } else {
796            if (mFastScroller != null) {
797                mFastScroller.stop();
798                mFastScroller = null;
799            }
800        }
801    }
802
803    /**
804     * Returns the current state of the fast scroll feature.
805     * @see #setFastScrollEnabled(boolean)
806     * @return true if fast scroll is enabled, false otherwise
807     */
808    @ViewDebug.ExportedProperty
809    public boolean isFastScrollEnabled() {
810        return mFastScrollEnabled;
811    }
812
813    /**
814     * If fast scroll is visible, then don't draw the vertical scrollbar.
815     * @hide
816     */
817    @Override
818    protected boolean isVerticalScrollBarHidden() {
819        return mFastScroller != null && mFastScroller.isVisible();
820    }
821
822    /**
823     * When smooth scrollbar is enabled, the position and size of the scrollbar thumb
824     * is computed based on the number of visible pixels in the visible items. This
825     * however assumes that all list items have the same height. If you use a list in
826     * which items have different heights, the scrollbar will change appearance as the
827     * user scrolls through the list. To avoid this issue, you need to disable this
828     * property.
829     *
830     * When smooth scrollbar is disabled, the position and size of the scrollbar thumb
831     * is based solely on the number of items in the adapter and the position of the
832     * visible items inside the adapter. This provides a stable scrollbar as the user
833     * navigates through a list of items with varying heights.
834     *
835     * @param enabled Whether or not to enable smooth scrollbar.
836     *
837     * @see #setSmoothScrollbarEnabled(boolean)
838     * @attr ref android.R.styleable#AbsListView_smoothScrollbar
839     */
840    public void setSmoothScrollbarEnabled(boolean enabled) {
841        mSmoothScrollbarEnabled = enabled;
842    }
843
844    /**
845     * Returns the current state of the fast scroll feature.
846     *
847     * @return True if smooth scrollbar is enabled is enabled, false otherwise.
848     *
849     * @see #setSmoothScrollbarEnabled(boolean)
850     */
851    @ViewDebug.ExportedProperty
852    public boolean isSmoothScrollbarEnabled() {
853        return mSmoothScrollbarEnabled;
854    }
855
856    /**
857     * Set the listener that will receive notifications every time the list scrolls.
858     *
859     * @param l the scroll listener
860     */
861    public void setOnScrollListener(OnScrollListener l) {
862        mOnScrollListener = l;
863        invokeOnItemScrollListener();
864    }
865
866    /**
867     * Notify our scroll listener (if there is one) of a change in scroll state
868     */
869    void invokeOnItemScrollListener() {
870        if (mFastScroller != null) {
871            mFastScroller.onScroll(this, mFirstPosition, getChildCount(), mItemCount);
872        }
873        if (mOnScrollListener != null) {
874            mOnScrollListener.onScroll(this, mFirstPosition, getChildCount(), mItemCount);
875        }
876    }
877
878    /**
879     * Indicates whether the children's drawing cache is used during a scroll.
880     * By default, the drawing cache is enabled but this will consume more memory.
881     *
882     * @return true if the scrolling cache is enabled, false otherwise
883     *
884     * @see #setScrollingCacheEnabled(boolean)
885     * @see View#setDrawingCacheEnabled(boolean)
886     */
887    @ViewDebug.ExportedProperty
888    public boolean isScrollingCacheEnabled() {
889        return mScrollingCacheEnabled;
890    }
891
892    /**
893     * Enables or disables the children's drawing cache during a scroll.
894     * By default, the drawing cache is enabled but this will use more memory.
895     *
896     * When the scrolling cache is enabled, the caches are kept after the
897     * first scrolling. You can manually clear the cache by calling
898     * {@link android.view.ViewGroup#setChildrenDrawingCacheEnabled(boolean)}.
899     *
900     * @param enabled true to enable the scroll cache, false otherwise
901     *
902     * @see #isScrollingCacheEnabled()
903     * @see View#setDrawingCacheEnabled(boolean)
904     */
905    public void setScrollingCacheEnabled(boolean enabled) {
906        if (mScrollingCacheEnabled && !enabled) {
907            clearScrollingCache();
908        }
909        mScrollingCacheEnabled = enabled;
910    }
911
912    /**
913     * Enables or disables the type filter window. If enabled, typing when
914     * this view has focus will filter the children to match the users input.
915     * Note that the {@link Adapter} used by this view must implement the
916     * {@link Filterable} interface.
917     *
918     * @param textFilterEnabled true to enable type filtering, false otherwise
919     *
920     * @see Filterable
921     */
922    public void setTextFilterEnabled(boolean textFilterEnabled) {
923        mTextFilterEnabled = textFilterEnabled;
924    }
925
926    /**
927     * Indicates whether type filtering is enabled for this view
928     *
929     * @return true if type filtering is enabled, false otherwise
930     *
931     * @see #setTextFilterEnabled(boolean)
932     * @see Filterable
933     */
934    @ViewDebug.ExportedProperty
935    public boolean isTextFilterEnabled() {
936        return mTextFilterEnabled;
937    }
938
939    @Override
940    public void getFocusedRect(Rect r) {
941        View view = getSelectedView();
942        if (view != null) {
943            // the focused rectangle of the selected view offset into the
944            // coordinate space of this view.
945            view.getFocusedRect(r);
946            offsetDescendantRectToMyCoords(view, r);
947        } else {
948            // otherwise, just the norm
949            super.getFocusedRect(r);
950        }
951    }
952
953    private void useDefaultSelector() {
954        setSelector(getResources().getDrawable(
955                com.android.internal.R.drawable.list_selector_background));
956    }
957
958    /**
959     * Indicates whether the content of this view is pinned to, or stacked from,
960     * the bottom edge.
961     *
962     * @return true if the content is stacked from the bottom edge, false otherwise
963     */
964    @ViewDebug.ExportedProperty
965    public boolean isStackFromBottom() {
966        return mStackFromBottom;
967    }
968
969    /**
970     * When stack from bottom is set to true, the list fills its content starting from
971     * the bottom of the view.
972     *
973     * @param stackFromBottom true to pin the view's content to the bottom edge,
974     *        false to pin the view's content to the top edge
975     */
976    public void setStackFromBottom(boolean stackFromBottom) {
977        if (mStackFromBottom != stackFromBottom) {
978            mStackFromBottom = stackFromBottom;
979            requestLayoutIfNecessary();
980        }
981    }
982
983    void requestLayoutIfNecessary() {
984        if (getChildCount() > 0) {
985            resetList();
986            requestLayout();
987            invalidate();
988        }
989    }
990
991    static class SavedState extends BaseSavedState {
992        long selectedId;
993        long firstId;
994        int viewTop;
995        int position;
996        int height;
997        String filter;
998
999        /**
1000         * Constructor called from {@link AbsListView#onSaveInstanceState()}
1001         */
1002        SavedState(Parcelable superState) {
1003            super(superState);
1004        }
1005
1006        /**
1007         * Constructor called from {@link #CREATOR}
1008         */
1009        private SavedState(Parcel in) {
1010            super(in);
1011            selectedId = in.readLong();
1012            firstId = in.readLong();
1013            viewTop = in.readInt();
1014            position = in.readInt();
1015            height = in.readInt();
1016            filter = in.readString();
1017        }
1018
1019        @Override
1020        public void writeToParcel(Parcel out, int flags) {
1021            super.writeToParcel(out, flags);
1022            out.writeLong(selectedId);
1023            out.writeLong(firstId);
1024            out.writeInt(viewTop);
1025            out.writeInt(position);
1026            out.writeInt(height);
1027            out.writeString(filter);
1028        }
1029
1030        @Override
1031        public String toString() {
1032            return "AbsListView.SavedState{"
1033                    + Integer.toHexString(System.identityHashCode(this))
1034                    + " selectedId=" + selectedId
1035                    + " firstId=" + firstId
1036                    + " viewTop=" + viewTop
1037                    + " position=" + position
1038                    + " height=" + height
1039                    + " filter=" + filter + "}";
1040        }
1041
1042        public static final Parcelable.Creator<SavedState> CREATOR
1043                = new Parcelable.Creator<SavedState>() {
1044            public SavedState createFromParcel(Parcel in) {
1045                return new SavedState(in);
1046            }
1047
1048            public SavedState[] newArray(int size) {
1049                return new SavedState[size];
1050            }
1051        };
1052    }
1053
1054    @Override
1055    public Parcelable onSaveInstanceState() {
1056        /*
1057         * This doesn't really make sense as the place to dismiss the
1058         * popup, but there don't seem to be any other useful hooks
1059         * that happen early enough to keep from getting complaints
1060         * about having leaked the window.
1061         */
1062        dismissPopup();
1063
1064        Parcelable superState = super.onSaveInstanceState();
1065
1066        SavedState ss = new SavedState(superState);
1067
1068        boolean haveChildren = getChildCount() > 0;
1069        long selectedId = getSelectedItemId();
1070        ss.selectedId = selectedId;
1071        ss.height = getHeight();
1072
1073        if (selectedId >= 0) {
1074            // Remember the selection
1075            ss.viewTop = mSelectedTop;
1076            ss.position = getSelectedItemPosition();
1077            ss.firstId = INVALID_POSITION;
1078        } else {
1079            if (haveChildren) {
1080                // Remember the position of the first child
1081                View v = getChildAt(0);
1082                ss.viewTop = v.getTop();
1083                ss.position = mFirstPosition;
1084                ss.firstId = mAdapter.getItemId(mFirstPosition);
1085            } else {
1086                ss.viewTop = 0;
1087                ss.firstId = INVALID_POSITION;
1088                ss.position = 0;
1089            }
1090        }
1091
1092        ss.filter = null;
1093        if (mFiltered) {
1094            final EditText textFilter = mTextFilter;
1095            if (textFilter != null) {
1096                Editable filterText = textFilter.getText();
1097                if (filterText != null) {
1098                    ss.filter = filterText.toString();
1099                }
1100            }
1101        }
1102
1103        return ss;
1104    }
1105
1106    @Override
1107    public void onRestoreInstanceState(Parcelable state) {
1108        SavedState ss = (SavedState) state;
1109
1110        super.onRestoreInstanceState(ss.getSuperState());
1111        mDataChanged = true;
1112
1113        mSyncHeight = ss.height;
1114
1115        if (ss.selectedId >= 0) {
1116            mNeedSync = true;
1117            mSyncRowId = ss.selectedId;
1118            mSyncPosition = ss.position;
1119            mSpecificTop = ss.viewTop;
1120            mSyncMode = SYNC_SELECTED_POSITION;
1121        } else if (ss.firstId >= 0) {
1122            setSelectedPositionInt(INVALID_POSITION);
1123            // Do this before setting mNeedSync since setNextSelectedPosition looks at mNeedSync
1124            setNextSelectedPositionInt(INVALID_POSITION);
1125            mNeedSync = true;
1126            mSyncRowId = ss.firstId;
1127            mSyncPosition = ss.position;
1128            mSpecificTop = ss.viewTop;
1129            mSyncMode = SYNC_FIRST_POSITION;
1130        }
1131
1132        setFilterText(ss.filter);
1133
1134        requestLayout();
1135    }
1136
1137    private boolean acceptFilter() {
1138        return mTextFilterEnabled && getAdapter() instanceof Filterable &&
1139                ((Filterable) getAdapter()).getFilter() != null;
1140    }
1141
1142    /**
1143     * Sets the initial value for the text filter.
1144     * @param filterText The text to use for the filter.
1145     *
1146     * @see #setTextFilterEnabled
1147     */
1148    public void setFilterText(String filterText) {
1149        // TODO: Should we check for acceptFilter()?
1150        if (mTextFilterEnabled && !TextUtils.isEmpty(filterText)) {
1151            createTextFilter(false);
1152            // This is going to call our listener onTextChanged, but we might not
1153            // be ready to bring up a window yet
1154            mTextFilter.setText(filterText);
1155            mTextFilter.setSelection(filterText.length());
1156            if (mAdapter instanceof Filterable) {
1157                // if mPopup is non-null, then onTextChanged will do the filtering
1158                if (mPopup == null) {
1159                    Filter f = ((Filterable) mAdapter).getFilter();
1160                    f.filter(filterText);
1161                }
1162                // Set filtered to true so we will display the filter window when our main
1163                // window is ready
1164                mFiltered = true;
1165                mDataSetObserver.clearSavedState();
1166            }
1167        }
1168    }
1169
1170    /**
1171     * Returns the list's text filter, if available.
1172     * @return the list's text filter or null if filtering isn't enabled
1173     */
1174    public CharSequence getTextFilter() {
1175        if (mTextFilterEnabled && mTextFilter != null) {
1176            return mTextFilter.getText();
1177        }
1178        return null;
1179    }
1180
1181    @Override
1182    protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
1183        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
1184        if (gainFocus && mSelectedPosition < 0 && !isInTouchMode()) {
1185            resurrectSelection();
1186        }
1187    }
1188
1189    @Override
1190    public void requestLayout() {
1191        if (!mBlockLayoutRequests && !mInLayout) {
1192            super.requestLayout();
1193        }
1194    }
1195
1196    /**
1197     * The list is empty. Clear everything out.
1198     */
1199    void resetList() {
1200        removeAllViewsInLayout();
1201        mFirstPosition = 0;
1202        mDataChanged = false;
1203        mNeedSync = false;
1204        mOldSelectedPosition = INVALID_POSITION;
1205        mOldSelectedRowId = INVALID_ROW_ID;
1206        setSelectedPositionInt(INVALID_POSITION);
1207        setNextSelectedPositionInt(INVALID_POSITION);
1208        mSelectedTop = 0;
1209        mSelectorRect.setEmpty();
1210        invalidate();
1211    }
1212
1213    @Override
1214    protected int computeVerticalScrollExtent() {
1215        final int count = getChildCount();
1216        if (count > 0) {
1217            if (mSmoothScrollbarEnabled) {
1218                int extent = count * 100;
1219
1220                View view = getChildAt(0);
1221                final int top = view.getTop();
1222                int height = view.getHeight();
1223                if (height > 0) {
1224                    extent += (top * 100) / height;
1225                }
1226
1227                view = getChildAt(count - 1);
1228                final int bottom = view.getBottom();
1229                height = view.getHeight();
1230                if (height > 0) {
1231                    extent -= ((bottom - getHeight()) * 100) / height;
1232                }
1233
1234                return extent;
1235            } else {
1236                return 1;
1237            }
1238        }
1239        return 0;
1240    }
1241
1242    @Override
1243    protected int computeVerticalScrollOffset() {
1244        final int firstPosition = mFirstPosition;
1245        final int childCount = getChildCount();
1246        if (firstPosition >= 0 && childCount > 0) {
1247            if (mSmoothScrollbarEnabled) {
1248                final View view = getChildAt(0);
1249                final int top = view.getTop();
1250                int height = view.getHeight();
1251                if (height > 0) {
1252                    return Math.max(firstPosition * 100 - (top * 100) / height, 0);
1253                }
1254            } else {
1255                int index;
1256                final int count = mItemCount;
1257                if (firstPosition == 0) {
1258                    index = 0;
1259                } else if (firstPosition + childCount == count) {
1260                    index = count;
1261                } else {
1262                    index = firstPosition + childCount / 2;
1263                }
1264                return (int) (firstPosition + childCount * (index / (float) count));
1265            }
1266        }
1267        return 0;
1268    }
1269
1270    @Override
1271    protected int computeVerticalScrollRange() {
1272        return mSmoothScrollbarEnabled ? Math.max(mItemCount * 100, 0) : mItemCount;
1273    }
1274
1275    @Override
1276    protected float getTopFadingEdgeStrength() {
1277        final int count = getChildCount();
1278        final float fadeEdge = super.getTopFadingEdgeStrength();
1279        if (count == 0) {
1280            return fadeEdge;
1281        } else {
1282            if (mFirstPosition > 0) {
1283                return 1.0f;
1284            }
1285
1286            final int top = getChildAt(0).getTop();
1287            final float fadeLength = (float) getVerticalFadingEdgeLength();
1288            return top < mPaddingTop ? (float) -(top - mPaddingTop) / fadeLength : fadeEdge;
1289        }
1290    }
1291
1292    @Override
1293    protected float getBottomFadingEdgeStrength() {
1294        final int count = getChildCount();
1295        final float fadeEdge = super.getBottomFadingEdgeStrength();
1296        if (count == 0) {
1297            return fadeEdge;
1298        } else {
1299            if (mFirstPosition + count - 1 < mItemCount - 1) {
1300                return 1.0f;
1301            }
1302
1303            final int bottom = getChildAt(count - 1).getBottom();
1304            final int height = getHeight();
1305            final float fadeLength = (float) getVerticalFadingEdgeLength();
1306            return bottom > height - mPaddingBottom ?
1307                    (float) (bottom - height + mPaddingBottom) / fadeLength : fadeEdge;
1308        }
1309    }
1310
1311    @Override
1312    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1313        if (mSelector == null) {
1314            useDefaultSelector();
1315        }
1316        final Rect listPadding = mListPadding;
1317        listPadding.left = mSelectionLeftPadding + mPaddingLeft;
1318        listPadding.top = mSelectionTopPadding + mPaddingTop;
1319        listPadding.right = mSelectionRightPadding + mPaddingRight;
1320        listPadding.bottom = mSelectionBottomPadding + mPaddingBottom;
1321    }
1322
1323    /**
1324     * Subclasses should NOT override this method but
1325     *  {@link #layoutChildren()} instead.
1326     */
1327    @Override
1328    protected void onLayout(boolean changed, int l, int t, int r, int b) {
1329        super.onLayout(changed, l, t, r, b);
1330        mInLayout = true;
1331        layoutChildren();
1332        mInLayout = false;
1333    }
1334
1335    /**
1336     * @hide
1337     */
1338    @Override
1339    protected boolean setFrame(int left, int top, int right, int bottom) {
1340        final boolean changed = super.setFrame(left, top, right, bottom);
1341
1342        if (changed) {
1343            // Reposition the popup when the frame has changed. This includes
1344            // translating the widget, not just changing its dimension. The
1345            // filter popup needs to follow the widget.
1346            final boolean visible = getWindowVisibility() == View.VISIBLE;
1347            if (mFiltered && visible && mPopup != null && mPopup.isShowing()) {
1348                positionPopup();
1349            }
1350
1351            if (mGestures != GESTURES_NONE && visible && mGesturesPopup != null &&
1352                    mGesturesPopup.isShowing()) {
1353                positionGesturesPopup();
1354            }
1355        }
1356
1357        return changed;
1358    }
1359
1360    /**
1361     * Subclasses must override this method to layout their children.
1362     */
1363    protected void layoutChildren() {
1364    }
1365
1366    void updateScrollIndicators() {
1367        if (mScrollUp != null) {
1368            boolean canScrollUp;
1369            // 0th element is not visible
1370            canScrollUp = mFirstPosition > 0;
1371
1372            // ... Or top of 0th element is not visible
1373            if (!canScrollUp) {
1374                if (getChildCount() > 0) {
1375                    View child = getChildAt(0);
1376                    canScrollUp = child.getTop() < mListPadding.top;
1377                }
1378            }
1379
1380            mScrollUp.setVisibility(canScrollUp ? View.VISIBLE : View.INVISIBLE);
1381        }
1382
1383        if (mScrollDown != null) {
1384            boolean canScrollDown;
1385            int count = getChildCount();
1386
1387            // Last item is not visible
1388            canScrollDown = (mFirstPosition + count) < mItemCount;
1389
1390            // ... Or bottom of the last element is not visible
1391            if (!canScrollDown && count > 0) {
1392                View child = getChildAt(count - 1);
1393                canScrollDown = child.getBottom() > mBottom - mListPadding.bottom;
1394            }
1395
1396            mScrollDown.setVisibility(canScrollDown ? View.VISIBLE : View.INVISIBLE);
1397        }
1398    }
1399
1400    @Override
1401    @ViewDebug.ExportedProperty
1402    public View getSelectedView() {
1403        if (mItemCount > 0 && mSelectedPosition >= 0) {
1404            return getChildAt(mSelectedPosition - mFirstPosition);
1405        } else {
1406            return null;
1407        }
1408    }
1409
1410    /**
1411     * List padding is the maximum of the normal view's padding and the padding of the selector.
1412     *
1413     * @see android.view.View#getPaddingTop()
1414     * @see #getSelector()
1415     *
1416     * @return The top list padding.
1417     */
1418    public int getListPaddingTop() {
1419        return mListPadding.top;
1420    }
1421
1422    /**
1423     * List padding is the maximum of the normal view's padding and the padding of the selector.
1424     *
1425     * @see android.view.View#getPaddingBottom()
1426     * @see #getSelector()
1427     *
1428     * @return The bottom list padding.
1429     */
1430    public int getListPaddingBottom() {
1431        return mListPadding.bottom;
1432    }
1433
1434    /**
1435     * List padding is the maximum of the normal view's padding and the padding of the selector.
1436     *
1437     * @see android.view.View#getPaddingLeft()
1438     * @see #getSelector()
1439     *
1440     * @return The left list padding.
1441     */
1442    public int getListPaddingLeft() {
1443        return mListPadding.left;
1444    }
1445
1446    /**
1447     * List padding is the maximum of the normal view's padding and the padding of the selector.
1448     *
1449     * @see android.view.View#getPaddingRight()
1450     * @see #getSelector()
1451     *
1452     * @return The right list padding.
1453     */
1454    public int getListPaddingRight() {
1455        return mListPadding.right;
1456    }
1457
1458    /**
1459     * Get a view and have it show the data associated with the specified
1460     * position. This is called when we have already discovered that the view is
1461     * not available for reuse in the recycle bin. The only choices left are
1462     * converting an old view or making a new one.
1463     *
1464     * @param position The position to display
1465     * @return A view displaying the data associated with the specified position
1466     */
1467    View obtainView(int position) {
1468        View scrapView;
1469
1470        scrapView = mRecycler.getScrapView(position);
1471
1472        View child;
1473        if (scrapView != null) {
1474            if (ViewDebug.TRACE_RECYCLER) {
1475                ViewDebug.trace(scrapView, ViewDebug.RecyclerTraceType.RECYCLE_FROM_SCRAP_HEAP,
1476                        position, -1);
1477            }
1478
1479            child = mAdapter.getView(position, scrapView, this);
1480
1481            if (ViewDebug.TRACE_RECYCLER) {
1482                ViewDebug.trace(child, ViewDebug.RecyclerTraceType.BIND_VIEW,
1483                        position, getChildCount());
1484            }
1485
1486            if (child != scrapView) {
1487                mRecycler.addScrapView(scrapView);
1488                if (mCacheColorHint != 0) {
1489                    child.setDrawingCacheBackgroundColor(mCacheColorHint);
1490                }
1491                if (ViewDebug.TRACE_RECYCLER) {
1492                    ViewDebug.trace(scrapView, ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP,
1493                            position, -1);
1494                }
1495            }
1496        } else {
1497            child = mAdapter.getView(position, null, this);
1498            if (mCacheColorHint != 0) {
1499                child.setDrawingCacheBackgroundColor(mCacheColorHint);
1500            }
1501            if (ViewDebug.TRACE_RECYCLER) {
1502                ViewDebug.trace(child, ViewDebug.RecyclerTraceType.NEW_VIEW,
1503                        position, getChildCount());
1504            }
1505        }
1506
1507        return child;
1508    }
1509
1510    void positionSelector(View sel) {
1511        final Rect selectorRect = mSelectorRect;
1512        selectorRect.set(sel.getLeft(), sel.getTop(), sel.getRight(), sel.getBottom());
1513        positionSelector(selectorRect.left, selectorRect.top, selectorRect.right,
1514                selectorRect.bottom);
1515
1516        final boolean isChildViewEnabled = mIsChildViewEnabled;
1517        if (sel.isEnabled() != isChildViewEnabled) {
1518            mIsChildViewEnabled = !isChildViewEnabled;
1519            refreshDrawableState();
1520        }
1521    }
1522
1523    private void positionSelector(int l, int t, int r, int b) {
1524        mSelectorRect.set(l - mSelectionLeftPadding, t - mSelectionTopPadding, r
1525                + mSelectionRightPadding, b + mSelectionBottomPadding);
1526    }
1527
1528    @Override
1529    protected void dispatchDraw(Canvas canvas) {
1530        int saveCount = 0;
1531        final boolean clipToPadding = (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
1532        if (clipToPadding) {
1533            saveCount = canvas.save();
1534            final int scrollX = mScrollX;
1535            final int scrollY = mScrollY;
1536            canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,
1537                    scrollX + mRight - mLeft - mPaddingRight,
1538                    scrollY + mBottom - mTop - mPaddingBottom);
1539            mGroupFlags &= ~CLIP_TO_PADDING_MASK;
1540        }
1541
1542        final boolean drawSelectorOnTop = mDrawSelectorOnTop;
1543        if (!drawSelectorOnTop) {
1544            drawSelector(canvas);
1545        }
1546
1547        super.dispatchDraw(canvas);
1548
1549        if (drawSelectorOnTop) {
1550            drawSelector(canvas);
1551        }
1552
1553        if (clipToPadding) {
1554            canvas.restoreToCount(saveCount);
1555            mGroupFlags |= CLIP_TO_PADDING_MASK;
1556        }
1557    }
1558
1559    @Override
1560    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
1561        if (getChildCount() > 0) {
1562            mDataChanged = true;
1563            rememberSyncState();
1564        }
1565
1566        if (mFastScroller != null) {
1567            mFastScroller.onSizeChanged(w, h, oldw, oldh);
1568        }
1569
1570        if (mInstallGesturesOverlay) {
1571            installGesturesOverlay();
1572            positionGesturesPopup();
1573        } else if (mGesturesPopup != null) {
1574            mGesturesPopup.update(w, h);
1575        }
1576    }
1577
1578    /**
1579     * @return True if the current touch mode requires that we draw the selector in the pressed
1580     *         state.
1581     */
1582    boolean touchModeDrawsInPressedState() {
1583        // FIXME use isPressed for this
1584        switch (mTouchMode) {
1585        case TOUCH_MODE_TAP:
1586        case TOUCH_MODE_DONE_WAITING:
1587            return true;
1588        default:
1589            return false;
1590        }
1591    }
1592
1593    /**
1594     * Indicates whether this view is in a state where the selector should be drawn. This will
1595     * happen if we have focus but are not in touch mode, or we are in the middle of displaying
1596     * the pressed state for an item.
1597     *
1598     * @return True if the selector should be shown
1599     */
1600    boolean shouldShowSelector() {
1601        return (hasFocus() && !isInTouchMode()) || touchModeDrawsInPressedState();
1602    }
1603
1604    private void drawSelector(Canvas canvas) {
1605        if (shouldShowSelector() && mSelectorRect != null && !mSelectorRect.isEmpty()) {
1606            final Drawable selector = mSelector;
1607            selector.setBounds(mSelectorRect);
1608            selector.draw(canvas);
1609        }
1610    }
1611
1612    /**
1613     * Controls whether the selection highlight drawable should be drawn on top of the item or
1614     * behind it.
1615     *
1616     * @param onTop If true, the selector will be drawn on the item it is highlighting. The default
1617     *        is false.
1618     *
1619     * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop
1620     */
1621    public void setDrawSelectorOnTop(boolean onTop) {
1622        mDrawSelectorOnTop = onTop;
1623    }
1624
1625    /**
1626     * Set a Drawable that should be used to highlight the currently selected item.
1627     *
1628     * @param resID A Drawable resource to use as the selection highlight.
1629     *
1630     * @attr ref android.R.styleable#AbsListView_listSelector
1631     */
1632    public void setSelector(int resID) {
1633        setSelector(getResources().getDrawable(resID));
1634    }
1635
1636    public void setSelector(Drawable sel) {
1637        if (mSelector != null) {
1638            mSelector.setCallback(null);
1639            unscheduleDrawable(mSelector);
1640        }
1641        mSelector = sel;
1642        Rect padding = new Rect();
1643        sel.getPadding(padding);
1644        mSelectionLeftPadding = padding.left;
1645        mSelectionTopPadding = padding.top;
1646        mSelectionRightPadding = padding.right;
1647        mSelectionBottomPadding = padding.bottom;
1648        sel.setCallback(this);
1649        sel.setState(getDrawableState());
1650    }
1651
1652    /**
1653     * Returns the selector {@link android.graphics.drawable.Drawable} that is used to draw the
1654     * selection in the list.
1655     *
1656     * @return the drawable used to display the selector
1657     */
1658    public Drawable getSelector() {
1659        return mSelector;
1660    }
1661
1662    /**
1663     * Sets the selector state to "pressed" and posts a CheckForKeyLongPress to see if
1664     * this is a long press.
1665     */
1666    void keyPressed() {
1667        Drawable selector = mSelector;
1668        Rect selectorRect = mSelectorRect;
1669        if (selector != null && (isFocused() || touchModeDrawsInPressedState())
1670                && selectorRect != null && !selectorRect.isEmpty()) {
1671
1672            final View v = getChildAt(mSelectedPosition - mFirstPosition);
1673
1674            if (v != null) {
1675                if (v.hasFocusable()) return;
1676                v.setPressed(true);
1677            }
1678            setPressed(true);
1679
1680            final boolean longClickable = isLongClickable();
1681            Drawable d = selector.getCurrent();
1682            if (d != null && d instanceof TransitionDrawable) {
1683                if (longClickable) {
1684                    ((TransitionDrawable) d).startTransition(ViewConfiguration
1685                            .getLongPressTimeout());
1686                } else {
1687                    ((TransitionDrawable) d).resetTransition();
1688                }
1689            }
1690            if (longClickable && !mDataChanged) {
1691                if (mPendingCheckForKeyLongPress == null) {
1692                    mPendingCheckForKeyLongPress = new CheckForKeyLongPress();
1693                }
1694                mPendingCheckForKeyLongPress.rememberWindowAttachCount();
1695                postDelayed(mPendingCheckForKeyLongPress, ViewConfiguration.getLongPressTimeout());
1696            }
1697        }
1698    }
1699
1700    public void setScrollIndicators(View up, View down) {
1701        mScrollUp = up;
1702        mScrollDown = down;
1703    }
1704
1705    @Override
1706    protected void drawableStateChanged() {
1707        super.drawableStateChanged();
1708        if (mSelector != null) {
1709            mSelector.setState(getDrawableState());
1710        }
1711    }
1712
1713    @Override
1714    protected int[] onCreateDrawableState(int extraSpace) {
1715        // If the child view is enabled then do the default behavior.
1716        if (mIsChildViewEnabled) {
1717            // Common case
1718            return super.onCreateDrawableState(extraSpace);
1719        }
1720
1721        // The selector uses this View's drawable state. The selected child view
1722        // is disabled, so we need to remove the enabled state from the drawable
1723        // states.
1724        final int enabledState = ENABLED_STATE_SET[0];
1725
1726        // If we don't have any extra space, it will return one of the static state arrays,
1727        // and clearing the enabled state on those arrays is a bad thing!  If we specify
1728        // we need extra space, it will create+copy into a new array that safely mutable.
1729        int[] state = super.onCreateDrawableState(extraSpace + 1);
1730        int enabledPos = -1;
1731        for (int i = state.length - 1; i >= 0; i--) {
1732            if (state[i] == enabledState) {
1733                enabledPos = i;
1734                break;
1735            }
1736        }
1737
1738        // Remove the enabled state
1739        if (enabledPos >= 0) {
1740            System.arraycopy(state, enabledPos + 1, state, enabledPos,
1741                    state.length - enabledPos - 1);
1742        }
1743
1744        return state;
1745    }
1746
1747    @Override
1748    public boolean verifyDrawable(Drawable dr) {
1749        return mSelector == dr || super.verifyDrawable(dr);
1750    }
1751
1752    @Override
1753    protected void onAttachedToWindow() {
1754        super.onAttachedToWindow();
1755
1756        final ViewTreeObserver treeObserver = getViewTreeObserver();
1757        if (treeObserver != null) {
1758            treeObserver.addOnTouchModeChangeListener(this);
1759            if (mTextFilterEnabled && mPopup != null && !mGlobalLayoutListenerAddedFilter) {
1760                treeObserver.addOnGlobalLayoutListener(this);
1761            }
1762            if (mGestures != GESTURES_NONE && mGesturesPopup != null &&
1763                    !mGlobalLayoutListenerAddedGestures) {
1764                treeObserver.addOnGlobalLayoutListener(mGesturesLayoutListener);
1765            }
1766        }
1767    }
1768
1769    @Override
1770    protected void onDetachedFromWindow() {
1771        super.onDetachedFromWindow();
1772
1773        final ViewTreeObserver treeObserver = getViewTreeObserver();
1774        if (treeObserver != null) {
1775            treeObserver.removeOnTouchModeChangeListener(this);
1776            if (mTextFilterEnabled && mPopup != null) {
1777                treeObserver.removeGlobalOnLayoutListener(this);
1778                mGlobalLayoutListenerAddedFilter = false;
1779            }
1780            if (mGesturesLayoutListener != null && mGesturesPopup != null) {
1781                mGlobalLayoutListenerAddedGestures = false;
1782                treeObserver.removeGlobalOnLayoutListener(mGesturesLayoutListener);
1783            }
1784        }
1785    }
1786
1787    @Override
1788    public void onWindowFocusChanged(boolean hasWindowFocus) {
1789        super.onWindowFocusChanged(hasWindowFocus);
1790
1791        final int touchMode = isInTouchMode() ? TOUCH_MODE_ON : TOUCH_MODE_OFF;
1792
1793        if (!hasWindowFocus) {
1794            setChildrenDrawingCacheEnabled(false);
1795            removeCallbacks(mFlingRunnable);
1796            // Always hide the type filter
1797            dismissPopup();
1798            dismissGesturesPopup();
1799
1800            if (touchMode == TOUCH_MODE_OFF) {
1801                // Remember the last selected element
1802                mResurrectToPosition = mSelectedPosition;
1803            }
1804        } else {
1805            if (mFiltered) {
1806                // Show the type filter only if a filter is in effect
1807                showPopup();
1808            }
1809            if (mGestures != GESTURES_NONE) {
1810                showGesturesPopup();
1811            }
1812
1813            // If we changed touch mode since the last time we had focus
1814            if (touchMode != mLastTouchMode && mLastTouchMode != TOUCH_MODE_UNKNOWN) {
1815                // If we come back in trackball mode, we bring the selection back
1816                if (touchMode == TOUCH_MODE_OFF) {
1817                    // This will trigger a layout
1818                    resurrectSelection();
1819
1820                // If we come back in touch mode, then we want to hide the selector
1821                } else {
1822                    hideSelector();
1823                    mLayoutMode = LAYOUT_NORMAL;
1824                    layoutChildren();
1825                }
1826            }
1827        }
1828
1829        mLastTouchMode = touchMode;
1830    }
1831
1832    /**
1833     * Creates the ContextMenuInfo returned from {@link #getContextMenuInfo()}. This
1834     * methods knows the view, position and ID of the item that received the
1835     * long press.
1836     *
1837     * @param view The view that received the long press.
1838     * @param position The position of the item that received the long press.
1839     * @param id The ID of the item that received the long press.
1840     * @return The extra information that should be returned by
1841     *         {@link #getContextMenuInfo()}.
1842     */
1843    ContextMenuInfo createContextMenuInfo(View view, int position, long id) {
1844        return new AdapterContextMenuInfo(view, position, id);
1845    }
1846
1847    /**
1848     * A base class for Runnables that will check that their view is still attached to
1849     * the original window as when the Runnable was created.
1850     *
1851     */
1852    private class WindowRunnnable {
1853        private int mOriginalAttachCount;
1854
1855        public void rememberWindowAttachCount() {
1856            mOriginalAttachCount = getWindowAttachCount();
1857        }
1858
1859        public boolean sameWindow() {
1860            return hasWindowFocus() && getWindowAttachCount() == mOriginalAttachCount;
1861        }
1862    }
1863
1864    private class PerformClick extends WindowRunnnable implements Runnable {
1865        View mChild;
1866        int mClickMotionPosition;
1867
1868        public void run() {
1869            // The data has changed since we posted this action in the event queue,
1870            // bail out before bad things happen
1871            if (mDataChanged) return;
1872
1873            if (mAdapter != null && mItemCount > 0 &&
1874                    mClickMotionPosition < mAdapter.getCount() && sameWindow()) {
1875                performItemClick(mChild, mClickMotionPosition, getAdapter().getItemId(
1876                        mClickMotionPosition));
1877            }
1878        }
1879    }
1880
1881    private class CheckForLongPress extends WindowRunnnable implements Runnable {
1882        public void run() {
1883            final int motionPosition = mMotionPosition;
1884            final View child = getChildAt(motionPosition - mFirstPosition);
1885            if (child != null) {
1886                final int longPressPosition = mMotionPosition;
1887                final long longPressId = mAdapter.getItemId(mMotionPosition);
1888
1889                boolean handled = false;
1890                if (sameWindow() && !mDataChanged) {
1891                    handled = performLongPress(child, longPressPosition, longPressId);
1892                }
1893                if (handled) {
1894                    mTouchMode = TOUCH_MODE_REST;
1895                    setPressed(false);
1896                    child.setPressed(false);
1897                } else {
1898                    mTouchMode = TOUCH_MODE_DONE_WAITING;
1899                }
1900
1901            }
1902        }
1903    }
1904
1905    private class CheckForKeyLongPress extends WindowRunnnable implements Runnable {
1906        public void run() {
1907            if (isPressed() && mSelectedPosition >= 0) {
1908                int index = mSelectedPosition - mFirstPosition;
1909                View v = getChildAt(index);
1910
1911                if (!mDataChanged) {
1912                    boolean handled = false;
1913                    if (sameWindow()) {
1914                        handled = performLongPress(v, mSelectedPosition, mSelectedRowId);
1915                    }
1916                    if (handled) {
1917                        setPressed(false);
1918                        v.setPressed(false);
1919                    }
1920                } else {
1921                    setPressed(false);
1922                    if (v != null) v.setPressed(false);
1923                }
1924            }
1925        }
1926    }
1927
1928    private boolean performLongPress(final View child,
1929            final int longPressPosition, final long longPressId) {
1930        boolean handled = false;
1931
1932        dismissGesturesPopup();
1933
1934        if (mOnItemLongClickListener != null) {
1935            handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, child,
1936                    longPressPosition, longPressId);
1937        }
1938        if (!handled) {
1939            mContextMenuInfo = createContextMenuInfo(child, longPressPosition, longPressId);
1940            handled = super.showContextMenuForChild(AbsListView.this);
1941        }
1942        if (handled) {
1943            performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
1944        }
1945        return handled;
1946    }
1947
1948    @Override
1949    protected ContextMenuInfo getContextMenuInfo() {
1950        return mContextMenuInfo;
1951    }
1952
1953    @Override
1954    public boolean showContextMenuForChild(View originalView) {
1955        final int longPressPosition = getPositionForView(originalView);
1956        if (longPressPosition >= 0) {
1957            final long longPressId = mAdapter.getItemId(longPressPosition);
1958            boolean handled = false;
1959
1960            if (mOnItemLongClickListener != null) {
1961                handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, originalView,
1962                        longPressPosition, longPressId);
1963            }
1964            if (!handled) {
1965                mContextMenuInfo = createContextMenuInfo(
1966                        getChildAt(longPressPosition - mFirstPosition),
1967                        longPressPosition, longPressId);
1968                handled = super.showContextMenuForChild(originalView);
1969            }
1970
1971            return handled;
1972        }
1973        return false;
1974    }
1975
1976    @Override
1977    public boolean onKeyUp(int keyCode, KeyEvent event) {
1978        switch (keyCode) {
1979        case KeyEvent.KEYCODE_DPAD_CENTER:
1980        case KeyEvent.KEYCODE_ENTER:
1981            if (isPressed() && mSelectedPosition >= 0 && mAdapter != null &&
1982                    mSelectedPosition < mAdapter.getCount()) {
1983                final View view = getChildAt(mSelectedPosition - mFirstPosition);
1984                performItemClick(view, mSelectedPosition, mSelectedRowId);
1985                setPressed(false);
1986                if (view != null) view.setPressed(false);
1987                return true;
1988            }
1989        }
1990        return super.onKeyUp(keyCode, event);
1991    }
1992
1993    @Override
1994    protected void dispatchSetPressed(boolean pressed) {
1995        // Don't dispatch setPressed to our children. We call setPressed on ourselves to
1996        // get the selector in the right state, but we don't want to press each child.
1997    }
1998
1999    /**
2000     * Maps a point to a position in the list.
2001     *
2002     * @param x X in local coordinate
2003     * @param y Y in local coordinate
2004     * @return The position of the item which contains the specified point, or
2005     *         {@link #INVALID_POSITION} if the point does not intersect an item.
2006     */
2007    public int pointToPosition(int x, int y) {
2008        Rect frame = mTouchFrame;
2009        if (frame == null) {
2010            mTouchFrame = new Rect();
2011            frame = mTouchFrame;
2012        }
2013
2014        final int count = getChildCount();
2015        for (int i = count - 1; i >= 0; i--) {
2016            final View child = getChildAt(i);
2017            if (child.getVisibility() == View.VISIBLE) {
2018                child.getHitRect(frame);
2019                if (frame.contains(x, y)) {
2020                    return mFirstPosition + i;
2021                }
2022            }
2023        }
2024        return INVALID_POSITION;
2025    }
2026
2027
2028    /**
2029     * Maps a point to a the rowId of the item which intersects that point.
2030     *
2031     * @param x X in local coordinate
2032     * @param y Y in local coordinate
2033     * @return The rowId of the item which contains the specified point, or {@link #INVALID_ROW_ID}
2034     *         if the point does not intersect an item.
2035     */
2036    public long pointToRowId(int x, int y) {
2037        int position = pointToPosition(x, y);
2038        if (position >= 0) {
2039            return mAdapter.getItemId(position);
2040        }
2041        return INVALID_ROW_ID;
2042    }
2043
2044    final class CheckForTap implements Runnable {
2045        public void run() {
2046            if (mTouchMode == TOUCH_MODE_DOWN) {
2047                mTouchMode = TOUCH_MODE_TAP;
2048                final View child = getChildAt(mMotionPosition - mFirstPosition);
2049                if (child != null && !child.hasFocusable()) {
2050                    mLayoutMode = LAYOUT_NORMAL;
2051
2052                    if (!mDataChanged) {
2053                        layoutChildren();
2054                        child.setPressed(true);
2055                        positionSelector(child);
2056                        setPressed(true);
2057
2058                        final int longPressTimeout = ViewConfiguration.getLongPressTimeout();
2059                        final boolean longClickable = isLongClickable();
2060
2061                        if (mSelector != null) {
2062                            Drawable d = mSelector.getCurrent();
2063                            if (d != null && d instanceof TransitionDrawable) {
2064                                if (longClickable) {
2065                                    ((TransitionDrawable) d).startTransition(longPressTimeout);
2066                                } else {
2067                                    ((TransitionDrawable) d).resetTransition();
2068                                }
2069                            }
2070                        }
2071
2072                        if (longClickable) {
2073                            if (mPendingCheckForLongPress == null) {
2074                                mPendingCheckForLongPress = new CheckForLongPress();
2075                            }
2076                            mPendingCheckForLongPress.rememberWindowAttachCount();
2077                            postDelayed(mPendingCheckForLongPress, longPressTimeout);
2078                        } else {
2079                            mTouchMode = TOUCH_MODE_DONE_WAITING;
2080                        }
2081                    } else {
2082                        mTouchMode = TOUCH_MODE_DONE_WAITING;
2083                    }
2084                }
2085            }
2086        }
2087    }
2088
2089    private boolean startScrollIfNeeded(int deltaY) {
2090        // Check if we have moved far enough that it looks more like a
2091        // scroll than a tap
2092        final int distance = Math.abs(deltaY);
2093        if (distance > mTouchSlop) {
2094            createScrollingCache();
2095            mTouchMode = TOUCH_MODE_SCROLL;
2096            mMotionCorrection = deltaY;
2097            final Handler handler = getHandler();
2098            // Handler should not be null unless the AbsListView is not attached to a
2099            // window, which would make it very hard to scroll it... but the monkeys
2100            // say it's possible.
2101            if (handler != null) {
2102                handler.removeCallbacks(mPendingCheckForLongPress);
2103            }
2104            setPressed(false);
2105            View motionView = getChildAt(mMotionPosition - mFirstPosition);
2106            if (motionView != null) {
2107                motionView.setPressed(false);
2108            }
2109            reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
2110            // Time to start stealing events! Once we've stolen them, don't let anyone
2111            // steal from us
2112            requestDisallowInterceptTouchEvent(true);
2113            return true;
2114        }
2115
2116        return false;
2117    }
2118
2119    public void onTouchModeChanged(boolean isInTouchMode) {
2120        if (isInTouchMode) {
2121            // Get rid of the selection when we enter touch mode
2122            hideSelector();
2123            // Layout, but only if we already have done so previously.
2124            // (Otherwise may clobber a LAYOUT_SYNC layout that was requested to restore
2125            // state.)
2126            if (getHeight() > 0 && getChildCount() > 0) {
2127                // We do not lose focus initiating a touch (since AbsListView is focusable in
2128                // touch mode). Force an initial layout to get rid of the selection.
2129                mLayoutMode = LAYOUT_NORMAL;
2130                layoutChildren();
2131            }
2132        }
2133    }
2134
2135    @Override
2136    public boolean onTouchEvent(MotionEvent ev) {
2137        if (mFastScroller != null) {
2138            boolean intercepted = mFastScroller.onTouchEvent(ev);
2139            if (intercepted) {
2140                return true;
2141            }
2142        }
2143
2144        final int action = ev.getAction();
2145        final int x = (int) ev.getX();
2146        final int y = (int) ev.getY();
2147
2148        View v;
2149        int deltaY;
2150
2151        if (mVelocityTracker == null) {
2152            mVelocityTracker = VelocityTracker.obtain();
2153        }
2154        mVelocityTracker.addMovement(ev);
2155
2156        switch (action) {
2157        case MotionEvent.ACTION_DOWN: {
2158            int motionPosition = pointToPosition(x, y);
2159            if (!mDataChanged) {
2160                if ((mTouchMode != TOUCH_MODE_FLING) && (motionPosition >= 0)
2161                        && (getAdapter().isEnabled(motionPosition))) {
2162                    // User clicked on an actual view (and was not stopping a fling). It might be a
2163                    // click or a scroll. Assume it is a click until proven otherwise
2164                    mTouchMode = TOUCH_MODE_DOWN;
2165                    // FIXME Debounce
2166                    if (mPendingCheckForTap == null) {
2167                        mPendingCheckForTap = new CheckForTap();
2168                    }
2169                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
2170                } else {
2171                    if (ev.getEdgeFlags() != 0 && motionPosition < 0) {
2172                        // If we couldn't find a view to click on, but the down event was touching
2173                        // the edge, we will bail out and try again. This allows the edge correcting
2174                        // code in ViewRoot to try to find a nearby view to select
2175                        return false;
2176                    }
2177                    // User clicked on whitespace, or stopped a fling. It is a scroll.
2178                    createScrollingCache();
2179                    mTouchMode = TOUCH_MODE_SCROLL;
2180                    mMotionCorrection = 0;
2181                    motionPosition = findMotionRow(y);
2182                    reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
2183                }
2184            }
2185
2186            if (motionPosition >= 0) {
2187                // Remember where the motion event started
2188                v = getChildAt(motionPosition - mFirstPosition);
2189                mMotionViewOriginalTop = v.getTop();
2190                mMotionX = x;
2191                mMotionY = y;
2192                mMotionPosition = motionPosition;
2193            }
2194            mLastY = Integer.MIN_VALUE;
2195            break;
2196        }
2197
2198        case MotionEvent.ACTION_MOVE: {
2199            deltaY = y - mMotionY;
2200            switch (mTouchMode) {
2201            case TOUCH_MODE_DOWN:
2202            case TOUCH_MODE_TAP:
2203            case TOUCH_MODE_DONE_WAITING:
2204                // Check if we have moved far enough that it looks more like a
2205                // scroll than a tap
2206                startScrollIfNeeded(deltaY);
2207                break;
2208            case TOUCH_MODE_SCROLL:
2209                if (PROFILE_SCROLLING) {
2210                    if (!mScrollProfilingStarted) {
2211                        Debug.startMethodTracing("AbsListViewScroll");
2212                        mScrollProfilingStarted = true;
2213                    }
2214                }
2215
2216                if (y != mLastY) {
2217                    deltaY -= mMotionCorrection;
2218                    int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY;
2219                    trackMotionScroll(deltaY, incrementalDeltaY);
2220
2221                    // Check to see if we have bumped into the scroll limit
2222                    View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
2223                    if (motionView != null) {
2224                        // Check if the top of the motion view is where it is
2225                        // supposed to be
2226                        if (motionView.getTop() != mMotionViewNewTop) {
2227                            // We did not scroll the full amount. Treat this essentially like the
2228                            // start of a new touch scroll
2229                            final int motionPosition = findMotionRow(y);
2230
2231                            mMotionCorrection = 0;
2232                            motionView = getChildAt(motionPosition - mFirstPosition);
2233                            mMotionViewOriginalTop = motionView.getTop();
2234                            mMotionY = y;
2235                            mMotionPosition = motionPosition;
2236                        }
2237                    }
2238                    mLastY = y;
2239                }
2240                break;
2241            }
2242
2243            break;
2244        }
2245
2246        case MotionEvent.ACTION_UP: {
2247            switch (mTouchMode) {
2248            case TOUCH_MODE_DOWN:
2249            case TOUCH_MODE_TAP:
2250            case TOUCH_MODE_DONE_WAITING:
2251                final int motionPosition = mMotionPosition;
2252                final View child = getChildAt(motionPosition - mFirstPosition);
2253                if (child != null && !child.hasFocusable()) {
2254                    if (mTouchMode != TOUCH_MODE_DOWN) {
2255                        child.setPressed(false);
2256                    }
2257
2258                    if (mPerformClick == null) {
2259                        mPerformClick = new PerformClick();
2260                    }
2261
2262                    final AbsListView.PerformClick performClick = mPerformClick;
2263                    performClick.mChild = child;
2264                    performClick.mClickMotionPosition = motionPosition;
2265                    performClick.rememberWindowAttachCount();
2266
2267                    mResurrectToPosition = motionPosition;
2268
2269                    if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) {
2270                        final Handler handler = getHandler();
2271                        if (handler != null) {
2272                            handler.removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
2273                                    mPendingCheckForTap : mPendingCheckForLongPress);
2274                        }
2275                        mLayoutMode = LAYOUT_NORMAL;
2276                        mTouchMode = TOUCH_MODE_TAP;
2277                        if (!mDataChanged) {
2278                            setSelectedPositionInt(mMotionPosition);
2279                            layoutChildren();
2280                            child.setPressed(true);
2281                            positionSelector(child);
2282                            setPressed(true);
2283                            if (mSelector != null) {
2284                                Drawable d = mSelector.getCurrent();
2285                                if (d != null && d instanceof TransitionDrawable) {
2286                                    ((TransitionDrawable)d).resetTransition();
2287                                }
2288                            }
2289                            postDelayed(new Runnable() {
2290                                public void run() {
2291                                    child.setPressed(false);
2292                                    setPressed(false);
2293                                    if (!mDataChanged) {
2294                                        post(performClick);
2295                                    }
2296                                    mTouchMode = TOUCH_MODE_REST;
2297                                }
2298                            }, ViewConfiguration.getPressedStateDuration());
2299                        }
2300                        return true;
2301                    } else {
2302                        if (!mDataChanged) {
2303                            post(performClick);
2304                        }
2305                    }
2306                }
2307                mTouchMode = TOUCH_MODE_REST;
2308                break;
2309            case TOUCH_MODE_SCROLL:
2310                final VelocityTracker velocityTracker = mVelocityTracker;
2311                velocityTracker.computeCurrentVelocity(1000);
2312                int initialVelocity = (int)velocityTracker.getYVelocity();
2313
2314                if ((Math.abs(initialVelocity) >
2315                        ViewConfiguration.get(mContext).getScaledMinimumFlingVelocity()) &&
2316                        (getChildCount() > 0)) {
2317                    if (mFlingRunnable == null) {
2318                        mFlingRunnable = new FlingRunnable();
2319                    }
2320                    reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
2321                    mFlingRunnable.start(-initialVelocity);
2322                } else {
2323                    mTouchMode = TOUCH_MODE_REST;
2324                    reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
2325                }
2326            }
2327
2328            setPressed(false);
2329
2330            // Need to redraw since we probably aren't drawing the selector anymore
2331            invalidate();
2332
2333            final Handler handler = getHandler();
2334            if (handler != null) {
2335                handler.removeCallbacks(mPendingCheckForLongPress);
2336            }
2337
2338            if (mVelocityTracker != null) {
2339                mVelocityTracker.recycle();
2340                mVelocityTracker = null;
2341            }
2342
2343            if (PROFILE_SCROLLING) {
2344                if (mScrollProfilingStarted) {
2345                    Debug.stopMethodTracing();
2346                    mScrollProfilingStarted = false;
2347                }
2348            }
2349            break;
2350        }
2351
2352        case MotionEvent.ACTION_CANCEL: {
2353            mTouchMode = TOUCH_MODE_REST;
2354            setPressed(false);
2355            View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
2356            if (motionView != null) {
2357                motionView.setPressed(false);
2358            }
2359            clearScrollingCache();
2360
2361            final Handler handler = getHandler();
2362            if (handler != null) {
2363                handler.removeCallbacks(mPendingCheckForLongPress);
2364            }
2365
2366            if (mVelocityTracker != null) {
2367                mVelocityTracker.recycle();
2368                mVelocityTracker = null;
2369            }
2370        }
2371
2372        }
2373
2374        return true;
2375    }
2376
2377    @Override
2378    public void draw(Canvas canvas) {
2379        super.draw(canvas);
2380        if (mFastScroller != null) {
2381            mFastScroller.draw(canvas);
2382        }
2383    }
2384
2385    @Override
2386    public boolean onInterceptTouchEvent(MotionEvent ev) {
2387        int action = ev.getAction();
2388        int x = (int) ev.getX();
2389        int y = (int) ev.getY();
2390        View v;
2391
2392        if (mFastScroller != null) {
2393            boolean intercepted = mFastScroller.onInterceptTouchEvent(ev);
2394            if (intercepted) {
2395                return true;
2396            }
2397        }
2398
2399        switch (action) {
2400        case MotionEvent.ACTION_DOWN: {
2401            int motionPosition = findMotionRow(y);
2402            if (mTouchMode != TOUCH_MODE_FLING && motionPosition >= 0) {
2403                // User clicked on an actual view (and was not stopping a fling).
2404                // Remember where the motion event started
2405                v = getChildAt(motionPosition - mFirstPosition);
2406                mMotionViewOriginalTop = v.getTop();
2407                mMotionX = x;
2408                mMotionY = y;
2409                mMotionPosition = motionPosition;
2410                mTouchMode = TOUCH_MODE_DOWN;
2411                clearScrollingCache();
2412            }
2413            mLastY = Integer.MIN_VALUE;
2414            break;
2415        }
2416
2417        case MotionEvent.ACTION_MOVE: {
2418            switch (mTouchMode) {
2419            case TOUCH_MODE_DOWN:
2420                if (startScrollIfNeeded(y - mMotionY)) {
2421                    return true;
2422                }
2423                break;
2424            }
2425            break;
2426        }
2427
2428        case MotionEvent.ACTION_UP: {
2429            mTouchMode = TOUCH_MODE_REST;
2430            reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
2431            break;
2432        }
2433        }
2434
2435        return false;
2436    }
2437
2438    /**
2439     * {@inheritDoc}
2440     */
2441    @Override
2442    public void addTouchables(ArrayList<View> views) {
2443        final int count = getChildCount();
2444        final int firstPosition = mFirstPosition;
2445        final ListAdapter adapter = mAdapter;
2446
2447        if (adapter == null) {
2448            return;
2449        }
2450
2451        for (int i = 0; i < count; i++) {
2452            final View child = getChildAt(i);
2453            if (adapter.isEnabled(firstPosition + i)) {
2454                views.add(child);
2455            }
2456            child.addTouchables(views);
2457        }
2458    }
2459
2460    /**
2461     * Fires an "on scroll state changed" event to the registered
2462     * {@link android.widget.AbsListView.OnScrollListener}, if any. The state change
2463     * is fired only if the specified state is different from the previously known state.
2464     *
2465     * @param newState The new scroll state.
2466     */
2467    void reportScrollStateChange(int newState) {
2468        if (newState != mLastScrollState) {
2469            if (mOnScrollListener != null) {
2470                mOnScrollListener.onScrollStateChanged(this, newState);
2471                mLastScrollState = newState;
2472            }
2473        }
2474    }
2475
2476    /**
2477     * Responsible for fling behavior. Use {@link #start(int)} to
2478     * initiate a fling. Each frame of the fling is handled in {@link #run()}.
2479     * A FlingRunnable will keep re-posting itself until the fling is done.
2480     *
2481     */
2482    private class FlingRunnable implements Runnable {
2483        /**
2484         * Tracks the decay of a fling scroll
2485         */
2486        private Scroller mScroller;
2487
2488        /**
2489         * Y value reported by mScroller on the previous fling
2490         */
2491        private int mLastFlingY;
2492
2493        public FlingRunnable() {
2494            mScroller = new Scroller(getContext());
2495        }
2496
2497        public void start(int initialVelocity) {
2498            int initialY = initialVelocity < 0 ? Integer.MAX_VALUE : 0;
2499            mLastFlingY = initialY;
2500            mScroller.fling(0, initialY, 0, initialVelocity,
2501                    0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE);
2502            mTouchMode = TOUCH_MODE_FLING;
2503            post(this);
2504
2505            if (PROFILE_FLINGING) {
2506                if (!mFlingProfilingStarted) {
2507                    Debug.startMethodTracing("AbsListViewFling");
2508                    mFlingProfilingStarted = true;
2509                }
2510            }
2511        }
2512
2513        private void endFling() {
2514            mTouchMode = TOUCH_MODE_REST;
2515            reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
2516            clearScrollingCache();
2517        }
2518
2519        public void run() {
2520            if (mTouchMode != TOUCH_MODE_FLING) {
2521                return;
2522            }
2523
2524            if (mItemCount == 0 || getChildCount() == 0) {
2525                endFling();
2526                return;
2527            }
2528
2529            final Scroller scroller = mScroller;
2530            boolean more = scroller.computeScrollOffset();
2531            final int y = scroller.getCurrY();
2532
2533            // Flip sign to convert finger direction to list items direction
2534            // (e.g. finger moving down means list is moving towards the top)
2535            int delta = mLastFlingY - y;
2536
2537            // Pretend that each frame of a fling scroll is a touch scroll
2538            if (delta > 0) {
2539                // List is moving towards the top. Use first view as mMotionPosition
2540                mMotionPosition = mFirstPosition;
2541                final View firstView = getChildAt(0);
2542                mMotionViewOriginalTop = firstView.getTop();
2543
2544                // Don't fling more than 1 screen
2545                delta = Math.min(getHeight() - mPaddingBottom - mPaddingTop - 1, delta);
2546            } else {
2547                // List is moving towards the bottom. Use last view as mMotionPosition
2548                int offsetToLast = getChildCount() - 1;
2549                mMotionPosition = mFirstPosition + offsetToLast;
2550
2551                final View lastView = getChildAt(offsetToLast);
2552                mMotionViewOriginalTop = lastView.getTop();
2553
2554                // Don't fling more than 1 screen
2555                delta = Math.max(-(getHeight() - mPaddingBottom - mPaddingTop - 1), delta);
2556            }
2557
2558            trackMotionScroll(delta, delta);
2559
2560            // Check to see if we have bumped into the scroll limit
2561            View motionView = getChildAt(mMotionPosition - mFirstPosition);
2562            if (motionView != null) {
2563                // Check if the top of the motion view is where it is
2564                // supposed to be
2565                if (motionView.getTop() != mMotionViewNewTop) {
2566                   more = false;
2567                }
2568            }
2569
2570            if (more) {
2571                invalidate();
2572                mLastFlingY = y;
2573                post(this);
2574            } else {
2575                endFling();
2576                if (PROFILE_FLINGING) {
2577                    if (mFlingProfilingStarted) {
2578                        Debug.stopMethodTracing();
2579                        mFlingProfilingStarted = false;
2580                    }
2581                }
2582            }
2583        }
2584    }
2585
2586    private void createScrollingCache() {
2587        if (mScrollingCacheEnabled && !mCachingStarted) {
2588            setChildrenDrawnWithCacheEnabled(true);
2589            setChildrenDrawingCacheEnabled(true);
2590            mCachingStarted = true;
2591        }
2592    }
2593
2594    private void clearScrollingCache() {
2595        if (mClearScrollingCache == null) {
2596            mClearScrollingCache = new Runnable() {
2597                public void run() {
2598                    if (mCachingStarted) {
2599                        mCachingStarted = false;
2600                        setChildrenDrawnWithCacheEnabled(false);
2601                        if ((mPersistentDrawingCache & PERSISTENT_SCROLLING_CACHE) == 0) {
2602                            setChildrenDrawingCacheEnabled(false);
2603                        }
2604                        if (!isAlwaysDrawnWithCacheEnabled()) {
2605                            invalidate();
2606                        }
2607                    }
2608                }
2609            };
2610        }
2611        post(mClearScrollingCache);
2612    }
2613
2614    /**
2615     * Track a motion scroll
2616     *
2617     * @param deltaY Amount to offset mMotionView. This is the accumulated delta since the motion
2618     *        began. Positive numbers mean the user's finger is moving down the screen.
2619     * @param incrementalDeltaY Change in deltaY from the previous event.
2620     */
2621    void trackMotionScroll(int deltaY, int incrementalDeltaY) {
2622        final int childCount = getChildCount();
2623        if (childCount == 0) {
2624            return;
2625        }
2626
2627        final int firstTop = getChildAt(0).getTop();
2628        final int lastBottom = getChildAt(childCount - 1).getBottom();
2629
2630        final Rect listPadding = mListPadding;
2631
2632         // FIXME account for grid vertical spacing too?
2633        final int spaceAbove = listPadding.top - firstTop;
2634        final int end = getHeight() - listPadding.bottom;
2635        final int spaceBelow = lastBottom - end;
2636
2637        final int height = getHeight() - mPaddingBottom - mPaddingTop;
2638        if (deltaY < 0) {
2639            deltaY = Math.max(-(height - 1), deltaY);
2640        } else {
2641            deltaY = Math.min(height - 1, deltaY);
2642        }
2643
2644        if (incrementalDeltaY < 0) {
2645            incrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY);
2646        } else {
2647            incrementalDeltaY = Math.min(height - 1, incrementalDeltaY);
2648        }
2649
2650        final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
2651
2652        if (spaceAbove >= absIncrementalDeltaY && spaceBelow >= absIncrementalDeltaY) {
2653            hideSelector();
2654            offsetChildrenTopAndBottom(incrementalDeltaY);
2655            invalidate();
2656            mMotionViewNewTop = mMotionViewOriginalTop + deltaY;
2657        } else {
2658            final int firstPosition = mFirstPosition;
2659
2660            if (firstPosition == 0 && firstTop >= listPadding.top && deltaY > 0) {
2661                // Don't need to move views down if the top of the first position is already visible
2662                return;
2663            }
2664
2665            if (firstPosition + childCount == mItemCount && lastBottom <= end && deltaY < 0) {
2666                // Don't need to move views up if the bottom of the last position is already visible
2667                return;
2668            }
2669
2670            final boolean down = incrementalDeltaY < 0;
2671
2672            hideSelector();
2673
2674            final int headerViewsCount = getHeaderViewsCount();
2675            final int footerViewsStart = mItemCount - getFooterViewsCount();
2676
2677            int start = 0;
2678            int count = 0;
2679
2680            if (down) {
2681                final int top = listPadding.top - incrementalDeltaY;
2682                for (int i = 0; i < childCount; i++) {
2683                    final View child = getChildAt(i);
2684                    if (child.getBottom() >= top) {
2685                        break;
2686                    } else {
2687                        count++;
2688                        int position = firstPosition + i;
2689                        if (position >= headerViewsCount && position < footerViewsStart) {
2690                            mRecycler.addScrapView(child);
2691
2692                            if (ViewDebug.TRACE_RECYCLER) {
2693                                ViewDebug.trace(child,
2694                                        ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP,
2695                                        firstPosition + i, -1);
2696                            }
2697                        }
2698                    }
2699                }
2700            } else {
2701                final int bottom = getHeight() - listPadding.bottom - incrementalDeltaY;
2702                for (int i = childCount - 1; i >= 0; i--) {
2703                    final View child = getChildAt(i);
2704                    if (child.getTop() <= bottom) {
2705                        break;
2706                    } else {
2707                        start = i;
2708                        count++;
2709                        int position = firstPosition + i;
2710                        if (position >= headerViewsCount && position < footerViewsStart) {
2711                            mRecycler.addScrapView(child);
2712
2713                            if (ViewDebug.TRACE_RECYCLER) {
2714                                ViewDebug.trace(child,
2715                                        ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP,
2716                                        firstPosition + i, -1);
2717                            }
2718                        }
2719                    }
2720                }
2721            }
2722
2723            mMotionViewNewTop = mMotionViewOriginalTop + deltaY;
2724
2725            mBlockLayoutRequests = true;
2726            detachViewsFromParent(start, count);
2727            offsetChildrenTopAndBottom(incrementalDeltaY);
2728
2729            if (down) {
2730                mFirstPosition += count;
2731            }
2732
2733            invalidate();
2734            fillGap(down);
2735            mBlockLayoutRequests = false;
2736
2737            invokeOnItemScrollListener();
2738        }
2739    }
2740
2741    /**
2742     * Returns the number of header views in the list. Header views are special views
2743     * at the top of the list that should not be recycled during a layout.
2744     *
2745     * @return The number of header views, 0 in the default implementation.
2746     */
2747    int getHeaderViewsCount() {
2748        return 0;
2749    }
2750
2751    /**
2752     * Returns the number of footer views in the list. Footer views are special views
2753     * at the bottom of the list that should not be recycled during a layout.
2754     *
2755     * @return The number of footer views, 0 in the default implementation.
2756     */
2757    int getFooterViewsCount() {
2758        return 0;
2759    }
2760
2761    /**
2762     * Fills the gap left open by a touch-scroll. During a touch scroll, children that
2763     * remain on screen are shifted and the other ones are discarded. The role of this
2764     * method is to fill the gap thus created by performing a partial layout in the
2765     * empty space.
2766     *
2767     * @param down true if the scroll is going down, false if it is going up
2768     */
2769    abstract void fillGap(boolean down);
2770
2771    void hideSelector() {
2772        if (mSelectedPosition != INVALID_POSITION) {
2773            mResurrectToPosition = mSelectedPosition;
2774            if (mNextSelectedPosition >= 0 && mNextSelectedPosition != mSelectedPosition) {
2775                mResurrectToPosition = mNextSelectedPosition;
2776            }
2777            setSelectedPositionInt(INVALID_POSITION);
2778            setNextSelectedPositionInt(INVALID_POSITION);
2779            mSelectedTop = 0;
2780            mSelectorRect.setEmpty();
2781        }
2782    }
2783
2784    /**
2785     * @return A position to select. First we try mSelectedPosition. If that has been clobbered by
2786     * entering touch mode, we then try mResurrectToPosition. Values are pinned to the range
2787     * of items available in the adapter
2788     */
2789    int reconcileSelectedPosition() {
2790        int position = mSelectedPosition;
2791        if (position < 0) {
2792            position = mResurrectToPosition;
2793        }
2794        position = Math.max(0, position);
2795        position = Math.min(position, mItemCount - 1);
2796        return position;
2797    }
2798
2799    /**
2800     * Find the row closest to y. This row will be used as the motion row when scrolling
2801     *
2802     * @param y Where the user touched
2803     * @return The position of the first (or only) item in the row closest to y
2804     */
2805    abstract int findMotionRow(int y);
2806
2807    /**
2808     * Causes all the views to be rebuilt and redrawn.
2809     */
2810    public void invalidateViews() {
2811        mDataChanged = true;
2812        rememberSyncState();
2813        requestLayout();
2814        invalidate();
2815    }
2816
2817    /**
2818     * Makes the item at the supplied position selected.
2819     *
2820     * @param position the position of the new selection
2821     */
2822    abstract void setSelectionInt(int position);
2823
2824    /**
2825     * Attempt to bring the selection back if the user is switching from touch
2826     * to trackball mode
2827     * @return Whether selection was set to something.
2828     */
2829    boolean resurrectSelection() {
2830        final int childCount = getChildCount();
2831
2832        if (childCount <= 0) {
2833            return false;
2834        }
2835
2836        int selectedTop = 0;
2837        int selectedPos;
2838        int childrenTop = mListPadding.top;
2839        int childrenBottom = mBottom - mTop - mListPadding.bottom;
2840        final int firstPosition = mFirstPosition;
2841        final int toPosition = mResurrectToPosition;
2842        boolean down = true;
2843
2844        if (toPosition >= firstPosition && toPosition < firstPosition + childCount) {
2845            selectedPos = toPosition;
2846
2847            final View selected = getChildAt(selectedPos - mFirstPosition);
2848            selectedTop = selected.getTop();
2849            int selectedBottom = selected.getBottom();
2850
2851            // We are scrolled, don't get in the fade
2852            if (selectedTop < childrenTop) {
2853                selectedTop = childrenTop + getVerticalFadingEdgeLength();
2854            } else if (selectedBottom > childrenBottom) {
2855                selectedTop = childrenBottom - selected.getMeasuredHeight()
2856                        - getVerticalFadingEdgeLength();
2857            }
2858        } else {
2859            if (toPosition < firstPosition) {
2860                // Default to selecting whatever is first
2861                selectedPos = firstPosition;
2862                for (int i = 0; i < childCount; i++) {
2863                    final View v = getChildAt(i);
2864                    final int top = v.getTop();
2865
2866                    if (i == 0) {
2867                        // Remember the position of the first item
2868                        selectedTop = top;
2869                        // See if we are scrolled at all
2870                        if (firstPosition > 0 || top < childrenTop) {
2871                            // If we are scrolled, don't select anything that is
2872                            // in the fade region
2873                            childrenTop += getVerticalFadingEdgeLength();
2874                        }
2875                    }
2876                    if (top >= childrenTop) {
2877                        // Found a view whose top is fully visisble
2878                        selectedPos = firstPosition + i;
2879                        selectedTop = top;
2880                        break;
2881                    }
2882                }
2883            } else {
2884                final int itemCount = mItemCount;
2885                down = false;
2886                selectedPos = firstPosition + childCount - 1;
2887
2888                for (int i = childCount - 1; i >= 0; i--) {
2889                    final View v = getChildAt(i);
2890                    final int top = v.getTop();
2891                    final int bottom = v.getBottom();
2892
2893                    if (i == childCount - 1) {
2894                        selectedTop = top;
2895                        if (firstPosition + childCount < itemCount || bottom > childrenBottom) {
2896                            childrenBottom -= getVerticalFadingEdgeLength();
2897                        }
2898                    }
2899
2900                    if (bottom <= childrenBottom) {
2901                        selectedPos = firstPosition + i;
2902                        selectedTop = top;
2903                        break;
2904                    }
2905                }
2906            }
2907        }
2908
2909        mResurrectToPosition = INVALID_POSITION;
2910        removeCallbacks(mFlingRunnable);
2911        mTouchMode = TOUCH_MODE_REST;
2912        clearScrollingCache();
2913        mSpecificTop = selectedTop;
2914        selectedPos = lookForSelectablePosition(selectedPos, down);
2915        if (selectedPos >= firstPosition && selectedPos <= getLastVisiblePosition()) {
2916            mLayoutMode = LAYOUT_SPECIFIC;
2917            setSelectionInt(selectedPos);
2918            invokeOnItemScrollListener();
2919        } else {
2920            selectedPos = INVALID_POSITION;
2921        }
2922        reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
2923
2924        return selectedPos >= 0;
2925    }
2926
2927    @Override
2928    protected void handleDataChanged() {
2929        int count = mItemCount;
2930        if (count > 0) {
2931
2932            int newPos;
2933
2934            int selectablePos;
2935
2936            // Find the row we are supposed to sync to
2937            if (mNeedSync) {
2938                // Update this first, since setNextSelectedPositionInt inspects it
2939                mNeedSync = false;
2940
2941                if (mTranscriptMode == TRANSCRIPT_MODE_ALWAYS_SCROLL ||
2942                        (mTranscriptMode == TRANSCRIPT_MODE_NORMAL &&
2943                                mFirstPosition + getChildCount() >= mOldItemCount)) {
2944                    mLayoutMode = LAYOUT_FORCE_BOTTOM;
2945                    return;
2946                }
2947
2948                switch (mSyncMode) {
2949                case SYNC_SELECTED_POSITION:
2950                    if (isInTouchMode()) {
2951                        // We saved our state when not in touch mode. (We know this because
2952                        // mSyncMode is SYNC_SELECTED_POSITION.) Now we are trying to
2953                        // restore in touch mode. Just leave mSyncPosition as it is (possibly
2954                        // adjusting if the available range changed) and return.
2955                        mLayoutMode = LAYOUT_SYNC;
2956                        mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
2957
2958                        return;
2959                    } else {
2960                        // See if we can find a position in the new data with the same
2961                        // id as the old selection. This will change mSyncPosition.
2962                        newPos = findSyncPosition();
2963                        if (newPos >= 0) {
2964                            // Found it. Now verify that new selection is still selectable
2965                            selectablePos = lookForSelectablePosition(newPos, true);
2966                            if (selectablePos == newPos) {
2967                                // Same row id is selected
2968                                mSyncPosition = newPos;
2969
2970                                if (mSyncHeight == getHeight()) {
2971                                    // If we are at the same height as when we saved state, try
2972                                    // to restore the scroll position too.
2973                                    mLayoutMode = LAYOUT_SYNC;
2974                                } else {
2975                                    // We are not the same height as when the selection was saved, so
2976                                    // don't try to restore the exact position
2977                                    mLayoutMode = LAYOUT_SET_SELECTION;
2978                                }
2979
2980                                // Restore selection
2981                                setNextSelectedPositionInt(newPos);
2982                                return;
2983                            }
2984                        }
2985                    }
2986                    break;
2987                case SYNC_FIRST_POSITION:
2988                    // Leave mSyncPosition as it is -- just pin to available range
2989                    mLayoutMode = LAYOUT_SYNC;
2990                    mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
2991
2992                    return;
2993                }
2994            }
2995
2996            if (!isInTouchMode()) {
2997                // We couldn't find matching data -- try to use the same position
2998                newPos = getSelectedItemPosition();
2999
3000                // Pin position to the available range
3001                if (newPos >= count) {
3002                    newPos = count - 1;
3003                }
3004                if (newPos < 0) {
3005                    newPos = 0;
3006                }
3007
3008                // Make sure we select something selectable -- first look down
3009                selectablePos = lookForSelectablePosition(newPos, true);
3010
3011                if (selectablePos >= 0) {
3012                    setNextSelectedPositionInt(selectablePos);
3013                    return;
3014                } else {
3015                    // Looking down didn't work -- try looking up
3016                    selectablePos = lookForSelectablePosition(newPos, false);
3017                    if (selectablePos >= 0) {
3018                        setNextSelectedPositionInt(selectablePos);
3019                        return;
3020                    }
3021                }
3022            } else {
3023
3024                // We already know where we want to resurrect the selection
3025                if (mResurrectToPosition >= 0) {
3026                    return;
3027                }
3028            }
3029
3030        }
3031
3032        // Nothing is selected. Give up and reset everything.
3033        mLayoutMode = mStackFromBottom ? LAYOUT_FORCE_BOTTOM : LAYOUT_FORCE_TOP;
3034        mSelectedPosition = INVALID_POSITION;
3035        mSelectedRowId = INVALID_ROW_ID;
3036        mNextSelectedPosition = INVALID_POSITION;
3037        mNextSelectedRowId = INVALID_ROW_ID;
3038        mNeedSync = false;
3039        checkSelectionChanged();
3040    }
3041
3042    /**
3043     * Removes the filter window
3044     */
3045    private void dismissPopup() {
3046        if (mPopup != null) {
3047            mPopup.dismiss();
3048        }
3049    }
3050
3051    /**
3052     * Shows the filter window
3053     */
3054    private void showPopup() {
3055        // Make sure we have a window before showing the popup
3056        if (getWindowVisibility() == View.VISIBLE) {
3057            createTextFilter(true);
3058            positionPopup();
3059            // Make sure we get focus if we are showing the popup
3060            checkFocus();
3061        }
3062    }
3063
3064    private void positionPopup() {
3065        int screenHeight = getResources().getDisplayMetrics().heightPixels;
3066        final int[] xy = new int[2];
3067        getLocationOnScreen(xy);
3068        // TODO: The 20 below should come from the theme
3069        // TODO: And the gravity should be defined in the theme as well
3070        final int bottomGap = screenHeight - xy[1] - getHeight() + (int) (mDensityScale * 20);
3071        if (!mPopup.isShowing()) {
3072            mPopup.showAtLocation(this, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL,
3073                    xy[0], bottomGap);
3074        } else {
3075            mPopup.update(xy[0], bottomGap, -1, -1);
3076        }
3077    }
3078
3079    /**
3080     * What is the distance between the source and destination rectangles given the direction of
3081     * focus navigation between them? The direction basically helps figure out more quickly what is
3082     * self evident by the relationship between the rects...
3083     *
3084     * @param source the source rectangle
3085     * @param dest the destination rectangle
3086     * @param direction the direction
3087     * @return the distance between the rectangles
3088     */
3089    static int getDistance(Rect source, Rect dest, int direction) {
3090        int sX, sY; // source x, y
3091        int dX, dY; // dest x, y
3092        switch (direction) {
3093        case View.FOCUS_RIGHT:
3094            sX = source.right;
3095            sY = source.top + source.height() / 2;
3096            dX = dest.left;
3097            dY = dest.top + dest.height() / 2;
3098            break;
3099        case View.FOCUS_DOWN:
3100            sX = source.left + source.width() / 2;
3101            sY = source.bottom;
3102            dX = dest.left + dest.width() / 2;
3103            dY = dest.top;
3104            break;
3105        case View.FOCUS_LEFT:
3106            sX = source.left;
3107            sY = source.top + source.height() / 2;
3108            dX = dest.right;
3109            dY = dest.top + dest.height() / 2;
3110            break;
3111        case View.FOCUS_UP:
3112            sX = source.left + source.width() / 2;
3113            sY = source.top;
3114            dX = dest.left + dest.width() / 2;
3115            dY = dest.bottom;
3116            break;
3117        default:
3118            throw new IllegalArgumentException("direction must be one of "
3119                    + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
3120        }
3121        int deltaX = dX - sX;
3122        int deltaY = dY - sY;
3123        return deltaY * deltaY + deltaX * deltaX;
3124    }
3125
3126    @Override
3127    protected boolean isInFilterMode() {
3128        return mFiltered;
3129    }
3130
3131    /**
3132     * Sends a key to the text filter window
3133     *
3134     * @param keyCode The keycode for the event
3135     * @param event The actual key event
3136     *
3137     * @return True if the text filter handled the event, false otherwise.
3138     */
3139    boolean sendToTextFilter(int keyCode, int count, KeyEvent event) {
3140        if (!acceptFilter()) {
3141            return false;
3142        }
3143
3144        boolean handled = false;
3145        boolean okToSend = true;
3146        switch (keyCode) {
3147        case KeyEvent.KEYCODE_DPAD_UP:
3148        case KeyEvent.KEYCODE_DPAD_DOWN:
3149        case KeyEvent.KEYCODE_DPAD_LEFT:
3150        case KeyEvent.KEYCODE_DPAD_RIGHT:
3151        case KeyEvent.KEYCODE_DPAD_CENTER:
3152        case KeyEvent.KEYCODE_ENTER:
3153            okToSend = false;
3154            break;
3155        case KeyEvent.KEYCODE_BACK:
3156            if (mFiltered && mPopup != null && mPopup.isShowing() &&
3157                    event.getAction() == KeyEvent.ACTION_DOWN) {
3158                handled = true;
3159                mTextFilter.setText("");
3160            }
3161            okToSend = false;
3162            break;
3163        case KeyEvent.KEYCODE_SPACE:
3164            // Only send spaces once we are filtered
3165            okToSend = mFiltered = true;
3166            break;
3167        }
3168
3169        if (okToSend) {
3170            createTextFilter(true);
3171
3172            KeyEvent forwardEvent = event;
3173            if (forwardEvent.getRepeatCount() > 0) {
3174                forwardEvent = KeyEvent.changeTimeRepeat(event, event.getEventTime(), 0);
3175            }
3176
3177            int action = event.getAction();
3178            switch (action) {
3179                case KeyEvent.ACTION_DOWN:
3180                    handled = mTextFilter.onKeyDown(keyCode, forwardEvent);
3181                    break;
3182
3183                case KeyEvent.ACTION_UP:
3184                    handled = mTextFilter.onKeyUp(keyCode, forwardEvent);
3185                    break;
3186
3187                case KeyEvent.ACTION_MULTIPLE:
3188                    handled = mTextFilter.onKeyMultiple(keyCode, count, event);
3189                    break;
3190            }
3191        }
3192        return handled;
3193    }
3194
3195    /**
3196     * Return an InputConnection for editing of the filter text.
3197     */
3198    @Override
3199    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
3200        if (isTextFilterEnabled()) {
3201            // XXX we need to have the text filter created, so we can get an
3202            // InputConnection to proxy to.  Unfortunately this means we pretty
3203            // much need to make it as soon as a list view gets focus.
3204            createTextFilter(false);
3205            if (mPublicInputConnection == null) {
3206                mDefInputConnection = new BaseInputConnection(this, false);
3207                mPublicInputConnection = new InputConnectionWrapper(
3208                        mTextFilter.onCreateInputConnection(outAttrs), true) {
3209                    @Override
3210                    public boolean reportFullscreenMode(boolean enabled) {
3211                        // Use our own input connection, since it is
3212                        // the "real" one the IME is talking with.
3213                        return mDefInputConnection.reportFullscreenMode(enabled);
3214                    }
3215
3216                    @Override
3217                    public boolean performEditorAction(int editorAction) {
3218                        // The editor is off in its own window; we need to be
3219                        // the one that does this.
3220                        if (editorAction == EditorInfo.IME_ACTION_DONE) {
3221                            InputMethodManager imm = (InputMethodManager)
3222                                    getContext().getSystemService(
3223                                            Context.INPUT_METHOD_SERVICE);
3224                            if (imm != null) {
3225                                imm.hideSoftInputFromWindow(getWindowToken(), 0);
3226                            }
3227                            return true;
3228                        }
3229                        return false;
3230                    }
3231
3232                    @Override
3233                    public boolean sendKeyEvent(KeyEvent event) {
3234                        // Use our own input connection, since the filter
3235                        // text view may not be shown in a window so has
3236                        // no ViewRoot to dispatch events with.
3237                        return mDefInputConnection.sendKeyEvent(event);
3238                    }
3239                };
3240            }
3241            outAttrs.inputType = EditorInfo.TYPE_CLASS_TEXT
3242                    | EditorInfo.TYPE_TEXT_VARIATION_FILTER;
3243            outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE;
3244            return mPublicInputConnection;
3245        }
3246        return null;
3247    }
3248
3249    /**
3250     * For filtering we proxy an input connection to an internal text editor,
3251     * and this allows the proxying to happen.
3252     */
3253    @Override
3254    public boolean checkInputConnectionProxy(View view) {
3255        return view == mTextFilter;
3256    }
3257
3258    /**
3259     * Creates the window for the text filter and populates it with an EditText field;
3260     *
3261     * @param animateEntrance true if the window should appear with an animation
3262     */
3263    private void createTextFilter(boolean animateEntrance) {
3264        if (mPopup == null) {
3265            Context c = getContext();
3266            PopupWindow p = new PopupWindow(c);
3267            LayoutInflater layoutInflater = (LayoutInflater)
3268                    c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
3269            mTextFilter = (EditText) layoutInflater.inflate(
3270                    com.android.internal.R.layout.typing_filter, null);
3271            // For some reason setting this as the "real" input type changes
3272            // the text view in some way that it doesn't work, and I don't
3273            // want to figure out why this is.
3274            mTextFilter.setRawInputType(EditorInfo.TYPE_CLASS_TEXT
3275                    | EditorInfo.TYPE_TEXT_VARIATION_FILTER);
3276            mTextFilter.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI);
3277            mTextFilter.addTextChangedListener(this);
3278            p.setFocusable(false);
3279            p.setTouchable(false);
3280            p.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
3281            p.setContentView(mTextFilter);
3282            p.setWidth(LayoutParams.WRAP_CONTENT);
3283            p.setHeight(LayoutParams.WRAP_CONTENT);
3284            p.setBackgroundDrawable(null);
3285            mPopup = p;
3286            getViewTreeObserver().addOnGlobalLayoutListener(this);
3287            mGlobalLayoutListenerAddedFilter = true;
3288        }
3289        if (animateEntrance) {
3290            mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilter);
3291        } else {
3292            mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilterRestore);
3293        }
3294    }
3295
3296    /**
3297     * Clear the text filter.
3298     */
3299    public void clearTextFilter() {
3300        if (mFiltered) {
3301            mTextFilter.setText("");
3302            mFiltered = false;
3303            if (mPopup != null && mPopup.isShowing()) {
3304                dismissPopup();
3305            }
3306        }
3307    }
3308
3309    /**
3310     * Returns if the ListView currently has a text filter.
3311     */
3312    public boolean hasTextFilter() {
3313        return mFiltered;
3314    }
3315
3316    public void onGlobalLayout() {
3317        if (isShown()) {
3318            // Show the popup if we are filtered
3319            if (mFiltered && mPopup != null && !mPopup.isShowing()) {
3320                showPopup();
3321            }
3322        } else {
3323            // Hide the popup when we are no longer visible
3324            if (mPopup.isShowing()) {
3325                dismissPopup();
3326            }
3327        }
3328
3329    }
3330
3331    /**
3332     * For our text watcher that is associated with the text filter.  Does
3333     * nothing.
3334     */
3335    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
3336    }
3337
3338    /**
3339     * For our text watcher that is associated with the text filter. Performs
3340     * the actual filtering as the text changes, and takes care of hiding and
3341     * showing the popup displaying the currently entered filter text.
3342     */
3343    public void onTextChanged(CharSequence s, int start, int before, int count) {
3344        if (mPopup != null && isTextFilterEnabled()) {
3345            int length = s.length();
3346            boolean showing = mPopup.isShowing();
3347            if (!showing && length > 0) {
3348                // Show the filter popup if necessary
3349                showPopup();
3350                mFiltered = true;
3351            } else if (showing && length == 0) {
3352                // Remove the filter popup if the user has cleared all text
3353                dismissPopup();
3354                mFiltered = false;
3355            }
3356            if (mAdapter instanceof Filterable) {
3357                Filter f = ((Filterable) mAdapter).getFilter();
3358                // Filter should not be null when we reach this part
3359                if (f != null) {
3360                    f.filter(s, this);
3361                } else {
3362                    throw new IllegalStateException("You cannot call onTextChanged with a non "
3363                            + "filterable adapter");
3364                }
3365            }
3366        }
3367    }
3368
3369    /**
3370     * For our text watcher that is associated with the text filter.  Does
3371     * nothing.
3372     */
3373    public void afterTextChanged(Editable s) {
3374    }
3375
3376    public void onFilterComplete(int count) {
3377        if (mSelectedPosition < 0 && count > 0) {
3378            mResurrectToPosition = INVALID_POSITION;
3379            resurrectSelection();
3380        }
3381    }
3382
3383    @Override
3384    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
3385        return new LayoutParams(p);
3386    }
3387
3388    @Override
3389    public LayoutParams generateLayoutParams(AttributeSet attrs) {
3390        return new AbsListView.LayoutParams(getContext(), attrs);
3391    }
3392
3393    @Override
3394    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
3395        return p instanceof AbsListView.LayoutParams;
3396    }
3397
3398    /**
3399     * Puts the list or grid into transcript mode. In this mode the list or grid will always scroll
3400     * to the bottom to show new items.
3401     *
3402     * @param mode the transcript mode to set
3403     *
3404     * @see #TRANSCRIPT_MODE_DISABLED
3405     * @see #TRANSCRIPT_MODE_NORMAL
3406     * @see #TRANSCRIPT_MODE_ALWAYS_SCROLL
3407     */
3408    public void setTranscriptMode(int mode) {
3409        mTranscriptMode = mode;
3410    }
3411
3412    /**
3413     * Returns the current transcript mode.
3414     *
3415     * @return {@link #TRANSCRIPT_MODE_DISABLED}, {@link #TRANSCRIPT_MODE_NORMAL} or
3416     *         {@link #TRANSCRIPT_MODE_ALWAYS_SCROLL}
3417     */
3418    public int getTranscriptMode() {
3419        return mTranscriptMode;
3420    }
3421
3422    @Override
3423    public int getSolidColor() {
3424        return mCacheColorHint;
3425    }
3426
3427    /**
3428     * When set to a non-zero value, the cache color hint indicates that this list is always drawn
3429     * on top of a solid, single-color, opaque background
3430     *
3431     * @param color The background color
3432     */
3433    public void setCacheColorHint(int color) {
3434        mCacheColorHint = color;
3435    }
3436
3437    /**
3438     * When set to a non-zero value, the cache color hint indicates that this list is always drawn
3439     * on top of a solid, single-color, opaque background
3440     *
3441     * @return The cache color hint
3442     */
3443    public int getCacheColorHint() {
3444        return mCacheColorHint;
3445    }
3446
3447    /**
3448     * Move all views (excluding headers and footers) held by this AbsListView into the supplied
3449     * List. This includes views displayed on the screen as well as views stored in AbsListView's
3450     * internal view recycler.
3451     *
3452     * @param views A list into which to put the reclaimed views
3453     */
3454    public void reclaimViews(List<View> views) {
3455        int childCount = getChildCount();
3456        RecyclerListener listener = mRecycler.mRecyclerListener;
3457
3458        // Reclaim views on screen
3459        for (int i = 0; i < childCount; i++) {
3460            View child = getChildAt(i);
3461            AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
3462            // Don't reclaim header or footer views, or views that should be ignored
3463            if (lp != null && mRecycler.shouldRecycleViewType(lp.viewType)) {
3464                views.add(child);
3465                if (listener != null) {
3466                    // Pretend they went through the scrap heap
3467                    listener.onMovedToScrapHeap(child);
3468                }
3469            }
3470        }
3471        mRecycler.reclaimScrapViews(views);
3472        removeAllViewsInLayout();
3473    }
3474
3475    /**
3476     * @hide
3477     */
3478    @Override
3479    protected boolean onConsistencyCheck(int consistency) {
3480        boolean result = super.onConsistencyCheck(consistency);
3481
3482        final boolean checkLayout = (consistency & ViewDebug.CONSISTENCY_LAYOUT) != 0;
3483
3484        if (checkLayout) {
3485            // The active recycler must be empty
3486            final View[] activeViews = mRecycler.mActiveViews;
3487            int count = activeViews.length;
3488            for (int i = 0; i < count; i++) {
3489                if (activeViews[i] != null) {
3490                    result = false;
3491                    android.util.Log.d(ViewDebug.CONSISTENCY_LOG_TAG,
3492                            "AbsListView " + this + " has a view in its active recycler: " +
3493                                    activeViews[i]);
3494                }
3495            }
3496
3497            // All views in the recycler must NOT be on screen and must NOT have a parent
3498            final ArrayList<View> scrap = mRecycler.mCurrentScrap;
3499            if (!checkScrap(scrap)) result = false;
3500            final ArrayList<View>[] scraps = mRecycler.mScrapViews;
3501            count = scraps.length;
3502            for (int i = 0; i < count; i++) {
3503                if (!checkScrap(scraps[i])) result = false;
3504            }
3505        }
3506
3507        return result;
3508    }
3509
3510    private boolean checkScrap(ArrayList<View> scrap) {
3511        if (scrap == null) return true;
3512        boolean result = true;
3513
3514        final int count = scrap.size();
3515        for (int i = 0; i < count; i++) {
3516            final View view = scrap.get(i);
3517            if (view.getParent() != null) {
3518                result = false;
3519                android.util.Log.d(ViewDebug.CONSISTENCY_LOG_TAG, "AbsListView " + this +
3520                        " has a view in its scrap heap still attached to a parent: " + view);
3521            }
3522            if (indexOfChild(view) >= 0) {
3523                result = false;
3524                android.util.Log.d(ViewDebug.CONSISTENCY_LOG_TAG, "AbsListView " + this +
3525                        " has a view in its scrap heap that is also a direct child: " + view);
3526            }
3527        }
3528
3529        return result;
3530    }
3531
3532    /**
3533     * Sets the recycler listener to be notified whenever a View is set aside in
3534     * the recycler for later reuse. This listener can be used to free resources
3535     * associated to the View.
3536     *
3537     * @param listener The recycler listener to be notified of views set aside
3538     *        in the recycler.
3539     *
3540     * @see android.widget.AbsListView.RecycleBin
3541     * @see android.widget.AbsListView.RecyclerListener
3542     */
3543    public void setRecyclerListener(RecyclerListener listener) {
3544        mRecycler.mRecyclerListener = listener;
3545    }
3546
3547    /**
3548     * AbsListView extends LayoutParams to provide a place to hold the view type.
3549     */
3550    public static class LayoutParams extends ViewGroup.LayoutParams {
3551        /**
3552         * View type for this view, as returned by
3553         * {@link android.widget.Adapter#getItemViewType(int) }
3554         */
3555        int viewType;
3556
3557        /**
3558         * When this boolean is set, the view has been added to the AbsListView
3559         * at least once. It is used to know whether headers/footers have already
3560         * been added to the list view and whether they should be treated as
3561         * recycled views or not.
3562         */
3563        boolean recycledHeaderFooter;
3564
3565        public LayoutParams(Context c, AttributeSet attrs) {
3566            super(c, attrs);
3567        }
3568
3569        public LayoutParams(int w, int h) {
3570            super(w, h);
3571        }
3572
3573        public LayoutParams(int w, int h, int viewType) {
3574            super(w, h);
3575            this.viewType = viewType;
3576        }
3577
3578        public LayoutParams(ViewGroup.LayoutParams source) {
3579            super(source);
3580        }
3581    }
3582
3583    /**
3584     * A RecyclerListener is used to receive a notification whenever a View is placed
3585     * inside the RecycleBin's scrap heap. This listener is used to free resources
3586     * associated to Views placed in the RecycleBin.
3587     *
3588     * @see android.widget.AbsListView.RecycleBin
3589     * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
3590     */
3591    public static interface RecyclerListener {
3592        /**
3593         * Indicates that the specified View was moved into the recycler's scrap heap.
3594         * The view is not displayed on screen any more and any expensive resource
3595         * associated with the view should be discarded.
3596         *
3597         * @param view
3598         */
3599        void onMovedToScrapHeap(View view);
3600    }
3601
3602    /**
3603     * The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of
3604     * storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the
3605     * start of a layout. By construction, they are displaying current information. At the end of
3606     * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that
3607     * could potentially be used by the adapter to avoid allocating views unnecessarily.
3608     *
3609     * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
3610     * @see android.widget.AbsListView.RecyclerListener
3611     */
3612    class RecycleBin {
3613        private RecyclerListener mRecyclerListener;
3614
3615        /**
3616         * The position of the first view stored in mActiveViews.
3617         */
3618        private int mFirstActivePosition;
3619
3620        /**
3621         * Views that were on screen at the start of layout. This array is populated at the start of
3622         * layout, and at the end of layout all view in mActiveViews are moved to mScrapViews.
3623         * Views in mActiveViews represent a contiguous range of Views, with position of the first
3624         * view store in mFirstActivePosition.
3625         */
3626        private View[] mActiveViews = new View[0];
3627
3628        /**
3629         * Unsorted views that can be used by the adapter as a convert view.
3630         */
3631        private ArrayList<View>[] mScrapViews;
3632
3633        private int mViewTypeCount;
3634
3635        private ArrayList<View> mCurrentScrap;
3636
3637        public void setViewTypeCount(int viewTypeCount) {
3638            if (viewTypeCount < 1) {
3639                throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
3640            }
3641            //noinspection unchecked
3642            ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
3643            for (int i = 0; i < viewTypeCount; i++) {
3644                scrapViews[i] = new ArrayList<View>();
3645            }
3646            mViewTypeCount = viewTypeCount;
3647            mCurrentScrap = scrapViews[0];
3648            mScrapViews = scrapViews;
3649        }
3650
3651        public boolean shouldRecycleViewType(int viewType) {
3652            return viewType >= 0;
3653        }
3654
3655        /**
3656         * Clears the scrap heap.
3657         */
3658        void clear() {
3659            if (mViewTypeCount == 1) {
3660                final ArrayList<View> scrap = mCurrentScrap;
3661                final int scrapCount = scrap.size();
3662                for (int i = 0; i < scrapCount; i++) {
3663                    removeDetachedView(scrap.remove(scrapCount - 1 - i), false);
3664                }
3665            } else {
3666                final int typeCount = mViewTypeCount;
3667                for (int i = 0; i < typeCount; i++) {
3668                    final ArrayList<View> scrap = mScrapViews[i];
3669                    final int scrapCount = scrap.size();
3670                    for (int j = 0; j < scrapCount; j++) {
3671                        removeDetachedView(scrap.remove(scrapCount - 1 - j), false);
3672                    }
3673                }
3674            }
3675        }
3676
3677        /**
3678         * Fill ActiveViews with all of the children of the AbsListView.
3679         *
3680         * @param childCount The minimum number of views mActiveViews should hold
3681         * @param firstActivePosition The position of the first view that will be stored in
3682         *        mActiveViews
3683         */
3684        void fillActiveViews(int childCount, int firstActivePosition) {
3685            if (mActiveViews.length < childCount) {
3686                mActiveViews = new View[childCount];
3687            }
3688            mFirstActivePosition = firstActivePosition;
3689
3690            final View[] activeViews = mActiveViews;
3691            for (int i = 0; i < childCount; i++) {
3692                View child = getChildAt(i);
3693                AbsListView.LayoutParams lp = (AbsListView.LayoutParams)child.getLayoutParams();
3694                // Don't put header or footer views into the scrap heap
3695                if (lp != null && lp.viewType != AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
3696                    // Note:  We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views.
3697                    //        However, we will NOT place them into scrap views.
3698                    activeViews[i] = child;
3699                }
3700            }
3701        }
3702
3703        /**
3704         * Get the view corresponding to the specified position. The view will be removed from
3705         * mActiveViews if it is found.
3706         *
3707         * @param position The position to look up in mActiveViews
3708         * @return The view if it is found, null otherwise
3709         */
3710        View getActiveView(int position) {
3711            int index = position - mFirstActivePosition;
3712            final View[] activeViews = mActiveViews;
3713            if (index >=0 && index < activeViews.length) {
3714                final View match = activeViews[index];
3715                activeViews[index] = null;
3716                return match;
3717            }
3718            return null;
3719        }
3720
3721        /**
3722         * @return A view from the ScrapViews collection. These are unordered.
3723         */
3724        View getScrapView(int position) {
3725            ArrayList<View> scrapViews;
3726            if (mViewTypeCount == 1) {
3727                scrapViews = mCurrentScrap;
3728                int size = scrapViews.size();
3729                if (size > 0) {
3730                    return scrapViews.remove(size - 1);
3731                } else {
3732                    return null;
3733                }
3734            } else {
3735                int whichScrap = mAdapter.getItemViewType(position);
3736                if (whichScrap >= 0 && whichScrap < mScrapViews.length) {
3737                    scrapViews = mScrapViews[whichScrap];
3738                    int size = scrapViews.size();
3739                    if (size > 0) {
3740                        return scrapViews.remove(size - 1);
3741                    }
3742                }
3743            }
3744            return null;
3745        }
3746
3747        /**
3748         * Put a view into the ScapViews list. These views are unordered.
3749         *
3750         * @param scrap The view to add
3751         */
3752        void addScrapView(View scrap) {
3753            AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
3754            if (lp == null) {
3755                return;
3756            }
3757
3758            // Don't put header or footer views or views that should be ignored
3759            // into the scrap heap
3760            int viewType = lp.viewType;
3761            if (!shouldRecycleViewType(viewType)) {
3762                return;
3763            }
3764
3765            if (mViewTypeCount == 1) {
3766                mCurrentScrap.add(scrap);
3767            } else {
3768                mScrapViews[viewType].add(scrap);
3769            }
3770
3771            if (mRecyclerListener != null) {
3772                mRecyclerListener.onMovedToScrapHeap(scrap);
3773            }
3774        }
3775
3776        /**
3777         * Move all views remaining in mActiveViews to mScrapViews.
3778         */
3779        void scrapActiveViews() {
3780            final View[] activeViews = mActiveViews;
3781            final boolean hasListener = mRecyclerListener != null;
3782            final boolean multipleScraps = mViewTypeCount > 1;
3783
3784            ArrayList<View> scrapViews = mCurrentScrap;
3785            final int count = activeViews.length;
3786            for (int i = 0; i < count; ++i) {
3787                final View victim = activeViews[i];
3788                if (victim != null) {
3789                    int whichScrap = ((AbsListView.LayoutParams)
3790                            victim.getLayoutParams()).viewType;
3791
3792                    activeViews[i] = null;
3793
3794                    if (whichScrap == AdapterView.ITEM_VIEW_TYPE_IGNORE) {
3795                        // Do not move views that should be ignored
3796                        continue;
3797                    }
3798
3799                    if (multipleScraps) {
3800                        scrapViews = mScrapViews[whichScrap];
3801                    }
3802                    scrapViews.add(victim);
3803
3804                    if (hasListener) {
3805                        mRecyclerListener.onMovedToScrapHeap(victim);
3806                    }
3807
3808                    if (ViewDebug.TRACE_RECYCLER) {
3809                        ViewDebug.trace(victim,
3810                                ViewDebug.RecyclerTraceType.MOVE_FROM_ACTIVE_TO_SCRAP_HEAP,
3811                                mFirstActivePosition + i, -1);
3812                    }
3813                }
3814            }
3815
3816            pruneScrapViews();
3817        }
3818
3819        /**
3820         * Makes sure that the size of mScrapViews does not exceed the size of mActiveViews.
3821         * (This can happen if an adapter does not recycle its views).
3822         */
3823        private void pruneScrapViews() {
3824            final int maxViews = mActiveViews.length;
3825            final int viewTypeCount = mViewTypeCount;
3826            final ArrayList<View>[] scrapViews = mScrapViews;
3827            for (int i = 0; i < viewTypeCount; ++i) {
3828                final ArrayList<View> scrapPile = scrapViews[i];
3829                int size = scrapPile.size();
3830                final int extras = size - maxViews;
3831                size--;
3832                for (int j = 0; j < extras; j++) {
3833                    removeDetachedView(scrapPile.remove(size--), false);
3834                }
3835            }
3836        }
3837
3838        /**
3839         * Puts all views in the scrap heap into the supplied list.
3840         */
3841        void reclaimScrapViews(List<View> views) {
3842            if (mViewTypeCount == 1) {
3843                views.addAll(mCurrentScrap);
3844            } else {
3845                final int viewTypeCount = mViewTypeCount;
3846                final ArrayList<View>[] scrapViews = mScrapViews;
3847                for (int i = 0; i < viewTypeCount; ++i) {
3848                    final ArrayList<View> scrapPile = scrapViews[i];
3849                    views.addAll(scrapPile);
3850                }
3851            }
3852        }
3853    }
3854
3855    private class GesturesProcessor implements GestureOverlayView.OnGesturePerformedListener {
3856
3857        private static final double SCORE_THRESHOLD = 0.1;
3858
3859        private LetterRecognizer mRecognizer;
3860        private ArrayList<Prediction> mPredictions;
3861        private final KeyCharacterMap mKeyMap;
3862        private final char[] mHolder;
3863
3864        GesturesProcessor() {
3865            mRecognizer = LetterRecognizer.getLetterRecognizer(getContext(),
3866                    LetterRecognizer.RECOGNIZER_LATIN_LOWERCASE);
3867            if (mGestures == GESTURES_FILTER) {
3868                mKeyMap = KeyCharacterMap.load(KeyCharacterMap.BUILT_IN_KEYBOARD);
3869                mHolder = new char[1];
3870            } else {
3871                mKeyMap = null;
3872                mHolder = null;
3873            }
3874        }
3875
3876        public void onGesturePerformed(GestureOverlayView overlay, Gesture gesture) {
3877            mPredictions = mRecognizer.recognize(gesture, mPredictions);
3878            if (!mPredictions.isEmpty()) {
3879                final Prediction prediction = mPredictions.get(0);
3880                if (prediction.score > SCORE_THRESHOLD) {
3881                    switch (mGestures) {
3882                        case GESTURES_JUMP:
3883                            processJump(prediction);
3884                            break;
3885                        case GESTURES_FILTER:
3886                            processFilter(prediction);
3887                            break;
3888                    }
3889                }
3890            }
3891        }
3892
3893        private void processJump(Prediction prediction) {
3894            final Object[] sections = mFastScroller.getSections();
3895            if (sections != null) {
3896                final String name = prediction.name;
3897                final int count = sections.length;
3898
3899                int index = -1;
3900                for (int i = 0; i < count; i++) {
3901                    if (name.equalsIgnoreCase((String) sections[i])) {
3902                        index = i;
3903                        break;
3904                    }
3905                }
3906
3907                if (index != -1) {
3908                    final SectionIndexer indexer = mFastScroller.getSectionIndexer();
3909                    final int position = indexer.getPositionForSection(index);
3910                    setSelection(position);
3911                }
3912            }
3913        }
3914
3915        private void processFilter(Prediction prediction) {
3916            mHolder[0] = prediction.name.charAt(0);
3917            final KeyEvent[] events = mKeyMap.getEvents(mHolder);
3918            if (events != null) {
3919                for (KeyEvent event : events) {
3920                    sendToTextFilter(event.getKeyCode(), event.getRepeatCount(),
3921                            event);
3922                }
3923            }
3924        }
3925    }
3926}
3927