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