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