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