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