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