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