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