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