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