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         if (changed) {
   2080             int childCount = getChildCount();
   2081             for (int i = 0; i < childCount; i++) {
   2082                 getChildAt(i).forceLayout();
   2083             }
   2084             mRecycler.markChildrenDirty();
   2085         }
   2086 
   2087         if (mFastScroller != null && (mItemCount != mOldItemCount || mDataChanged)) {
   2088             mFastScroller.onItemCountChanged(mItemCount);
   2089         }
   2090 
   2091         layoutChildren();
   2092         mInLayout = false;
   2093 
   2094         mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;
   2095     }
   2096 
   2097     /**
   2098      * @hide
   2099      */
   2100     @Override
   2101     protected boolean setFrame(int left, int top, int right, int bottom) {
   2102         final boolean changed = super.setFrame(left, top, right, bottom);
   2103 
   2104         if (changed) {
   2105             // Reposition the popup when the frame has changed. This includes
   2106             // translating the widget, not just changing its dimension. The
   2107             // filter popup needs to follow the widget.
   2108             final boolean visible = getWindowVisibility() == View.VISIBLE;
   2109             if (mFiltered && visible && mPopup != null && mPopup.isShowing()) {
   2110                 positionPopup();
   2111             }
   2112         }
   2113 
   2114         return changed;
   2115     }
   2116 
   2117     /**
   2118      * Subclasses must override this method to layout their children.
   2119      */
   2120     protected void layoutChildren() {
   2121     }
   2122 
   2123     void updateScrollIndicators() {
   2124         if (mScrollUp != null) {
   2125             boolean canScrollUp;
   2126             // 0th element is not visible
   2127             canScrollUp = mFirstPosition > 0;
   2128 
   2129             // ... Or top of 0th element is not visible
   2130             if (!canScrollUp) {
   2131                 if (getChildCount() > 0) {
   2132                     View child = getChildAt(0);
   2133                     canScrollUp = child.getTop() < mListPadding.top;
   2134                 }
   2135             }
   2136 
   2137             mScrollUp.setVisibility(canScrollUp ? View.VISIBLE : View.INVISIBLE);
   2138         }
   2139 
   2140         if (mScrollDown != null) {
   2141             boolean canScrollDown;
   2142             int count = getChildCount();
   2143 
   2144             // Last item is not visible
   2145             canScrollDown = (mFirstPosition + count) < mItemCount;
   2146 
   2147             // ... Or bottom of the last element is not visible
   2148             if (!canScrollDown && count > 0) {
   2149                 View child = getChildAt(count - 1);
   2150                 canScrollDown = child.getBottom() > mBottom - mListPadding.bottom;
   2151             }
   2152 
   2153             mScrollDown.setVisibility(canScrollDown ? View.VISIBLE : View.INVISIBLE);
   2154         }
   2155     }
   2156 
   2157     @Override
   2158     @ViewDebug.ExportedProperty
   2159     public View getSelectedView() {
   2160         if (mItemCount > 0 && mSelectedPosition >= 0) {
   2161             return getChildAt(mSelectedPosition - mFirstPosition);
   2162         } else {
   2163             return null;
   2164         }
   2165     }
   2166 
   2167     /**
   2168      * List padding is the maximum of the normal view's padding and the padding of the selector.
   2169      *
   2170      * @see android.view.View#getPaddingTop()
   2171      * @see #getSelector()
   2172      *
   2173      * @return The top list padding.
   2174      */
   2175     public int getListPaddingTop() {
   2176         return mListPadding.top;
   2177     }
   2178 
   2179     /**
   2180      * List padding is the maximum of the normal view's padding and the padding of the selector.
   2181      *
   2182      * @see android.view.View#getPaddingBottom()
   2183      * @see #getSelector()
   2184      *
   2185      * @return The bottom list padding.
   2186      */
   2187     public int getListPaddingBottom() {
   2188         return mListPadding.bottom;
   2189     }
   2190 
   2191     /**
   2192      * List padding is the maximum of the normal view's padding and the padding of the selector.
   2193      *
   2194      * @see android.view.View#getPaddingLeft()
   2195      * @see #getSelector()
   2196      *
   2197      * @return The left list padding.
   2198      */
   2199     public int getListPaddingLeft() {
   2200         return mListPadding.left;
   2201     }
   2202 
   2203     /**
   2204      * List padding is the maximum of the normal view's padding and the padding of the selector.
   2205      *
   2206      * @see android.view.View#getPaddingRight()
   2207      * @see #getSelector()
   2208      *
   2209      * @return The right list padding.
   2210      */
   2211     public int getListPaddingRight() {
   2212         return mListPadding.right;
   2213     }
   2214 
   2215     /**
   2216      * Get a view and have it show the data associated with the specified
   2217      * position. This is called when we have already discovered that the view is
   2218      * not available for reuse in the recycle bin. The only choices left are
   2219      * converting an old view or making a new one.
   2220      *
   2221      * @param position The position to display
   2222      * @param isScrap Array of at least 1 boolean, the first entry will become true if
   2223      *                the returned view was taken from the scrap heap, false if otherwise.
   2224      *
   2225      * @return A view displaying the data associated with the specified position
   2226      */
   2227     View obtainView(int position, boolean[] isScrap) {
   2228         Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView");
   2229 
   2230         isScrap[0] = false;
   2231         View scrapView;
   2232 
   2233         scrapView = mRecycler.getTransientStateView(position);
   2234         if (scrapView == null) {
   2235             scrapView = mRecycler.getScrapView(position);
   2236         }
   2237 
   2238         View child;
   2239         if (scrapView != null) {
   2240             child = mAdapter.getView(position, scrapView, this);
   2241 
   2242             if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
   2243                 child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
   2244             }
   2245 
   2246             if (child != scrapView) {
   2247                 mRecycler.addScrapView(scrapView, position);
   2248                 if (mCacheColorHint != 0) {
   2249                     child.setDrawingCacheBackgroundColor(mCacheColorHint);
   2250                 }
   2251             } else {
   2252                 isScrap[0] = true;
   2253 
   2254                 // Clear any system-managed transient state so that we can
   2255                 // recycle this view and bind it to different data.
   2256                 if (child.isAccessibilityFocused()) {
   2257                     child.clearAccessibilityFocus();
   2258                 }
   2259 
   2260                 child.dispatchFinishTemporaryDetach();
   2261             }
   2262         } else {
   2263             child = mAdapter.getView(position, null, this);
   2264 
   2265             if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
   2266                 child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
   2267             }
   2268 
   2269             if (mCacheColorHint != 0) {
   2270                 child.setDrawingCacheBackgroundColor(mCacheColorHint);
   2271             }
   2272         }
   2273 
   2274         if (mAdapterHasStableIds) {
   2275             final ViewGroup.LayoutParams vlp = child.getLayoutParams();
   2276             LayoutParams lp;
   2277             if (vlp == null) {
   2278                 lp = (LayoutParams) generateDefaultLayoutParams();
   2279             } else if (!checkLayoutParams(vlp)) {
   2280                 lp = (LayoutParams) generateLayoutParams(vlp);
   2281             } else {
   2282                 lp = (LayoutParams) vlp;
   2283             }
   2284             lp.itemId = mAdapter.getItemId(position);
   2285             child.setLayoutParams(lp);
   2286         }
   2287 
   2288         if (AccessibilityManager.getInstance(mContext).isEnabled()) {
   2289             if (mAccessibilityDelegate == null) {
   2290                 mAccessibilityDelegate = new ListItemAccessibilityDelegate();
   2291             }
   2292             if (child.getAccessibilityDelegate() == null) {
   2293                 child.setAccessibilityDelegate(mAccessibilityDelegate);
   2294             }
   2295         }
   2296 
   2297         Trace.traceEnd(Trace.TRACE_TAG_VIEW);
   2298 
   2299         return child;
   2300     }
   2301 
   2302     class ListItemAccessibilityDelegate extends AccessibilityDelegate {
   2303         @Override
   2304         public AccessibilityNodeInfo createAccessibilityNodeInfo(View host) {
   2305             // If the data changed the children are invalid since the data model changed.
   2306             // Hence, we pretend they do not exist. After a layout the children will sync
   2307             // with the model at which point we notify that the accessibility state changed,
   2308             // so a service will be able to re-fetch the views.
   2309             if (mDataChanged) {
   2310                 return null;
   2311             }
   2312             return super.createAccessibilityNodeInfo(host);
   2313         }
   2314 
   2315         @Override
   2316         public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
   2317             super.onInitializeAccessibilityNodeInfo(host, info);
   2318 
   2319             final int position = getPositionForView(host);
   2320             onInitializeAccessibilityNodeInfoForItem(host, position, info);
   2321         }
   2322 
   2323         @Override
   2324         public boolean performAccessibilityAction(View host, int action, Bundle arguments) {
   2325             if (super.performAccessibilityAction(host, action, arguments)) {
   2326                 return true;
   2327             }
   2328 
   2329             final int position = getPositionForView(host);
   2330             final ListAdapter adapter = getAdapter();
   2331 
   2332             if ((position == INVALID_POSITION) || (adapter == null)) {
   2333                 // Cannot perform actions on invalid items.
   2334                 return false;
   2335             }
   2336 
   2337             if (!isEnabled() || !adapter.isEnabled(position)) {
   2338                 // Cannot perform actions on disabled items.
   2339                 return false;
   2340             }
   2341 
   2342             final long id = getItemIdAtPosition(position);
   2343 
   2344             switch (action) {
   2345                 case AccessibilityNodeInfo.ACTION_CLEAR_SELECTION: {
   2346                     if (getSelectedItemPosition() == position) {
   2347                         setSelection(INVALID_POSITION);
   2348                         return true;
   2349                     }
   2350                 } return false;
   2351                 case AccessibilityNodeInfo.ACTION_SELECT: {
   2352                     if (getSelectedItemPosition() != position) {
   2353                         setSelection(position);
   2354                         return true;
   2355                     }
   2356                 } return false;
   2357                 case AccessibilityNodeInfo.ACTION_CLICK: {
   2358                     if (isClickable()) {
   2359                         return performItemClick(host, position, id);
   2360                     }
   2361                 } return false;
   2362                 case AccessibilityNodeInfo.ACTION_LONG_CLICK: {
   2363                     if (isLongClickable()) {
   2364                         return performLongPress(host, position, id);
   2365                     }
   2366                 } return false;
   2367             }
   2368 
   2369             return false;
   2370         }
   2371     }
   2372 
   2373     /**
   2374      * Initializes an {@link AccessibilityNodeInfo} with information about a
   2375      * particular item in the list.
   2376      *
   2377      * @param view View representing the list item.
   2378      * @param position Position of the list item within the adapter.
   2379      * @param info Node info to populate.
   2380      */
   2381     public void onInitializeAccessibilityNodeInfoForItem(
   2382             View view, int position, AccessibilityNodeInfo info) {
   2383         final ListAdapter adapter = getAdapter();
   2384         if (position == INVALID_POSITION || adapter == null) {
   2385             // The item doesn't exist, so there's not much we can do here.
   2386             return;
   2387         }
   2388 
   2389         if (!isEnabled() || !adapter.isEnabled(position)) {
   2390             info.setEnabled(false);
   2391             return;
   2392         }
   2393 
   2394         if (position == getSelectedItemPosition()) {
   2395             info.setSelected(true);
   2396             info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_SELECTION);
   2397         } else {
   2398             info.addAction(AccessibilityNodeInfo.ACTION_SELECT);
   2399         }
   2400 
   2401         if (isClickable()) {
   2402             info.addAction(AccessibilityNodeInfo.ACTION_CLICK);
   2403             info.setClickable(true);
   2404         }
   2405 
   2406         if (isLongClickable()) {
   2407             info.addAction(AccessibilityNodeInfo.ACTION_LONG_CLICK);
   2408             info.setLongClickable(true);
   2409         }
   2410     }
   2411 
   2412     void positionSelector(int position, View sel) {
   2413         if (position != INVALID_POSITION) {
   2414             mSelectorPosition = position;
   2415         }
   2416 
   2417         final Rect selectorRect = mSelectorRect;
   2418         selectorRect.set(sel.getLeft(), sel.getTop(), sel.getRight(), sel.getBottom());
   2419         if (sel instanceof SelectionBoundsAdjuster) {
   2420             ((SelectionBoundsAdjuster)sel).adjustListItemSelectionBounds(selectorRect);
   2421         }
   2422         positionSelector(selectorRect.left, selectorRect.top, selectorRect.right,
   2423                 selectorRect.bottom);
   2424 
   2425         final boolean isChildViewEnabled = mIsChildViewEnabled;
   2426         if (sel.isEnabled() != isChildViewEnabled) {
   2427             mIsChildViewEnabled = !isChildViewEnabled;
   2428             if (getSelectedItemPosition() != INVALID_POSITION) {
   2429                 refreshDrawableState();
   2430             }
   2431         }
   2432     }
   2433 
   2434     private void positionSelector(int l, int t, int r, int b) {
   2435         mSelectorRect.set(l - mSelectionLeftPadding, t - mSelectionTopPadding, r
   2436                 + mSelectionRightPadding, b + mSelectionBottomPadding);
   2437     }
   2438 
   2439     @Override
   2440     protected void dispatchDraw(Canvas canvas) {
   2441         int saveCount = 0;
   2442         final boolean clipToPadding = (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
   2443         if (clipToPadding) {
   2444             saveCount = canvas.save();
   2445             final int scrollX = mScrollX;
   2446             final int scrollY = mScrollY;
   2447             canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,
   2448                     scrollX + mRight - mLeft - mPaddingRight,
   2449                     scrollY + mBottom - mTop - mPaddingBottom);
   2450             mGroupFlags &= ~CLIP_TO_PADDING_MASK;
   2451         }
   2452 
   2453         final boolean drawSelectorOnTop = mDrawSelectorOnTop;
   2454         if (!drawSelectorOnTop) {
   2455             drawSelector(canvas);
   2456         }
   2457 
   2458         super.dispatchDraw(canvas);
   2459 
   2460         if (drawSelectorOnTop) {
   2461             drawSelector(canvas);
   2462         }
   2463 
   2464         if (clipToPadding) {
   2465             canvas.restoreToCount(saveCount);
   2466             mGroupFlags |= CLIP_TO_PADDING_MASK;
   2467         }
   2468     }
   2469 
   2470     @Override
   2471     protected boolean isPaddingOffsetRequired() {
   2472         return (mGroupFlags & CLIP_TO_PADDING_MASK) != CLIP_TO_PADDING_MASK;
   2473     }
   2474 
   2475     @Override
   2476     protected int getLeftPaddingOffset() {
   2477         return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : -mPaddingLeft;
   2478     }
   2479 
   2480     @Override
   2481     protected int getTopPaddingOffset() {
   2482         return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : -mPaddingTop;
   2483     }
   2484 
   2485     @Override
   2486     protected int getRightPaddingOffset() {
   2487         return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : mPaddingRight;
   2488     }
   2489 
   2490     @Override
   2491     protected int getBottomPaddingOffset() {
   2492         return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : mPaddingBottom;
   2493     }
   2494 
   2495     @Override
   2496     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
   2497         if (getChildCount() > 0) {
   2498             mDataChanged = true;
   2499             rememberSyncState();
   2500         }
   2501 
   2502         if (mFastScroller != null) {
   2503             mFastScroller.onSizeChanged(w, h, oldw, oldh);
   2504         }
   2505     }
   2506 
   2507     /**
   2508      * @return True if the current touch mode requires that we draw the selector in the pressed
   2509      *         state.
   2510      */
   2511     boolean touchModeDrawsInPressedState() {
   2512         // FIXME use isPressed for this
   2513         switch (mTouchMode) {
   2514         case TOUCH_MODE_TAP:
   2515         case TOUCH_MODE_DONE_WAITING:
   2516             return true;
   2517         default:
   2518             return false;
   2519         }
   2520     }
   2521 
   2522     /**
   2523      * Indicates whether this view is in a state where the selector should be drawn. This will
   2524      * happen if we have focus but are not in touch mode, or we are in the middle of displaying
   2525      * the pressed state for an item.
   2526      *
   2527      * @return True if the selector should be shown
   2528      */
   2529     boolean shouldShowSelector() {
   2530         return (!isInTouchMode()) || (touchModeDrawsInPressedState() && isPressed());
   2531     }
   2532 
   2533     private void drawSelector(Canvas canvas) {
   2534         if (!mSelectorRect.isEmpty()) {
   2535             final Drawable selector = mSelector;
   2536             selector.setBounds(mSelectorRect);
   2537             selector.draw(canvas);
   2538         }
   2539     }
   2540 
   2541     /**
   2542      * Controls whether the selection highlight drawable should be drawn on top of the item or
   2543      * behind it.
   2544      *
   2545      * @param onTop If true, the selector will be drawn on the item it is highlighting. The default
   2546      *        is false.
   2547      *
   2548      * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop
   2549      */
   2550     public void setDrawSelectorOnTop(boolean onTop) {
   2551         mDrawSelectorOnTop = onTop;
   2552     }
   2553 
   2554     /**
   2555      * Set a Drawable that should be used to highlight the currently selected item.
   2556      *
   2557      * @param resID A Drawable resource to use as the selection highlight.
   2558      *
   2559      * @attr ref android.R.styleable#AbsListView_listSelector
   2560      */
   2561     public void setSelector(int resID) {
   2562         setSelector(getResources().getDrawable(resID));
   2563     }
   2564 
   2565     public void setSelector(Drawable sel) {
   2566         if (mSelector != null) {
   2567             mSelector.setCallback(null);
   2568             unscheduleDrawable(mSelector);
   2569         }
   2570         mSelector = sel;
   2571         Rect padding = new Rect();
   2572         sel.getPadding(padding);
   2573         mSelectionLeftPadding = padding.left;
   2574         mSelectionTopPadding = padding.top;
   2575         mSelectionRightPadding = padding.right;
   2576         mSelectionBottomPadding = padding.bottom;
   2577         sel.setCallback(this);
   2578         updateSelectorState();
   2579     }
   2580 
   2581     /**
   2582      * Returns the selector {@link android.graphics.drawable.Drawable} that is used to draw the
   2583      * selection in the list.
   2584      *
   2585      * @return the drawable used to display the selector
   2586      */
   2587     public Drawable getSelector() {
   2588         return mSelector;
   2589     }
   2590 
   2591     /**
   2592      * Sets the selector state to "pressed" and posts a CheckForKeyLongPress to see if
   2593      * this is a long press.
   2594      */
   2595     void keyPressed() {
   2596         if (!isEnabled() || !isClickable()) {
   2597             return;
   2598         }
   2599 
   2600         Drawable selector = mSelector;
   2601         Rect selectorRect = mSelectorRect;
   2602         if (selector != null && (isFocused() || touchModeDrawsInPressedState())
   2603                 && !selectorRect.isEmpty()) {
   2604 
   2605             final View v = getChildAt(mSelectedPosition - mFirstPosition);
   2606 
   2607             if (v != null) {
   2608                 if (v.hasFocusable()) return;
   2609                 v.setPressed(true);
   2610             }
   2611             setPressed(true);
   2612 
   2613             final boolean longClickable = isLongClickable();
   2614             Drawable d = selector.getCurrent();
   2615             if (d != null && d instanceof TransitionDrawable) {
   2616                 if (longClickable) {
   2617                     ((TransitionDrawable) d).startTransition(
   2618                             ViewConfiguration.getLongPressTimeout());
   2619                 } else {
   2620                     ((TransitionDrawable) d).resetTransition();
   2621                 }
   2622             }
   2623             if (longClickable && !mDataChanged) {
   2624                 if (mPendingCheckForKeyLongPress == null) {
   2625                     mPendingCheckForKeyLongPress = new CheckForKeyLongPress();
   2626                 }
   2627                 mPendingCheckForKeyLongPress.rememberWindowAttachCount();
   2628                 postDelayed(mPendingCheckForKeyLongPress, ViewConfiguration.getLongPressTimeout());
   2629             }
   2630         }
   2631     }
   2632 
   2633     public void setScrollIndicators(View up, View down) {
   2634         mScrollUp = up;
   2635         mScrollDown = down;
   2636     }
   2637 
   2638     void updateSelectorState() {
   2639         if (mSelector != null) {
   2640             if (shouldShowSelector()) {
   2641                 mSelector.setState(getDrawableState());
   2642             } else {
   2643                 mSelector.setState(StateSet.NOTHING);
   2644             }
   2645         }
   2646     }
   2647 
   2648     @Override
   2649     protected void drawableStateChanged() {
   2650         super.drawableStateChanged();
   2651         updateSelectorState();
   2652     }
   2653 
   2654     @Override
   2655     protected int[] onCreateDrawableState(int extraSpace) {
   2656         // If the child view is enabled then do the default behavior.
   2657         if (mIsChildViewEnabled) {
   2658             // Common case
   2659             return super.onCreateDrawableState(extraSpace);
   2660         }
   2661 
   2662         // The selector uses this View's drawable state. The selected child view
   2663         // is disabled, so we need to remove the enabled state from the drawable
   2664         // states.
   2665         final int enabledState = ENABLED_STATE_SET[0];
   2666 
   2667         // If we don't have any extra space, it will return one of the static state arrays,
   2668         // and clearing the enabled state on those arrays is a bad thing!  If we specify
   2669         // we need extra space, it will create+copy into a new array that safely mutable.
   2670         int[] state = super.onCreateDrawableState(extraSpace + 1);
   2671         int enabledPos = -1;
   2672         for (int i = state.length - 1; i >= 0; i--) {
   2673             if (state[i] == enabledState) {
   2674                 enabledPos = i;
   2675                 break;
   2676             }
   2677         }
   2678 
   2679         // Remove the enabled state
   2680         if (enabledPos >= 0) {
   2681             System.arraycopy(state, enabledPos + 1, state, enabledPos,
   2682                     state.length - enabledPos - 1);
   2683         }
   2684 
   2685         return state;
   2686     }
   2687 
   2688     @Override
   2689     public boolean verifyDrawable(Drawable dr) {
   2690         return mSelector == dr || super.verifyDrawable(dr);
   2691     }
   2692 
   2693     @Override
   2694     public void jumpDrawablesToCurrentState() {
   2695         super.jumpDrawablesToCurrentState();
   2696         if (mSelector != null) mSelector.jumpToCurrentState();
   2697     }
   2698 
   2699     @Override
   2700     protected void onAttachedToWindow() {
   2701         super.onAttachedToWindow();
   2702 
   2703         final ViewTreeObserver treeObserver = getViewTreeObserver();
   2704         treeObserver.addOnTouchModeChangeListener(this);
   2705         if (mTextFilterEnabled && mPopup != null && !mGlobalLayoutListenerAddedFilter) {
   2706             treeObserver.addOnGlobalLayoutListener(this);
   2707         }
   2708 
   2709         if (mAdapter != null && mDataSetObserver == null) {
   2710             mDataSetObserver = new AdapterDataSetObserver();
   2711             mAdapter.registerDataSetObserver(mDataSetObserver);
   2712 
   2713             // Data may have changed while we were detached. Refresh.
   2714             mDataChanged = true;
   2715             mOldItemCount = mItemCount;
   2716             mItemCount = mAdapter.getCount();
   2717         }
   2718     }
   2719 
   2720     @Override
   2721     protected void onDetachedFromWindow() {
   2722         super.onDetachedFromWindow();
   2723 
   2724         // Dismiss the popup in case onSaveInstanceState() was not invoked
   2725         dismissPopup();
   2726 
   2727         // Detach any view left in the scrap heap
   2728         mRecycler.clear();
   2729 
   2730         final ViewTreeObserver treeObserver = getViewTreeObserver();
   2731         treeObserver.removeOnTouchModeChangeListener(this);
   2732         if (mTextFilterEnabled && mPopup != null) {
   2733             treeObserver.removeOnGlobalLayoutListener(this);
   2734             mGlobalLayoutListenerAddedFilter = false;
   2735         }
   2736 
   2737         if (mAdapter != null && mDataSetObserver != null) {
   2738             mAdapter.unregisterDataSetObserver(mDataSetObserver);
   2739             mDataSetObserver = null;
   2740         }
   2741 
   2742         if (mScrollStrictSpan != null) {
   2743             mScrollStrictSpan.finish();
   2744             mScrollStrictSpan = null;
   2745         }
   2746 
   2747         if (mFlingStrictSpan != null) {
   2748             mFlingStrictSpan.finish();
   2749             mFlingStrictSpan = null;
   2750         }
   2751 
   2752         if (mFlingRunnable != null) {
   2753             removeCallbacks(mFlingRunnable);
   2754         }
   2755 
   2756         if (mPositionScroller != null) {
   2757             mPositionScroller.stop();
   2758         }
   2759 
   2760         if (mClearScrollingCache != null) {
   2761             removeCallbacks(mClearScrollingCache);
   2762         }
   2763 
   2764         if (mPerformClick != null) {
   2765             removeCallbacks(mPerformClick);
   2766         }
   2767 
   2768         if (mTouchModeReset != null) {
   2769             removeCallbacks(mTouchModeReset);
   2770             mTouchModeReset.run();
   2771         }
   2772     }
   2773 
   2774     @Override
   2775     public void onWindowFocusChanged(boolean hasWindowFocus) {
   2776         super.onWindowFocusChanged(hasWindowFocus);
   2777 
   2778         final int touchMode = isInTouchMode() ? TOUCH_MODE_ON : TOUCH_MODE_OFF;
   2779 
   2780         if (!hasWindowFocus) {
   2781             setChildrenDrawingCacheEnabled(false);
   2782             if (mFlingRunnable != null) {
   2783                 removeCallbacks(mFlingRunnable);
   2784                 // let the fling runnable report it's new state which
   2785                 // should be idle
   2786                 mFlingRunnable.endFling();
   2787                 if (mPositionScroller != null) {
   2788                     mPositionScroller.stop();
   2789                 }
   2790                 if (mScrollY != 0) {
   2791                     mScrollY = 0;
   2792                     invalidateParentCaches();
   2793                     finishGlows();
   2794                     invalidate();
   2795                 }
   2796             }
   2797             // Always hide the type filter
   2798             dismissPopup();
   2799 
   2800             if (touchMode == TOUCH_MODE_OFF) {
   2801                 // Remember the last selected element
   2802                 mResurrectToPosition = mSelectedPosition;
   2803             }
   2804         } else {
   2805             if (mFiltered && !mPopupHidden) {
   2806                 // Show the type filter only if a filter is in effect
   2807                 showPopup();
   2808             }
   2809 
   2810             // If we changed touch mode since the last time we had focus
   2811             if (touchMode != mLastTouchMode && mLastTouchMode != TOUCH_MODE_UNKNOWN) {
   2812                 // If we come back in trackball mode, we bring the selection back
   2813                 if (touchMode == TOUCH_MODE_OFF) {
   2814                     // This will trigger a layout
   2815                     resurrectSelection();
   2816 
   2817                 // If we come back in touch mode, then we want to hide the selector
   2818                 } else {
   2819                     hideSelector();
   2820                     mLayoutMode = LAYOUT_NORMAL;
   2821                     layoutChildren();
   2822                 }
   2823             }
   2824         }
   2825 
   2826         mLastTouchMode = touchMode;
   2827     }
   2828 
   2829     @Override
   2830     public void onRtlPropertiesChanged(int layoutDirection) {
   2831         super.onRtlPropertiesChanged(layoutDirection);
   2832         if (mFastScroller != null) {
   2833            mFastScroller.setScrollbarPosition(getVerticalScrollbarPosition());
   2834         }
   2835     }
   2836 
   2837     /**
   2838      * Creates the ContextMenuInfo returned from {@link #getContextMenuInfo()}. This
   2839      * methods knows the view, position and ID of the item that received the
   2840      * long press.
   2841      *
   2842      * @param view The view that received the long press.
   2843      * @param position The position of the item that received the long press.
   2844      * @param id The ID of the item that received the long press.
   2845      * @return The extra information that should be returned by
   2846      *         {@link #getContextMenuInfo()}.
   2847      */
   2848     ContextMenuInfo createContextMenuInfo(View view, int position, long id) {
   2849         return new AdapterContextMenuInfo(view, position, id);
   2850     }
   2851 
   2852     @Override
   2853     public void onCancelPendingInputEvents() {
   2854         super.onCancelPendingInputEvents();
   2855         if (mPerformClick != null) {
   2856             removeCallbacks(mPerformClick);
   2857         }
   2858         if (mPendingCheckForTap != null) {
   2859             removeCallbacks(mPendingCheckForTap);
   2860         }
   2861         if (mPendingCheckForLongPress != null) {
   2862             removeCallbacks(mPendingCheckForLongPress);
   2863         }
   2864         if (mPendingCheckForKeyLongPress != null) {
   2865             removeCallbacks(mPendingCheckForKeyLongPress);
   2866         }
   2867     }
   2868 
   2869     /**
   2870      * A base class for Runnables that will check that their view is still attached to
   2871      * the original window as when the Runnable was created.
   2872      *
   2873      */
   2874     private class WindowRunnnable {
   2875         private int mOriginalAttachCount;
   2876 
   2877         public void rememberWindowAttachCount() {
   2878             mOriginalAttachCount = getWindowAttachCount();
   2879         }
   2880 
   2881         public boolean sameWindow() {
   2882             return getWindowAttachCount() == mOriginalAttachCount;
   2883         }
   2884     }
   2885 
   2886     private class PerformClick extends WindowRunnnable implements Runnable {
   2887         int mClickMotionPosition;
   2888 
   2889         @Override
   2890         public void run() {
   2891             // The data has changed since we posted this action in the event queue,
   2892             // bail out before bad things happen
   2893             if (mDataChanged) return;
   2894 
   2895             final ListAdapter adapter = mAdapter;
   2896             final int motionPosition = mClickMotionPosition;
   2897             if (adapter != null && mItemCount > 0 &&
   2898                     motionPosition != INVALID_POSITION &&
   2899                     motionPosition < adapter.getCount() && sameWindow()) {
   2900                 final View view = getChildAt(motionPosition - mFirstPosition);
   2901                 // If there is no view, something bad happened (the view scrolled off the
   2902                 // screen, etc.) and we should cancel the click
   2903                 if (view != null) {
   2904                     performItemClick(view, motionPosition, adapter.getItemId(motionPosition));
   2905                 }
   2906             }
   2907         }
   2908     }
   2909 
   2910     private class CheckForLongPress extends WindowRunnnable implements Runnable {
   2911         @Override
   2912         public void run() {
   2913             final int motionPosition = mMotionPosition;
   2914             final View child = getChildAt(motionPosition - mFirstPosition);
   2915             if (child != null) {
   2916                 final int longPressPosition = mMotionPosition;
   2917                 final long longPressId = mAdapter.getItemId(mMotionPosition);
   2918 
   2919                 boolean handled = false;
   2920                 if (sameWindow() && !mDataChanged) {
   2921                     handled = performLongPress(child, longPressPosition, longPressId);
   2922                 }
   2923                 if (handled) {
   2924                     mTouchMode = TOUCH_MODE_REST;
   2925                     setPressed(false);
   2926                     child.setPressed(false);
   2927                 } else {
   2928                     mTouchMode = TOUCH_MODE_DONE_WAITING;
   2929                 }
   2930             }
   2931         }
   2932     }
   2933 
   2934     private class CheckForKeyLongPress extends WindowRunnnable implements Runnable {
   2935         @Override
   2936         public void run() {
   2937             if (isPressed() && mSelectedPosition >= 0) {
   2938                 int index = mSelectedPosition - mFirstPosition;
   2939                 View v = getChildAt(index);
   2940 
   2941                 if (!mDataChanged) {
   2942                     boolean handled = false;
   2943                     if (sameWindow()) {
   2944                         handled = performLongPress(v, mSelectedPosition, mSelectedRowId);
   2945                     }
   2946                     if (handled) {
   2947                         setPressed(false);
   2948                         v.setPressed(false);
   2949                     }
   2950                 } else {
   2951                     setPressed(false);
   2952                     if (v != null) v.setPressed(false);
   2953                 }
   2954             }
   2955         }
   2956     }
   2957 
   2958     boolean performLongPress(final View child,
   2959             final int longPressPosition, final long longPressId) {
   2960         // CHOICE_MODE_MULTIPLE_MODAL takes over long press.
   2961         if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
   2962             if (mChoiceActionMode == null &&
   2963                     (mChoiceActionMode = startActionMode(mMultiChoiceModeCallback)) != null) {
   2964                 setItemChecked(longPressPosition, true);
   2965                 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
   2966             }
   2967             return true;
   2968         }
   2969 
   2970         boolean handled = false;
   2971         if (mOnItemLongClickListener != null) {
   2972             handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, child,
   2973                     longPressPosition, longPressId);
   2974         }
   2975         if (!handled) {
   2976             mContextMenuInfo = createContextMenuInfo(child, longPressPosition, longPressId);
   2977             handled = super.showContextMenuForChild(AbsListView.this);
   2978         }
   2979         if (handled) {
   2980             performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
   2981         }
   2982         return handled;
   2983     }
   2984 
   2985     @Override
   2986     protected ContextMenuInfo getContextMenuInfo() {
   2987         return mContextMenuInfo;
   2988     }
   2989 
   2990     /** @hide */
   2991     @Override
   2992     public boolean showContextMenu(float x, float y, int metaState) {
   2993         final int position = pointToPosition((int)x, (int)y);
   2994         if (position != INVALID_POSITION) {
   2995             final long id = mAdapter.getItemId(position);
   2996             View child = getChildAt(position - mFirstPosition);
   2997             if (child != null) {
   2998                 mContextMenuInfo = createContextMenuInfo(child, position, id);
   2999                 return super.showContextMenuForChild(AbsListView.this);
   3000             }
   3001         }
   3002         return super.showContextMenu(x, y, metaState);
   3003     }
   3004 
   3005     @Override
   3006     public boolean showContextMenuForChild(View originalView) {
   3007         final int longPressPosition = getPositionForView(originalView);
   3008         if (longPressPosition >= 0) {
   3009             final long longPressId = mAdapter.getItemId(longPressPosition);
   3010             boolean handled = false;
   3011 
   3012             if (mOnItemLongClickListener != null) {
   3013                 handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, originalView,
   3014                         longPressPosition, longPressId);
   3015             }
   3016             if (!handled) {
   3017                 mContextMenuInfo = createContextMenuInfo(
   3018                         getChildAt(longPressPosition - mFirstPosition),
   3019                         longPressPosition, longPressId);
   3020                 handled = super.showContextMenuForChild(originalView);
   3021             }
   3022 
   3023             return handled;
   3024         }
   3025         return false;
   3026     }
   3027 
   3028     @Override
   3029     public boolean onKeyDown(int keyCode, KeyEvent event) {
   3030         return false;
   3031     }
   3032 
   3033     @Override
   3034     public boolean onKeyUp(int keyCode, KeyEvent event) {
   3035         if (KeyEvent.isConfirmKey(keyCode)) {
   3036             if (!isEnabled()) {
   3037                 return true;
   3038             }
   3039             if (isClickable() && isPressed() &&
   3040                     mSelectedPosition >= 0 && mAdapter != null &&
   3041                     mSelectedPosition < mAdapter.getCount()) {
   3042 
   3043                 final View view = getChildAt(mSelectedPosition - mFirstPosition);
   3044                 if (view != null) {
   3045                     performItemClick(view, mSelectedPosition, mSelectedRowId);
   3046                     view.setPressed(false);
   3047                 }
   3048                 setPressed(false);
   3049                 return true;
   3050             }
   3051         }
   3052         return super.onKeyUp(keyCode, event);
   3053     }
   3054 
   3055     @Override
   3056     protected void dispatchSetPressed(boolean pressed) {
   3057         // Don't dispatch setPressed to our children. We call setPressed on ourselves to
   3058         // get the selector in the right state, but we don't want to press each child.
   3059     }
   3060 
   3061     /**
   3062      * Maps a point to a position in the list.
   3063      *
   3064      * @param x X in local coordinate
   3065      * @param y Y in local coordinate
   3066      * @return The position of the item which contains the specified point, or
   3067      *         {@link #INVALID_POSITION} if the point does not intersect an item.
   3068      */
   3069     public int pointToPosition(int x, int y) {
   3070         Rect frame = mTouchFrame;
   3071         if (frame == null) {
   3072             mTouchFrame = new Rect();
   3073             frame = mTouchFrame;
   3074         }
   3075 
   3076         final int count = getChildCount();
   3077         for (int i = count - 1; i >= 0; i--) {
   3078             final View child = getChildAt(i);
   3079             if (child.getVisibility() == View.VISIBLE) {
   3080                 child.getHitRect(frame);
   3081                 if (frame.contains(x, y)) {
   3082                     return mFirstPosition + i;
   3083                 }
   3084             }
   3085         }
   3086         return INVALID_POSITION;
   3087     }
   3088 
   3089 
   3090     /**
   3091      * Maps a point to a the rowId of the item which intersects that point.
   3092      *
   3093      * @param x X in local coordinate
   3094      * @param y Y in local coordinate
   3095      * @return The rowId of the item which contains the specified point, or {@link #INVALID_ROW_ID}
   3096      *         if the point does not intersect an item.
   3097      */
   3098     public long pointToRowId(int x, int y) {
   3099         int position = pointToPosition(x, y);
   3100         if (position >= 0) {
   3101             return mAdapter.getItemId(position);
   3102         }
   3103         return INVALID_ROW_ID;
   3104     }
   3105 
   3106     final class CheckForTap implements Runnable {
   3107         @Override
   3108         public void run() {
   3109             if (mTouchMode == TOUCH_MODE_DOWN) {
   3110                 mTouchMode = TOUCH_MODE_TAP;
   3111                 final View child = getChildAt(mMotionPosition - mFirstPosition);
   3112                 if (child != null && !child.hasFocusable()) {
   3113                     mLayoutMode = LAYOUT_NORMAL;
   3114 
   3115                     if (!mDataChanged) {
   3116                         child.setPressed(true);
   3117                         setPressed(true);
   3118                         layoutChildren();
   3119                         positionSelector(mMotionPosition, child);
   3120                         refreshDrawableState();
   3121 
   3122                         final int longPressTimeout = ViewConfiguration.getLongPressTimeout();
   3123                         final boolean longClickable = isLongClickable();
   3124 
   3125                         if (mSelector != null) {
   3126                             Drawable d = mSelector.getCurrent();
   3127                             if (d != null && d instanceof TransitionDrawable) {
   3128                                 if (longClickable) {
   3129                                     ((TransitionDrawable) d).startTransition(longPressTimeout);
   3130                                 } else {
   3131                                     ((TransitionDrawable) d).resetTransition();
   3132                                 }
   3133                             }
   3134                         }
   3135 
   3136                         if (longClickable) {
   3137                             if (mPendingCheckForLongPress == null) {
   3138                                 mPendingCheckForLongPress = new CheckForLongPress();
   3139                             }
   3140                             mPendingCheckForLongPress.rememberWindowAttachCount();
   3141                             postDelayed(mPendingCheckForLongPress, longPressTimeout);
   3142                         } else {
   3143                             mTouchMode = TOUCH_MODE_DONE_WAITING;
   3144                         }
   3145                     } else {
   3146                         mTouchMode = TOUCH_MODE_DONE_WAITING;
   3147                     }
   3148                 }
   3149             }
   3150         }
   3151     }
   3152 
   3153     private boolean startScrollIfNeeded(int y) {
   3154         // Check if we have moved far enough that it looks more like a
   3155         // scroll than a tap
   3156         final int deltaY = y - mMotionY;
   3157         final int distance = Math.abs(deltaY);
   3158         final boolean overscroll = mScrollY != 0;
   3159         if (overscroll || distance > mTouchSlop) {
   3160             createScrollingCache();
   3161             if (overscroll) {
   3162                 mTouchMode = TOUCH_MODE_OVERSCROLL;
   3163                 mMotionCorrection = 0;
   3164             } else {
   3165                 mTouchMode = TOUCH_MODE_SCROLL;
   3166                 mMotionCorrection = deltaY > 0 ? mTouchSlop : -mTouchSlop;
   3167             }
   3168             removeCallbacks(mPendingCheckForLongPress);
   3169             setPressed(false);
   3170             final View motionView = getChildAt(mMotionPosition - mFirstPosition);
   3171             if (motionView != null) {
   3172                 motionView.setPressed(false);
   3173             }
   3174             reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
   3175             // Time to start stealing events! Once we've stolen them, don't let anyone
   3176             // steal from us
   3177             final ViewParent parent = getParent();
   3178             if (parent != null) {
   3179                 parent.requestDisallowInterceptTouchEvent(true);
   3180             }
   3181             scrollIfNeeded(y);
   3182             return true;
   3183         }
   3184 
   3185         return false;
   3186     }
   3187 
   3188     private void scrollIfNeeded(int y) {
   3189         final int rawDeltaY = y - mMotionY;
   3190         final int deltaY = rawDeltaY - mMotionCorrection;
   3191         int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY;
   3192 
   3193         if (mTouchMode == TOUCH_MODE_SCROLL) {
   3194             if (PROFILE_SCROLLING) {
   3195                 if (!mScrollProfilingStarted) {
   3196                     Debug.startMethodTracing("AbsListViewScroll");
   3197                     mScrollProfilingStarted = true;
   3198                 }
   3199             }
   3200 
   3201             if (mScrollStrictSpan == null) {
   3202                 // If it's non-null, we're already in a scroll.
   3203                 mScrollStrictSpan = StrictMode.enterCriticalSpan("AbsListView-scroll");
   3204             }
   3205 
   3206             if (y != mLastY) {
   3207                 // We may be here after stopping a fling and continuing to scroll.
   3208                 // If so, we haven't disallowed intercepting touch events yet.
   3209                 // Make sure that we do so in case we're in a parent that can intercept.
   3210                 if ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) == 0 &&
   3211                         Math.abs(rawDeltaY) > mTouchSlop) {
   3212                     final ViewParent parent = getParent();
   3213                     if (parent != null) {
   3214                         parent.requestDisallowInterceptTouchEvent(true);
   3215                     }
   3216                 }
   3217 
   3218                 final int motionIndex;
   3219                 if (mMotionPosition >= 0) {
   3220                     motionIndex = mMotionPosition - mFirstPosition;
   3221                 } else {
   3222                     // If we don't have a motion position that we can reliably track,
   3223                     // pick something in the middle to make a best guess at things below.
   3224                     motionIndex = getChildCount() / 2;
   3225                 }
   3226 
   3227                 int motionViewPrevTop = 0;
   3228                 View motionView = this.getChildAt(motionIndex);
   3229                 if (motionView != null) {
   3230                     motionViewPrevTop = motionView.getTop();
   3231                 }
   3232 
   3233                 // No need to do all this work if we're not going to move anyway
   3234                 boolean atEdge = false;
   3235                 if (incrementalDeltaY != 0) {
   3236                     atEdge = trackMotionScroll(deltaY, incrementalDeltaY);
   3237                 }
   3238 
   3239                 // Check to see if we have bumped into the scroll limit
   3240                 motionView = this.getChildAt(motionIndex);
   3241                 if (motionView != null) {
   3242                     // Check if the top of the motion view is where it is
   3243                     // supposed to be
   3244                     final int motionViewRealTop = motionView.getTop();
   3245                     if (atEdge) {
   3246                         // Apply overscroll
   3247 
   3248                         int overscroll = -incrementalDeltaY -
   3249                                 (motionViewRealTop - motionViewPrevTop);
   3250                         overScrollBy(0, overscroll, 0, mScrollY, 0, 0,
   3251                                 0, mOverscrollDistance, true);
   3252                         if (Math.abs(mOverscrollDistance) == Math.abs(mScrollY)) {
   3253                             // Don't allow overfling if we're at the edge.
   3254                             if (mVelocityTracker != null) {
   3255                                 mVelocityTracker.clear();
   3256                             }
   3257                         }
   3258 
   3259                         final int overscrollMode = getOverScrollMode();
   3260                         if (overscrollMode == OVER_SCROLL_ALWAYS ||
   3261                                 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
   3262                                         !contentFits())) {
   3263                             mDirection = 0; // Reset when entering overscroll.
   3264                             mTouchMode = TOUCH_MODE_OVERSCROLL;
   3265                             if (rawDeltaY > 0) {
   3266                                 mEdgeGlowTop.onPull((float) overscroll / getHeight());
   3267                                 if (!mEdgeGlowBottom.isFinished()) {
   3268                                     mEdgeGlowBottom.onRelease();
   3269                                 }
   3270                                 invalidate(mEdgeGlowTop.getBounds(false));
   3271                             } else if (rawDeltaY < 0) {
   3272                                 mEdgeGlowBottom.onPull((float) overscroll / getHeight());
   3273                                 if (!mEdgeGlowTop.isFinished()) {
   3274                                     mEdgeGlowTop.onRelease();
   3275                                 }
   3276                                 invalidate(mEdgeGlowBottom.getBounds(true));
   3277                             }
   3278                         }
   3279                     }
   3280                     mMotionY = y;
   3281                 }
   3282                 mLastY = y;
   3283             }
   3284         } else if (mTouchMode == TOUCH_MODE_OVERSCROLL) {
   3285             if (y != mLastY) {
   3286                 final int oldScroll = mScrollY;
   3287                 final int newScroll = oldScroll - incrementalDeltaY;
   3288                 int newDirection = y > mLastY ? 1 : -1;
   3289 
   3290                 if (mDirection == 0) {
   3291                     mDirection = newDirection;
   3292                 }
   3293 
   3294                 int overScrollDistance = -incrementalDeltaY;
   3295                 if ((newScroll < 0 && oldScroll >= 0) || (newScroll > 0 && oldScroll <= 0)) {
   3296                     overScrollDistance = -oldScroll;
   3297                     incrementalDeltaY += overScrollDistance;
   3298                 } else {
   3299                     incrementalDeltaY = 0;
   3300                 }
   3301 
   3302                 if (overScrollDistance != 0) {
   3303                     overScrollBy(0, overScrollDistance, 0, mScrollY, 0, 0,
   3304                             0, mOverscrollDistance, true);
   3305                     final int overscrollMode = getOverScrollMode();
   3306                     if (overscrollMode == OVER_SCROLL_ALWAYS ||
   3307                             (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
   3308                                     !contentFits())) {
   3309                         if (rawDeltaY > 0) {
   3310                             mEdgeGlowTop.onPull((float) overScrollDistance / getHeight());
   3311                             if (!mEdgeGlowBottom.isFinished()) {
   3312                                 mEdgeGlowBottom.onRelease();
   3313                             }
   3314                             invalidate(mEdgeGlowTop.getBounds(false));
   3315                         } else if (rawDeltaY < 0) {
   3316                             mEdgeGlowBottom.onPull((float) overScrollDistance / getHeight());
   3317                             if (!mEdgeGlowTop.isFinished()) {
   3318                                 mEdgeGlowTop.onRelease();
   3319                             }
   3320                             invalidate(mEdgeGlowBottom.getBounds(true));
   3321                         }
   3322                     }
   3323                 }
   3324 
   3325                 if (incrementalDeltaY != 0) {
   3326                     // Coming back to 'real' list scrolling
   3327                     if (mScrollY != 0) {
   3328                         mScrollY = 0;
   3329                         invalidateParentIfNeeded();
   3330                     }
   3331 
   3332                     trackMotionScroll(incrementalDeltaY, incrementalDeltaY);
   3333 
   3334                     mTouchMode = TOUCH_MODE_SCROLL;
   3335 
   3336                     // We did not scroll the full amount. Treat this essentially like the
   3337                     // start of a new touch scroll
   3338                     final int motionPosition = findClosestMotionRow(y);
   3339 
   3340                     mMotionCorrection = 0;
   3341                     View motionView = getChildAt(motionPosition - mFirstPosition);
   3342                     mMotionViewOriginalTop = motionView != null ? motionView.getTop() : 0;
   3343                     mMotionY = y;
   3344                     mMotionPosition = motionPosition;
   3345                 }
   3346                 mLastY = y;
   3347                 mDirection = newDirection;
   3348             }
   3349         }
   3350     }
   3351 
   3352     @Override
   3353     public void onTouchModeChanged(boolean isInTouchMode) {
   3354         if (isInTouchMode) {
   3355             // Get rid of the selection when we enter touch mode
   3356             hideSelector();
   3357             // Layout, but only if we already have done so previously.
   3358             // (Otherwise may clobber a LAYOUT_SYNC layout that was requested to restore
   3359             // state.)
   3360             if (getHeight() > 0 && getChildCount() > 0) {
   3361                 // We do not lose focus initiating a touch (since AbsListView is focusable in
   3362                 // touch mode). Force an initial layout to get rid of the selection.
   3363                 layoutChildren();
   3364             }
   3365             updateSelectorState();
   3366         } else {
   3367             int touchMode = mTouchMode;
   3368             if (touchMode == TOUCH_MODE_OVERSCROLL || touchMode == TOUCH_MODE_OVERFLING) {
   3369                 if (mFlingRunnable != null) {
   3370                     mFlingRunnable.endFling();
   3371                 }
   3372                 if (mPositionScroller != null) {
   3373                     mPositionScroller.stop();
   3374                 }
   3375 
   3376                 if (mScrollY != 0) {
   3377                     mScrollY = 0;
   3378                     invalidateParentCaches();
   3379                     finishGlows();
   3380                     invalidate();
   3381                 }
   3382             }
   3383         }
   3384     }
   3385 
   3386     @Override
   3387     public boolean onTouchEvent(MotionEvent ev) {
   3388         if (!isEnabled()) {
   3389             // A disabled view that is clickable still consumes the touch
   3390             // events, it just doesn't respond to them.
   3391             return isClickable() || isLongClickable();
   3392         }
   3393 
   3394         if (mPositionScroller != null) {
   3395             mPositionScroller.stop();
   3396         }
   3397 
   3398         if (!isAttachedToWindow()) {
   3399             // Something isn't right.
   3400             // Since we rely on being attached to get data set change notifications,
   3401             // don't risk doing anything where we might try to resync and find things
   3402             // in a bogus state.
   3403             return false;
   3404         }
   3405 
   3406         if (mFastScroller != null) {
   3407             boolean intercepted = mFastScroller.onTouchEvent(ev);
   3408             if (intercepted) {
   3409                 return true;
   3410             }
   3411         }
   3412 
   3413         initVelocityTrackerIfNotExists();
   3414         mVelocityTracker.addMovement(ev);
   3415 
   3416         final int actionMasked = ev.getActionMasked();
   3417         switch (actionMasked) {
   3418             case MotionEvent.ACTION_DOWN: {
   3419                 onTouchDown(ev);
   3420                 break;
   3421             }
   3422 
   3423             case MotionEvent.ACTION_MOVE: {
   3424                 onTouchMove(ev);
   3425                 break;
   3426             }
   3427 
   3428             case MotionEvent.ACTION_UP: {
   3429                 onTouchUp(ev);
   3430                 break;
   3431             }
   3432 
   3433             case MotionEvent.ACTION_CANCEL: {
   3434                 onTouchCancel();
   3435                 break;
   3436             }
   3437 
   3438             case MotionEvent.ACTION_POINTER_UP: {
   3439                 onSecondaryPointerUp(ev);
   3440                 final int x = mMotionX;
   3441                 final int y = mMotionY;
   3442                 final int motionPosition = pointToPosition(x, y);
   3443                 if (motionPosition >= 0) {
   3444                     // Remember where the motion event started
   3445                     final View child = getChildAt(motionPosition - mFirstPosition);
   3446                     mMotionViewOriginalTop = child.getTop();
   3447                     mMotionPosition = motionPosition;
   3448                 }
   3449                 mLastY = y;
   3450                 break;
   3451             }
   3452 
   3453             case MotionEvent.ACTION_POINTER_DOWN: {
   3454                 // New pointers take over dragging duties
   3455                 final int index = ev.getActionIndex();
   3456                 final int id = ev.getPointerId(index);
   3457                 final int x = (int) ev.getX(index);
   3458                 final int y = (int) ev.getY(index);
   3459                 mMotionCorrection = 0;
   3460                 mActivePointerId = id;
   3461                 mMotionX = x;
   3462                 mMotionY = y;
   3463                 final int motionPosition = pointToPosition(x, y);
   3464                 if (motionPosition >= 0) {
   3465                     // Remember where the motion event started
   3466                     final View child = getChildAt(motionPosition - mFirstPosition);
   3467                     mMotionViewOriginalTop = child.getTop();
   3468                     mMotionPosition = motionPosition;
   3469                 }
   3470                 mLastY = y;
   3471                 break;
   3472             }
   3473         }
   3474 
   3475         return true;
   3476     }
   3477 
   3478     private void onTouchDown(MotionEvent ev) {
   3479         mActivePointerId = ev.getPointerId(0);
   3480 
   3481         if (mTouchMode == TOUCH_MODE_OVERFLING) {
   3482             // Stopped the fling. It is a scroll.
   3483             mFlingRunnable.endFling();
   3484             if (mPositionScroller != null) {
   3485                 mPositionScroller.stop();
   3486             }
   3487             mTouchMode = TOUCH_MODE_OVERSCROLL;
   3488             mMotionX = (int) ev.getX();
   3489             mMotionY = (int) ev.getY();
   3490             mLastY = mMotionY;
   3491             mMotionCorrection = 0;
   3492             mDirection = 0;
   3493         } else {
   3494             final int x = (int) ev.getX();
   3495             final int y = (int) ev.getY();
   3496             int motionPosition = pointToPosition(x, y);
   3497 
   3498             if (!mDataChanged) {
   3499                 if (mTouchMode == TOUCH_MODE_FLING) {
   3500                     // Stopped a fling. It is a scroll.
   3501                     createScrollingCache();
   3502                     mTouchMode = TOUCH_MODE_SCROLL;
   3503                     mMotionCorrection = 0;
   3504                     motionPosition = findMotionRow(y);
   3505                     mFlingRunnable.flywheelTouch();
   3506                 } else if ((motionPosition >= 0) && getAdapter().isEnabled(motionPosition)) {
   3507                     // User clicked on an actual view (and was not stopping a
   3508                     // fling). It might be a click or a scroll. Assume it is a
   3509                     // click until proven otherwise.
   3510                     mTouchMode = TOUCH_MODE_DOWN;
   3511 
   3512                     // FIXME Debounce
   3513                     if (mPendingCheckForTap == null) {
   3514                         mPendingCheckForTap = new CheckForTap();
   3515                     }
   3516 
   3517                     postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
   3518                 }
   3519             }
   3520 
   3521             if (motionPosition >= 0) {
   3522                 // Remember where the motion event started
   3523                 final View v = getChildAt(motionPosition - mFirstPosition);
   3524                 mMotionViewOriginalTop = v.getTop();
   3525             }
   3526 
   3527             mMotionX = x;
   3528             mMotionY = y;
   3529             mMotionPosition = motionPosition;
   3530             mLastY = Integer.MIN_VALUE;
   3531         }
   3532 
   3533         if (mTouchMode == TOUCH_MODE_DOWN && mMotionPosition != INVALID_POSITION
   3534                 && performButtonActionOnTouchDown(ev)) {
   3535             removeCallbacks(mPendingCheckForTap);
   3536         }
   3537     }
   3538 
   3539     private void onTouchMove(MotionEvent ev) {
   3540         int pointerIndex = ev.findPointerIndex(mActivePointerId);
   3541         if (pointerIndex == -1) {
   3542             pointerIndex = 0;
   3543             mActivePointerId = ev.getPointerId(pointerIndex);
   3544         }
   3545 
   3546         if (mDataChanged) {
   3547             // Re-sync everything if data has been changed
   3548             // since the scroll operation can query the adapter.
   3549             layoutChildren();
   3550         }
   3551 
   3552         final int y = (int) ev.getY(pointerIndex);
   3553 
   3554         switch (mTouchMode) {
   3555             case TOUCH_MODE_DOWN:
   3556             case TOUCH_MODE_TAP:
   3557             case TOUCH_MODE_DONE_WAITING:
   3558                 // Check if we have moved far enough that it looks more like a
   3559                 // scroll than a tap. If so, we'll enter scrolling mode.
   3560                 if (startScrollIfNeeded(y)) {
   3561                     break;
   3562                 }
   3563                 // Otherwise, check containment within list bounds. If we're
   3564                 // outside bounds, cancel any active presses.
   3565                 final float x = ev.getX(pointerIndex);
   3566                 if (!pointInView(x, y, mTouchSlop)) {
   3567                     setPressed(false);
   3568                     final View motionView = getChildAt(mMotionPosition - mFirstPosition);
   3569                     if (motionView != null) {
   3570                         motionView.setPressed(false);
   3571                     }
   3572                     removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
   3573                             mPendingCheckForTap : mPendingCheckForLongPress);
   3574                     mTouchMode = TOUCH_MODE_DONE_WAITING;
   3575                     updateSelectorState();
   3576                 }
   3577                 break;
   3578             case TOUCH_MODE_SCROLL:
   3579             case TOUCH_MODE_OVERSCROLL:
   3580                 scrollIfNeeded(y);
   3581                 break;
   3582         }
   3583     }
   3584 
   3585     private void onTouchUp(MotionEvent ev) {
   3586         switch (mTouchMode) {
   3587         case TOUCH_MODE_DOWN:
   3588         case TOUCH_MODE_TAP:
   3589         case TOUCH_MODE_DONE_WAITING:
   3590             final int motionPosition = mMotionPosition;
   3591             final View child = getChildAt(motionPosition - mFirstPosition);
   3592             if (child != null) {
   3593                 if (mTouchMode != TOUCH_MODE_DOWN) {
   3594                     child.setPressed(false);
   3595                 }
   3596 
   3597                 final float x = ev.getX();
   3598                 final boolean inList = x > mListPadding.left && x < getWidth() - mListPadding.right;
   3599                 if (inList && !child.hasFocusable()) {
   3600                     if (mPerformClick == null) {
   3601                         mPerformClick = new PerformClick();
   3602                     }
   3603 
   3604                     final AbsListView.PerformClick performClick = mPerformClick;
   3605                     performClick.mClickMotionPosition = motionPosition;
   3606                     performClick.rememberWindowAttachCount();
   3607 
   3608                     mResurrectToPosition = motionPosition;
   3609 
   3610                     if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) {
   3611                         removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
   3612                                 mPendingCheckForTap : mPendingCheckForLongPress);
   3613                         mLayoutMode = LAYOUT_NORMAL;
   3614                         if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
   3615                             mTouchMode = TOUCH_MODE_TAP;
   3616                             setSelectedPositionInt(mMotionPosition);
   3617                             layoutChildren();
   3618                             child.setPressed(true);
   3619                             positionSelector(mMotionPosition, child);
   3620                             setPressed(true);
   3621                             if (mSelector != null) {
   3622                                 Drawable d = mSelector.getCurrent();
   3623                                 if (d != null && d instanceof TransitionDrawable) {
   3624                                     ((TransitionDrawable) d).resetTransition();
   3625                                 }
   3626                             }
   3627                             if (mTouchModeReset != null) {
   3628                                 removeCallbacks(mTouchModeReset);
   3629                             }
   3630                             mTouchModeReset = new Runnable() {
   3631                                 @Override
   3632                                 public void run() {
   3633                                     mTouchModeReset = null;
   3634                                     mTouchMode = TOUCH_MODE_REST;
   3635                                     child.setPressed(false);
   3636                                     setPressed(false);
   3637                                     if (!mDataChanged && isAttachedToWindow()) {
   3638                                         performClick.run();
   3639                                     }
   3640                                 }
   3641                             };
   3642                             postDelayed(mTouchModeReset,
   3643                                     ViewConfiguration.getPressedStateDuration());
   3644                         } else {
   3645                             mTouchMode = TOUCH_MODE_REST;
   3646                             updateSelectorState();
   3647                         }
   3648                         return;
   3649                     } else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
   3650                         performClick.run();
   3651                     }
   3652                 }
   3653             }
   3654             mTouchMode = TOUCH_MODE_REST;
   3655             updateSelectorState();
   3656             break;
   3657         case TOUCH_MODE_SCROLL:
   3658             final int childCount = getChildCount();
   3659             if (childCount > 0) {
   3660                 final int firstChildTop = getChildAt(0).getTop();
   3661                 final int lastChildBottom = getChildAt(childCount - 1).getBottom();
   3662                 final int contentTop = mListPadding.top;
   3663                 final int contentBottom = getHeight() - mListPadding.bottom;
   3664                 if (mFirstPosition == 0 && firstChildTop >= contentTop &&
   3665                         mFirstPosition + childCount < mItemCount &&
   3666                         lastChildBottom <= getHeight() - contentBottom) {
   3667                     mTouchMode = TOUCH_MODE_REST;
   3668                     reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
   3669                 } else {
   3670                     final VelocityTracker velocityTracker = mVelocityTracker;
   3671                     velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
   3672 
   3673                     final int initialVelocity = (int)
   3674                             (velocityTracker.getYVelocity(mActivePointerId) * mVelocityScale);
   3675                     // Fling if we have enough velocity and we aren't at a boundary.
   3676                     // Since we can potentially overfling more than we can overscroll, don't
   3677                     // allow the weird behavior where you can scroll to a boundary then
   3678                     // fling further.
   3679                     if (Math.abs(initialVelocity) > mMinimumVelocity &&
   3680                             !((mFirstPosition == 0 &&
   3681                                     firstChildTop == contentTop - mOverscrollDistance) ||
   3682                               (mFirstPosition + childCount == mItemCount &&
   3683                                     lastChildBottom == contentBottom + mOverscrollDistance))) {
   3684                         if (mFlingRunnable == null) {
   3685                             mFlingRunnable = new FlingRunnable();
   3686                         }
   3687                         reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
   3688 
   3689                         mFlingRunnable.start(-initialVelocity);
   3690                     } else {
   3691                         mTouchMode = TOUCH_MODE_REST;
   3692                         reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
   3693                         if (mFlingRunnable != null) {
   3694                             mFlingRunnable.endFling();
   3695                         }
   3696                         if (mPositionScroller != null) {
   3697                             mPositionScroller.stop();
   3698                         }
   3699                     }
   3700                 }
   3701             } else {
   3702                 mTouchMode = TOUCH_MODE_REST;
   3703                 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
   3704             }
   3705             break;
   3706 
   3707         case TOUCH_MODE_OVERSCROLL:
   3708             if (mFlingRunnable == null) {
   3709                 mFlingRunnable = new FlingRunnable();
   3710             }
   3711             final VelocityTracker velocityTracker = mVelocityTracker;
   3712             velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
   3713             final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
   3714 
   3715             reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
   3716             if (Math.abs(initialVelocity) > mMinimumVelocity) {
   3717                 mFlingRunnable.startOverfling(-initialVelocity);
   3718             } else {
   3719                 mFlingRunnable.startSpringback();
   3720             }
   3721 
   3722             break;
   3723         }
   3724 
   3725         setPressed(false);
   3726 
   3727         if (mEdgeGlowTop != null) {
   3728             mEdgeGlowTop.onRelease();
   3729             mEdgeGlowBottom.onRelease();
   3730         }
   3731 
   3732         // Need to redraw since we probably aren't drawing the selector anymore
   3733         invalidate();
   3734         removeCallbacks(mPendingCheckForLongPress);
   3735         recycleVelocityTracker();
   3736 
   3737         mActivePointerId = INVALID_POINTER;
   3738 
   3739         if (PROFILE_SCROLLING) {
   3740             if (mScrollProfilingStarted) {
   3741                 Debug.stopMethodTracing();
   3742                 mScrollProfilingStarted = false;
   3743             }
   3744         }
   3745 
   3746         if (mScrollStrictSpan != null) {
   3747             mScrollStrictSpan.finish();
   3748             mScrollStrictSpan = null;
   3749         }
   3750     }
   3751 
   3752     private void onTouchCancel() {
   3753         switch (mTouchMode) {
   3754         case TOUCH_MODE_OVERSCROLL:
   3755             if (mFlingRunnable == null) {
   3756                 mFlingRunnable = new FlingRunnable();
   3757             }
   3758             mFlingRunnable.startSpringback();
   3759             break;
   3760 
   3761         case TOUCH_MODE_OVERFLING:
   3762             // Do nothing - let it play out.
   3763             break;
   3764 
   3765         default:
   3766             mTouchMode = TOUCH_MODE_REST;
   3767             setPressed(false);
   3768             final View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
   3769             if (motionView != null) {
   3770                 motionView.setPressed(false);
   3771             }
   3772             clearScrollingCache();
   3773             removeCallbacks(mPendingCheckForLongPress);
   3774             recycleVelocityTracker();
   3775         }
   3776 
   3777         if (mEdgeGlowTop != null) {
   3778             mEdgeGlowTop.onRelease();
   3779             mEdgeGlowBottom.onRelease();
   3780         }
   3781         mActivePointerId = INVALID_POINTER;
   3782     }
   3783 
   3784     @Override
   3785     protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
   3786         if (mScrollY != scrollY) {
   3787             onScrollChanged(mScrollX, scrollY, mScrollX, mScrollY);
   3788             mScrollY = scrollY;
   3789             invalidateParentIfNeeded();
   3790 
   3791             awakenScrollBars();
   3792         }
   3793     }
   3794 
   3795     @Override
   3796     public boolean onGenericMotionEvent(MotionEvent event) {
   3797         if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
   3798             switch (event.getAction()) {
   3799                 case MotionEvent.ACTION_SCROLL: {
   3800                     if (mTouchMode == TOUCH_MODE_REST) {
   3801                         final float vscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
   3802                         if (vscroll != 0) {
   3803                             final int delta = (int) (vscroll * getVerticalScrollFactor());
   3804                             if (!trackMotionScroll(delta, delta)) {
   3805                                 return true;
   3806                             }
   3807                         }
   3808                     }
   3809                 }
   3810             }
   3811         }
   3812         return super.onGenericMotionEvent(event);
   3813     }
   3814 
   3815     @Override
   3816     public void draw(Canvas canvas) {
   3817         super.draw(canvas);
   3818         if (mEdgeGlowTop != null) {
   3819             final int scrollY = mScrollY;
   3820             if (!mEdgeGlowTop.isFinished()) {
   3821                 final int restoreCount = canvas.save();
   3822                 final int leftPadding = mListPadding.left + mGlowPaddingLeft;
   3823                 final int rightPadding = mListPadding.right + mGlowPaddingRight;
   3824                 final int width = getWidth() - leftPadding - rightPadding;
   3825 
   3826                 int edgeY = Math.min(0, scrollY + mFirstPositionDistanceGuess);
   3827                 canvas.translate(leftPadding, edgeY);
   3828                 mEdgeGlowTop.setSize(width, getHeight());
   3829                 if (mEdgeGlowTop.draw(canvas)) {
   3830                     mEdgeGlowTop.setPosition(leftPadding, edgeY);
   3831                     invalidate(mEdgeGlowTop.getBounds(false));
   3832                 }
   3833                 canvas.restoreToCount(restoreCount);
   3834             }
   3835             if (!mEdgeGlowBottom.isFinished()) {
   3836                 final int restoreCount = canvas.save();
   3837                 final int leftPadding = mListPadding.left + mGlowPaddingLeft;
   3838                 final int rightPadding = mListPadding.right + mGlowPaddingRight;
   3839                 final int width = getWidth() - leftPadding - rightPadding;
   3840                 final int height = getHeight();
   3841 
   3842                 int edgeX = -width + leftPadding;
   3843                 int edgeY = Math.max(height, scrollY + mLastPositionDistanceGuess);
   3844                 canvas.translate(edgeX, edgeY);
   3845                 canvas.rotate(180, width, 0);
   3846                 mEdgeGlowBottom.setSize(width, height);
   3847                 if (mEdgeGlowBottom.draw(canvas)) {
   3848                     // Account for the rotation
   3849                     mEdgeGlowBottom.setPosition(edgeX + width, edgeY);
   3850                     invalidate(mEdgeGlowBottom.getBounds(true));
   3851                 }
   3852                 canvas.restoreToCount(restoreCount);
   3853             }
   3854         }
   3855     }
   3856 
   3857     /**
   3858      * @hide
   3859      */
   3860     public void setOverScrollEffectPadding(int leftPadding, int rightPadding) {
   3861         mGlowPaddingLeft = leftPadding;
   3862         mGlowPaddingRight = rightPadding;
   3863     }
   3864 
   3865     private void initOrResetVelocityTracker() {
   3866         if (mVelocityTracker == null) {
   3867             mVelocityTracker = VelocityTracker.obtain();
   3868         } else {
   3869             mVelocityTracker.clear();
   3870         }
   3871     }
   3872 
   3873     private void initVelocityTrackerIfNotExists() {
   3874         if (mVelocityTracker == null) {
   3875             mVelocityTracker = VelocityTracker.obtain();
   3876         }
   3877     }
   3878 
   3879     private void recycleVelocityTracker() {
   3880         if (mVelocityTracker != null) {
   3881             mVelocityTracker.recycle();
   3882             mVelocityTracker = null;
   3883         }
   3884     }
   3885 
   3886     @Override
   3887     public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
   3888         if (disallowIntercept) {
   3889             recycleVelocityTracker();
   3890         }
   3891         super.requestDisallowInterceptTouchEvent(disallowIntercept);
   3892     }
   3893 
   3894     @Override
   3895     public boolean onInterceptHoverEvent(MotionEvent event) {
   3896         if (mFastScroller != null && mFastScroller.onInterceptHoverEvent(event)) {
   3897             return true;
   3898         }
   3899 
   3900         return super.onInterceptHoverEvent(event);
   3901     }
   3902 
   3903     @Override
   3904     public boolean onInterceptTouchEvent(MotionEvent ev) {
   3905         int action = ev.getAction();
   3906         View v;
   3907 
   3908         if (mPositionScroller != null) {
   3909             mPositionScroller.stop();
   3910         }
   3911 
   3912         if (!isAttachedToWindow()) {
   3913             // Something isn't right.
   3914             // Since we rely on being attached to get data set change notifications,
   3915             // don't risk doing anything where we might try to resync and find things
   3916             // in a bogus state.
   3917             return false;
   3918         }
   3919 
   3920         if (mFastScroller != null && mFastScroller.onInterceptTouchEvent(ev)) {
   3921             return true;
   3922         }
   3923 
   3924         switch (action & MotionEvent.ACTION_MASK) {
   3925         case MotionEvent.ACTION_DOWN: {
   3926             int touchMode = mTouchMode;
   3927             if (touchMode == TOUCH_MODE_OVERFLING || touchMode == TOUCH_MODE_OVERSCROLL) {
   3928                 mMotionCorrection = 0;
   3929                 return true;
   3930             }
   3931 
   3932             final int x = (int) ev.getX();
   3933             final int y = (int) ev.getY();
   3934             mActivePointerId = ev.getPointerId(0);
   3935 
   3936             int motionPosition = findMotionRow(y);
   3937             if (touchMode != TOUCH_MODE_FLING && motionPosition >= 0) {
   3938                 // User clicked on an actual view (and was not stopping a fling).
   3939                 // Remember where the motion event started
   3940                 v = getChildAt(motionPosition - mFirstPosition);
   3941                 mMotionViewOriginalTop = v.getTop();
   3942                 mMotionX = x;
   3943                 mMotionY = y;
   3944                 mMotionPosition = motionPosition;
   3945                 mTouchMode = TOUCH_MODE_DOWN;
   3946                 clearScrollingCache();
   3947             }
   3948             mLastY = Integer.MIN_VALUE;
   3949             initOrResetVelocityTracker();
   3950             mVelocityTracker.addMovement(ev);
   3951             if (touchMode == TOUCH_MODE_FLING) {
   3952                 return true;
   3953             }
   3954             break;
   3955         }
   3956 
   3957         case MotionEvent.ACTION_MOVE: {
   3958             switch (mTouchMode) {
   3959             case TOUCH_MODE_DOWN:
   3960                 int pointerIndex = ev.findPointerIndex(mActivePointerId);
   3961                 if (pointerIndex == -1) {
   3962                     pointerIndex = 0;
   3963                     mActivePointerId = ev.getPointerId(pointerIndex);
   3964                 }
   3965                 final int y = (int) ev.getY(pointerIndex);
   3966                 initVelocityTrackerIfNotExists();
   3967                 mVelocityTracker.addMovement(ev);
   3968                 if (startScrollIfNeeded(y)) {
   3969                     return true;
   3970                 }
   3971                 break;
   3972             }
   3973             break;
   3974         }
   3975 
   3976         case MotionEvent.ACTION_CANCEL:
   3977         case MotionEvent.ACTION_UP: {
   3978             mTouchMode = TOUCH_MODE_REST;
   3979             mActivePointerId = INVALID_POINTER;
   3980             recycleVelocityTracker();
   3981             reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
   3982             break;
   3983         }
   3984 
   3985         case MotionEvent.ACTION_POINTER_UP: {
   3986             onSecondaryPointerUp(ev);
   3987             break;
   3988         }
   3989         }
   3990 
   3991         return false;
   3992     }
   3993 
   3994     private void onSecondaryPointerUp(MotionEvent ev) {
   3995         final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
   3996                 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
   3997         final int pointerId = ev.getPointerId(pointerIndex);
   3998         if (pointerId == mActivePointerId) {
   3999             // This was our active pointer going up. Choose a new
   4000             // active pointer and adjust accordingly.
   4001             // TODO: Make this decision more intelligent.
   4002             final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
   4003             mMotionX = (int) ev.getX(newPointerIndex);
   4004             mMotionY = (int) ev.getY(newPointerIndex);
   4005             mMotionCorrection = 0;
   4006             mActivePointerId = ev.getPointerId(newPointerIndex);
   4007         }
   4008     }
   4009 
   4010     /**
   4011      * {@inheritDoc}
   4012      */
   4013     @Override
   4014     public void addTouchables(ArrayList<View> views) {
   4015         final int count = getChildCount();
   4016         final int firstPosition = mFirstPosition;
   4017         final ListAdapter adapter = mAdapter;
   4018 
   4019         if (adapter == null) {
   4020             return;
   4021         }
   4022 
   4023         for (int i = 0; i < count; i++) {
   4024             final View child = getChildAt(i);
   4025             if (adapter.isEnabled(firstPosition + i)) {
   4026                 views.add(child);
   4027             }
   4028             child.addTouchables(views);
   4029         }
   4030     }
   4031 
   4032     /**
   4033      * Fires an "on scroll state changed" event to the registered
   4034      * {@link android.widget.AbsListView.OnScrollListener}, if any. The state change
   4035      * is fired only if the specified state is different from the previously known state.
   4036      *
   4037      * @param newState The new scroll state.
   4038      */
   4039     void reportScrollStateChange(int newState) {
   4040         if (newState != mLastScrollState) {
   4041             if (mOnScrollListener != null) {
   4042                 mLastScrollState = newState;
   4043                 mOnScrollListener.onScrollStateChanged(this, newState);
   4044             }
   4045         }
   4046     }
   4047 
   4048     /**
   4049      * Responsible for fling behavior. Use {@link #start(int)} to
   4050      * initiate a fling. Each frame of the fling is handled in {@link #run()}.
   4051      * A FlingRunnable will keep re-posting itself until the fling is done.
   4052      *
   4053      */
   4054     private class FlingRunnable implements Runnable {
   4055         /**
   4056          * Tracks the decay of a fling scroll
   4057          */
   4058         private final OverScroller mScroller;
   4059 
   4060         /**
   4061          * Y value reported by mScroller on the previous fling
   4062          */
   4063         private int mLastFlingY;
   4064 
   4065         private final Runnable mCheckFlywheel = new Runnable() {
   4066             @Override
   4067             public void run() {
   4068                 final int activeId = mActivePointerId;
   4069                 final VelocityTracker vt = mVelocityTracker;
   4070                 final OverScroller scroller = mScroller;
   4071                 if (vt == null || activeId == INVALID_POINTER) {
   4072                     return;
   4073                 }
   4074 
   4075                 vt.computeCurrentVelocity(1000, mMaximumVelocity);
   4076                 final float yvel = -vt.getYVelocity(activeId);
   4077 
   4078                 if (Math.abs(yvel) >= mMinimumVelocity
   4079                         && scroller.isScrollingInDirection(0, yvel)) {
   4080                     // Keep the fling alive a little longer
   4081                     postDelayed(this, FLYWHEEL_TIMEOUT);
   4082                 } else {
   4083                     endFling();
   4084                     mTouchMode = TOUCH_MODE_SCROLL;
   4085                     reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
   4086                 }
   4087             }
   4088         };
   4089 
   4090         private static final int FLYWHEEL_TIMEOUT = 40; // milliseconds
   4091 
   4092         FlingRunnable() {
   4093             mScroller = new OverScroller(getContext());
   4094         }
   4095 
   4096         void start(int initialVelocity) {
   4097             int initialY = initialVelocity < 0 ? Integer.MAX_VALUE : 0;
   4098             mLastFlingY = initialY;
   4099             mScroller.setInterpolator(null);
   4100             mScroller.fling(0, initialY, 0, initialVelocity,
   4101                     0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE);
   4102             mTouchMode = TOUCH_MODE_FLING;
   4103             postOnAnimation(this);
   4104 
   4105             if (PROFILE_FLINGING) {
   4106                 if (!mFlingProfilingStarted) {
   4107                     Debug.startMethodTracing("AbsListViewFling");
   4108                     mFlingProfilingStarted = true;
   4109                 }
   4110             }
   4111 
   4112             if (mFlingStrictSpan == null) {
   4113                 mFlingStrictSpan = StrictMode.enterCriticalSpan("AbsListView-fling");
   4114             }
   4115         }
   4116 
   4117         void startSpringback() {
   4118             if (mScroller.springBack(0, mScrollY, 0, 0, 0, 0)) {
   4119                 mTouchMode = TOUCH_MODE_OVERFLING;
   4120                 invalidate();
   4121                 postOnAnimation(this);
   4122             } else {
   4123                 mTouchMode = TOUCH_MODE_REST;
   4124                 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
   4125             }
   4126         }
   4127 
   4128         void startOverfling(int initialVelocity) {
   4129             mScroller.setInterpolator(null);
   4130             mScroller.fling(0, mScrollY, 0, initialVelocity, 0, 0,
   4131                     Integer.MIN_VALUE, Integer.MAX_VALUE, 0, getHeight());
   4132             mTouchMode = TOUCH_MODE_OVERFLING;
   4133             invalidate();
   4134             postOnAnimation(this);
   4135         }
   4136 
   4137         void edgeReached(int delta) {
   4138             mScroller.notifyVerticalEdgeReached(mScrollY, 0, mOverflingDistance);
   4139             final int overscrollMode = getOverScrollMode();
   4140             if (overscrollMode == OVER_SCROLL_ALWAYS ||
   4141                     (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && !contentFits())) {
   4142                 mTouchMode = TOUCH_MODE_OVERFLING;
   4143                 final int vel = (int) mScroller.getCurrVelocity();
   4144                 if (delta > 0) {
   4145                     mEdgeGlowTop.onAbsorb(vel);
   4146                 } else {
   4147                     mEdgeGlowBottom.onAbsorb(vel);
   4148                 }
   4149             } else {
   4150                 mTouchMode = TOUCH_MODE_REST;
   4151                 if (mPositionScroller != null) {
   4152                     mPositionScroller.stop();
   4153                 }
   4154             }
   4155             invalidate();
   4156             postOnAnimation(this);
   4157         }
   4158 
   4159         void startScroll(int distance, int duration, boolean linear) {
   4160             int initialY = distance < 0 ? Integer.MAX_VALUE : 0;
   4161             mLastFlingY = initialY;
   4162             mScroller.setInterpolator(linear ? sLinearInterpolator : null);
   4163             mScroller.startScroll(0, initialY, 0, distance, duration);
   4164             mTouchMode = TOUCH_MODE_FLING;
   4165             postOnAnimation(this);
   4166         }
   4167 
   4168         void endFling() {
   4169             mTouchMode = TOUCH_MODE_REST;
   4170 
   4171             removeCallbacks(this);
   4172             removeCallbacks(mCheckFlywheel);
   4173 
   4174             reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
   4175             clearScrollingCache();
   4176             mScroller.abortAnimation();
   4177 
   4178             if (mFlingStrictSpan != null) {
   4179                 mFlingStrictSpan.finish();
   4180                 mFlingStrictSpan = null;
   4181             }
   4182         }
   4183 
   4184         void flywheelTouch() {
   4185             postDelayed(mCheckFlywheel, FLYWHEEL_TIMEOUT);
   4186         }
   4187 
   4188         @Override
   4189         public void run() {
   4190             switch (mTouchMode) {
   4191             default:
   4192                 endFling();
   4193                 return;
   4194 
   4195             case TOUCH_MODE_SCROLL:
   4196                 if (mScroller.isFinished()) {
   4197                     return;
   4198                 }
   4199                 // Fall through
   4200             case TOUCH_MODE_FLING: {
   4201                 if (mDataChanged) {
   4202                     layoutChildren();
   4203                 }
   4204 
   4205                 if (mItemCount == 0 || getChildCount() == 0) {
   4206                     endFling();
   4207                     return;
   4208                 }
   4209 
   4210                 final OverScroller scroller = mScroller;
   4211                 boolean more = scroller.computeScrollOffset();
   4212                 final int y = scroller.getCurrY();
   4213 
   4214                 // Flip sign to convert finger direction to list items direction
   4215                 // (e.g. finger moving down means list is moving towards the top)
   4216                 int delta = mLastFlingY - y;
   4217 
   4218                 // Pretend that each frame of a fling scroll is a touch scroll
   4219                 if (delta > 0) {
   4220                     // List is moving towards the top. Use first view as mMotionPosition
   4221                     mMotionPosition = mFirstPosition;
   4222                     final View firstView = getChildAt(0);
   4223                     mMotionViewOriginalTop = firstView.getTop();
   4224 
   4225                     // Don't fling more than 1 screen
   4226                     delta = Math.min(getHeight() - mPaddingBottom - mPaddingTop - 1, delta);
   4227                 } else {
   4228                     // List is moving towards the bottom. Use last view as mMotionPosition
   4229                     int offsetToLast = getChildCount() - 1;
   4230                     mMotionPosition = mFirstPosition + offsetToLast;
   4231 
   4232                     final View lastView = getChildAt(offsetToLast);
   4233                     mMotionViewOriginalTop = lastView.getTop();
   4234 
   4235                     // Don't fling more than 1 screen
   4236                     delta = Math.max(-(getHeight() - mPaddingBottom - mPaddingTop - 1), delta);
   4237                 }
   4238 
   4239                 // Check to see if we have bumped into the scroll limit
   4240                 View motionView = getChildAt(mMotionPosition - mFirstPosition);
   4241                 int oldTop = 0;
   4242                 if (motionView != null) {
   4243                     oldTop = motionView.getTop();
   4244                 }
   4245 
   4246                 // Don't stop just because delta is zero (it could have been rounded)
   4247                 final boolean atEdge = trackMotionScroll(delta, delta);
   4248                 final boolean atEnd = atEdge && (delta != 0);
   4249                 if (atEnd) {
   4250                     if (motionView != null) {
   4251                         // Tweak the scroll for how far we overshot
   4252                         int overshoot = -(delta - (motionView.getTop() - oldTop));
   4253                         overScrollBy(0, overshoot, 0, mScrollY, 0, 0,
   4254                                 0, mOverflingDistance, false);
   4255                     }
   4256                     if (more) {
   4257                         edgeReached(delta);
   4258                     }
   4259                     break;
   4260                 }
   4261 
   4262                 if (more && !atEnd) {
   4263                     if (atEdge) invalidate();
   4264                     mLastFlingY = y;
   4265                     postOnAnimation(this);
   4266                 } else {
   4267                     endFling();
   4268 
   4269                     if (PROFILE_FLINGING) {
   4270                         if (mFlingProfilingStarted) {
   4271                             Debug.stopMethodTracing();
   4272                             mFlingProfilingStarted = false;
   4273                         }
   4274 
   4275                         if (mFlingStrictSpan != null) {
   4276                             mFlingStrictSpan.finish();
   4277                             mFlingStrictSpan = null;
   4278                         }
   4279                     }
   4280                 }
   4281                 break;
   4282             }
   4283 
   4284             case TOUCH_MODE_OVERFLING: {
   4285                 final OverScroller scroller = mScroller;
   4286                 if (scroller.computeScrollOffset()) {
   4287                     final int scrollY = mScrollY;
   4288                     final int currY = scroller.getCurrY();
   4289                     final int deltaY = currY - scrollY;
   4290                     if (overScrollBy(0, deltaY, 0, scrollY, 0, 0,
   4291                             0, mOverflingDistance, false)) {
   4292                         final boolean crossDown = scrollY <= 0 && currY > 0;
   4293                         final boolean crossUp = scrollY >= 0 && currY < 0;
   4294                         if (crossDown || crossUp) {
   4295                             int velocity = (int) scroller.getCurrVelocity();
   4296                             if (crossUp) velocity = -velocity;
   4297 
   4298                             // Don't flywheel from this; we're just continuing things.
   4299                             scroller.abortAnimation();
   4300                             start(velocity);
   4301                         } else {
   4302                             startSpringback();
   4303                         }
   4304                     } else {
   4305                         invalidate();
   4306                         postOnAnimation(this);
   4307                     }
   4308                 } else {
   4309                     endFling();
   4310                 }
   4311                 break;
   4312             }
   4313             }
   4314         }
   4315     }
   4316 
   4317     class PositionScroller implements Runnable {
   4318         private static final int SCROLL_DURATION = 200;
   4319 
   4320         private static final int MOVE_DOWN_POS = 1;
   4321         private static final int MOVE_UP_POS = 2;
   4322         private static final int MOVE_DOWN_BOUND = 3;
   4323         private static final int MOVE_UP_BOUND = 4;
   4324         private static final int MOVE_OFFSET = 5;
   4325 
   4326         private int mMode;
   4327         private int mTargetPos;
   4328         private int mBoundPos;
   4329         private int mLastSeenPos;
   4330         private int mScrollDuration;
   4331         private final int mExtraScroll;
   4332 
   4333         private int mOffsetFromTop;
   4334 
   4335         PositionScroller() {
   4336             mExtraScroll = ViewConfiguration.get(mContext).getScaledFadingEdgeLength();
   4337         }
   4338 
   4339         void start(final int position) {
   4340             stop();
   4341 
   4342             if (mDataChanged) {
   4343                 // Wait until we're back in a stable state to try this.
   4344                 mPositionScrollAfterLayout = new Runnable() {
   4345                     @Override public void run() {
   4346                         start(position);
   4347                     }
   4348                 };
   4349                 return;
   4350             }
   4351 
   4352             final int childCount = getChildCount();
   4353             if (childCount == 0) {
   4354                 // Can't scroll without children.
   4355                 return;
   4356             }
   4357 
   4358             final int firstPos = mFirstPosition;
   4359             final int lastPos = firstPos + childCount - 1;
   4360 
   4361             int viewTravelCount;
   4362             int clampedPosition = Math.max(0, Math.min(getCount() - 1, position));
   4363             if (clampedPosition < firstPos) {
   4364                 viewTravelCount = firstPos - clampedPosition + 1;
   4365                 mMode = MOVE_UP_POS;
   4366             } else if (clampedPosition > lastPos) {
   4367                 viewTravelCount = clampedPosition - lastPos + 1;
   4368                 mMode = MOVE_DOWN_POS;
   4369             } else {
   4370                 scrollToVisible(clampedPosition, INVALID_POSITION, SCROLL_DURATION);
   4371                 return;
   4372             }
   4373 
   4374             if (viewTravelCount > 0) {
   4375                 mScrollDuration = SCROLL_DURATION / viewTravelCount;
   4376             } else {
   4377                 mScrollDuration = SCROLL_DURATION;
   4378             }
   4379             mTargetPos = clampedPosition;
   4380             mBoundPos = INVALID_POSITION;
   4381             mLastSeenPos = INVALID_POSITION;
   4382 
   4383             postOnAnimation(this);
   4384         }
   4385 
   4386         void start(final int position, final int boundPosition) {
   4387             stop();
   4388 
   4389             if (boundPosition == INVALID_POSITION) {
   4390                 start(position);
   4391                 return;
   4392             }
   4393 
   4394             if (mDataChanged) {
   4395                 // Wait until we're back in a stable state to try this.
   4396                 mPositionScrollAfterLayout = new Runnable() {
   4397                     @Override public void run() {
   4398                         start(position, boundPosition);
   4399                     }
   4400                 };
   4401                 return;
   4402             }
   4403 
   4404             final int childCount = getChildCount();
   4405             if (childCount == 0) {
   4406                 // Can't scroll without children.
   4407                 return;
   4408             }
   4409 
   4410             final int firstPos = mFirstPosition;
   4411             final int lastPos = firstPos + childCount - 1;
   4412 
   4413             int viewTravelCount;
   4414             int clampedPosition = Math.max(0, Math.min(getCount() - 1, position));
   4415             if (clampedPosition < firstPos) {
   4416                 final int boundPosFromLast = lastPos - boundPosition;
   4417                 if (boundPosFromLast < 1) {
   4418                     // Moving would shift our bound position off the screen. Abort.
   4419                     return;
   4420                 }
   4421 
   4422                 final int posTravel = firstPos - clampedPosition + 1;
   4423                 final int boundTravel = boundPosFromLast - 1;
   4424                 if (boundTravel < posTravel) {
   4425                     viewTravelCount = boundTravel;
   4426                     mMode = MOVE_UP_BOUND;
   4427                 } else {
   4428                     viewTravelCount = posTravel;
   4429                     mMode = MOVE_UP_POS;
   4430                 }
   4431             } else if (clampedPosition > lastPos) {
   4432                 final int boundPosFromFirst = boundPosition - firstPos;
   4433                 if (boundPosFromFirst < 1) {
   4434                     // Moving would shift our bound position off the screen. Abort.
   4435                     return;
   4436                 }
   4437 
   4438                 final int posTravel = clampedPosition - lastPos + 1;
   4439                 final int boundTravel = boundPosFromFirst - 1;
   4440                 if (boundTravel < posTravel) {
   4441                     viewTravelCount = boundTravel;
   4442                     mMode = MOVE_DOWN_BOUND;
   4443                 } else {
   4444                     viewTravelCount = posTravel;
   4445                     mMode = MOVE_DOWN_POS;
   4446                 }
   4447             } else {
   4448                 scrollToVisible(clampedPosition, boundPosition, SCROLL_DURATION);
   4449                 return;
   4450             }
   4451 
   4452             if (viewTravelCount > 0) {
   4453                 mScrollDuration = SCROLL_DURATION / viewTravelCount;
   4454             } else {
   4455                 mScrollDuration = SCROLL_DURATION;
   4456             }
   4457             mTargetPos = clampedPosition;
   4458             mBoundPos = boundPosition;
   4459             mLastSeenPos = INVALID_POSITION;
   4460 
   4461             postOnAnimation(this);
   4462         }
   4463 
   4464         void startWithOffset(int position, int offset) {
   4465             startWithOffset(position, offset, SCROLL_DURATION);
   4466         }
   4467 
   4468         void startWithOffset(final int position, int offset, final int duration) {
   4469             stop();
   4470 
   4471             if (mDataChanged) {
   4472                 // Wait until we're back in a stable state to try this.
   4473                 final int postOffset = offset;
   4474                 mPositionScrollAfterLayout = new Runnable() {
   4475                     @Override public void run() {
   4476                         startWithOffset(position, postOffset, duration);
   4477                     }
   4478                 };
   4479                 return;
   4480             }
   4481 
   4482             final int childCount = getChildCount();
   4483             if (childCount == 0) {
   4484                 // Can't scroll without children.
   4485                 return;
   4486             }
   4487 
   4488             offset += getPaddingTop();
   4489 
   4490             mTargetPos = Math.max(0, Math.min(getCount() - 1, position));
   4491             mOffsetFromTop = offset;
   4492             mBoundPos = INVALID_POSITION;
   4493             mLastSeenPos = INVALID_POSITION;
   4494             mMode = MOVE_OFFSET;
   4495 
   4496             final int firstPos = mFirstPosition;
   4497             final int lastPos = firstPos + childCount - 1;
   4498 
   4499             int viewTravelCount;
   4500             if (mTargetPos < firstPos) {
   4501                 viewTravelCount = firstPos - mTargetPos;
   4502             } else if (mTargetPos > lastPos) {
   4503                 viewTravelCount = mTargetPos - lastPos;
   4504             } else {
   4505                 // On-screen, just scroll.
   4506                 final int targetTop = getChildAt(mTargetPos - firstPos).getTop();
   4507                 smoothScrollBy(targetTop - offset, duration, true);
   4508                 return;
   4509             }
   4510 
   4511             // Estimate how many screens we should travel
   4512             final float screenTravelCount = (float) viewTravelCount / childCount;
   4513             mScrollDuration = screenTravelCount < 1 ?
   4514                     duration : (int) (duration / screenTravelCount);
   4515             mLastSeenPos = INVALID_POSITION;
   4516 
   4517             postOnAnimation(this);
   4518         }
   4519 
   4520         /**
   4521          * Scroll such that targetPos is in the visible padded region without scrolling
   4522          * boundPos out of view. Assumes targetPos is onscreen.
   4523          */
   4524         void scrollToVisible(int targetPos, int boundPos, int duration) {
   4525             final int firstPos = mFirstPosition;
   4526             final int childCount = getChildCount();
   4527             final int lastPos = firstPos + childCount - 1;
   4528             final int paddedTop = mListPadding.top;
   4529             final int paddedBottom = getHeight() - mListPadding.bottom;
   4530 
   4531             if (targetPos < firstPos || targetPos > lastPos) {
   4532                 Log.w(TAG, "scrollToVisible called with targetPos " + targetPos +
   4533                         " not visible [" + firstPos + ", " + lastPos + "]");
   4534             }
   4535             if (boundPos < firstPos || boundPos > lastPos) {
   4536                 // boundPos doesn't matter, it's already offscreen.
   4537                 boundPos = INVALID_POSITION;
   4538             }
   4539 
   4540             final View targetChild = getChildAt(targetPos - firstPos);
   4541             final int targetTop = targetChild.getTop();
   4542             final int targetBottom = targetChild.getBottom();
   4543             int scrollBy = 0;
   4544 
   4545             if (targetBottom > paddedBottom) {
   4546                 scrollBy = targetBottom - paddedBottom;
   4547             }
   4548             if (targetTop < paddedTop) {
   4549                 scrollBy = targetTop - paddedTop;
   4550             }
   4551 
   4552             if (scrollBy == 0) {
   4553                 return;
   4554             }
   4555 
   4556             if (boundPos >= 0) {
   4557                 final View boundChild = getChildAt(boundPos - firstPos);
   4558                 final int boundTop = boundChild.getTop();
   4559                 final int boundBottom = boundChild.getBottom();
   4560                 final int absScroll = Math.abs(scrollBy);
   4561 
   4562                 if (scrollBy < 0 && boundBottom + absScroll > paddedBottom) {
   4563                     // Don't scroll the bound view off the bottom of the screen.
   4564                     scrollBy = Math.max(0, boundBottom - paddedBottom);
   4565                 } else if (scrollBy > 0 && boundTop - absScroll < paddedTop) {
   4566                     // Don't scroll the bound view off the top of the screen.
   4567                     scrollBy = Math.min(0, boundTop - paddedTop);
   4568                 }
   4569             }
   4570 
   4571             smoothScrollBy(scrollBy, duration);
   4572         }
   4573 
   4574         void stop() {
   4575             removeCallbacks(this);
   4576         }
   4577 
   4578         @Override
   4579         public void run() {
   4580             final int listHeight = getHeight();
   4581             final int firstPos = mFirstPosition;
   4582 
   4583             switch (mMode) {
   4584             case MOVE_DOWN_POS: {
   4585                 final int lastViewIndex = getChildCount() - 1;
   4586                 final int lastPos = firstPos + lastViewIndex;
   4587 
   4588                 if (lastViewIndex < 0) {
   4589                     return;
   4590                 }
   4591 
   4592                 if (lastPos == mLastSeenPos) {
   4593                     // No new views, let things keep going.
   4594                     postOnAnimation(this);
   4595                     return;
   4596                 }
   4597 
   4598                 final View lastView = getChildAt(lastViewIndex);
   4599                 final int lastViewHeight = lastView.getHeight();
   4600                 final int lastViewTop = lastView.getTop();
   4601                 final int lastViewPixelsShowing = listHeight - lastViewTop;
   4602                 final int extraScroll = lastPos < mItemCount - 1 ?
   4603                         Math.max(mListPadding.bottom, mExtraScroll) : mListPadding.bottom;
   4604 
   4605                 final int scrollBy = lastViewHeight - lastViewPixelsShowing + extraScroll;
   4606                 smoothScrollBy(scrollBy, mScrollDuration, true);
   4607 
   4608                 mLastSeenPos = lastPos;
   4609                 if (lastPos < mTargetPos) {
   4610                     postOnAnimation(this);
   4611                 }
   4612                 break;
   4613             }
   4614 
   4615             case MOVE_DOWN_BOUND: {
   4616                 final int nextViewIndex = 1;
   4617                 final int childCount = getChildCount();
   4618 
   4619                 if (firstPos == mBoundPos || childCount <= nextViewIndex
   4620                         || firstPos + childCount >= mItemCount) {
   4621                     return;
   4622                 }
   4623                 final int nextPos = firstPos + nextViewIndex;
   4624 
   4625                 if (nextPos == mLastSeenPos) {
   4626                     // No new views, let things keep going.
   4627                     postOnAnimation(this);
   4628                     return;
   4629                 }
   4630 
   4631                 final View nextView = getChildAt(nextViewIndex);
   4632                 final int nextViewHeight = nextView.getHeight();
   4633                 final int nextViewTop = nextView.getTop();
   4634                 final int extraScroll = Math.max(mListPadding.bottom, mExtraScroll);
   4635                 if (nextPos < mBoundPos) {
   4636                     smoothScrollBy(Math.max(0, nextViewHeight + nextViewTop - extraScroll),
   4637                             mScrollDuration, true);
   4638 
   4639                     mLastSeenPos = nextPos;
   4640 
   4641                     postOnAnimation(this);
   4642                 } else  {
   4643                     if (nextViewTop > extraScroll) {
   4644                         smoothScrollBy(nextViewTop - extraScroll, mScrollDuration, true);
   4645                     }
   4646                 }
   4647                 break;
   4648             }
   4649 
   4650             case MOVE_UP_POS: {
   4651                 if (firstPos == mLastSeenPos) {
   4652                     // No new views, let things keep going.
   4653                     postOnAnimation(this);
   4654                     return;
   4655                 }
   4656 
   4657                 final View firstView = getChildAt(0);
   4658                 if (firstView == null) {
   4659                     return;
   4660                 }
   4661                 final int firstViewTop = firstView.getTop();
   4662                 final int extraScroll = firstPos > 0 ?
   4663                         Math.max(mExtraScroll, mListPadding.top) : mListPadding.top;
   4664 
   4665                 smoothScrollBy(firstViewTop - extraScroll, mScrollDuration, true);
   4666 
   4667                 mLastSeenPos = firstPos;
   4668 
   4669                 if (firstPos > mTargetPos) {
   4670                     postOnAnimation(this);
   4671                 }
   4672                 break;
   4673             }
   4674 
   4675             case MOVE_UP_BOUND: {
   4676                 final int lastViewIndex = getChildCount() - 2;
   4677                 if (lastViewIndex < 0) {
   4678                     return;
   4679                 }
   4680                 final int lastPos = firstPos + lastViewIndex;
   4681 
   4682                 if (lastPos == mLastSeenPos) {
   4683                     // No new views, let things keep going.
   4684                     postOnAnimation(this);
   4685                     return;
   4686                 }
   4687 
   4688                 final View lastView = getChildAt(lastViewIndex);
   4689                 final int lastViewHeight = lastView.getHeight();
   4690                 final int lastViewTop = lastView.getTop();
   4691                 final int lastViewPixelsShowing = listHeight - lastViewTop;
   4692                 final int extraScroll = Math.max(mListPadding.top, mExtraScroll);
   4693                 mLastSeenPos = lastPos;
   4694                 if (lastPos > mBoundPos) {
   4695                     smoothScrollBy(-(lastViewPixelsShowing - extraScroll), mScrollDuration, true);
   4696                     postOnAnimation(this);
   4697                 } else {
   4698                     final int bottom = listHeight - extraScroll;
   4699                     final int lastViewBottom = lastViewTop + lastViewHeight;
   4700                     if (bottom > lastViewBottom) {
   4701                         smoothScrollBy(-(bottom - lastViewBottom), mScrollDuration, true);
   4702                     }
   4703                 }
   4704                 break;
   4705             }
   4706 
   4707             case MOVE_OFFSET: {
   4708                 if (mLastSeenPos == firstPos) {
   4709                     // No new views, let things keep going.
   4710                     postOnAnimation(this);
   4711                     return;
   4712                 }
   4713 
   4714                 mLastSeenPos = firstPos;
   4715 
   4716                 final int childCount = getChildCount();
   4717                 final int position = mTargetPos;
   4718                 final int lastPos = firstPos + childCount - 1;
   4719 
   4720                 int viewTravelCount = 0;
   4721                 if (position < firstPos) {
   4722                     viewTravelCount = firstPos - position + 1;
   4723                 } else if (position > lastPos) {
   4724                     viewTravelCount = position - lastPos;
   4725                 }
   4726 
   4727                 // Estimate how many screens we should travel
   4728                 final float screenTravelCount = (float) viewTravelCount / childCount;
   4729 
   4730                 final float modifier = Math.min(Math.abs(screenTravelCount), 1.f);
   4731                 if (position < firstPos) {
   4732                     final int distance = (int) (-getHeight() * modifier);
   4733                     final int duration = (int) (mScrollDuration * modifier);
   4734                     smoothScrollBy(distance, duration, true);
   4735                     postOnAnimation(this);
   4736                 } else if (position > lastPos) {
   4737                     final int distance = (int) (getHeight() * modifier);
   4738                     final int duration = (int) (mScrollDuration * modifier);
   4739                     smoothScrollBy(distance, duration, true);
   4740                     postOnAnimation(this);
   4741                 } else {
   4742                     // On-screen, just scroll.
   4743                     final int targetTop = getChildAt(position - firstPos).getTop();
   4744                     final int distance = targetTop - mOffsetFromTop;
   4745                     final int duration = (int) (mScrollDuration *
   4746                             ((float) Math.abs(distance) / getHeight()));
   4747                     smoothScrollBy(distance, duration, true);
   4748                 }
   4749                 break;
   4750             }
   4751 
   4752             default:
   4753                 break;
   4754             }
   4755         }
   4756     }
   4757 
   4758     /**
   4759      * The amount of friction applied to flings. The default value
   4760      * is {@link ViewConfiguration#getScrollFriction}.
   4761      */
   4762     public void setFriction(float friction) {
   4763         if (mFlingRunnable == null) {
   4764             mFlingRunnable = new FlingRunnable();
   4765         }
   4766         mFlingRunnable.mScroller.setFriction(friction);
   4767     }
   4768 
   4769     /**
   4770      * Sets a scale factor for the fling velocity. The initial scale
   4771      * factor is 1.0.
   4772      *
   4773      * @param scale The scale factor to multiply the velocity by.
   4774      */
   4775     public void setVelocityScale(float scale) {
   4776         mVelocityScale = scale;
   4777     }
   4778 
   4779     /**
   4780      * Smoothly scroll to the specified adapter position. The view will
   4781      * scroll such that the indicated position is displayed.
   4782      * @param position Scroll to this adapter position.
   4783      */
   4784     public void smoothScrollToPosition(int position) {
   4785         if (mPositionScroller == null) {
   4786             mPositionScroller = new PositionScroller();
   4787         }
   4788         mPositionScroller.start(position);
   4789     }
   4790 
   4791     /**
   4792      * Smoothly scroll to the specified adapter position. The view will scroll
   4793      * such that the indicated position is displayed <code>offset</code> pixels from
   4794      * the top edge of the view. If this is impossible, (e.g. the offset would scroll
   4795      * the first or last item beyond the boundaries of the list) it will get as close
   4796      * as possible. The scroll will take <code>duration</code> milliseconds to complete.
   4797      *
   4798      * @param position Position to scroll to
   4799      * @param offset Desired distance in pixels of <code>position</code> from the top
   4800      *               of the view when scrolling is finished
   4801      * @param duration Number of milliseconds to use for the scroll
   4802      */
   4803     public void smoothScrollToPositionFromTop(int position, int offset, int duration) {
   4804         if (mPositionScroller == null) {
   4805             mPositionScroller = new PositionScroller();
   4806         }
   4807         mPositionScroller.startWithOffset(position, offset, duration);
   4808     }
   4809 
   4810     /**
   4811      * Smoothly scroll to the specified adapter position. The view will scroll
   4812      * such that the indicated position is displayed <code>offset</code> pixels from
   4813      * the top edge of the view. If this is impossible, (e.g. the offset would scroll
   4814      * the first or last item beyond the boundaries of the list) it will get as close
   4815      * as possible.
   4816      *
   4817      * @param position Position to scroll to
   4818      * @param offset Desired distance in pixels of <code>position</code> from the top
   4819      *               of the view when scrolling is finished
   4820      */
   4821     public void smoothScrollToPositionFromTop(int position, int offset) {
   4822         if (mPositionScroller == null) {
   4823             mPositionScroller = new PositionScroller();
   4824         }
   4825         mPositionScroller.startWithOffset(position, offset);
   4826     }
   4827 
   4828     /**
   4829      * Smoothly scroll to the specified adapter position. The view will
   4830      * scroll such that the indicated position is displayed, but it will
   4831      * stop early if scrolling further would scroll boundPosition out of
   4832      * view.
   4833      * @param position Scroll to this adapter position.
   4834      * @param boundPosition Do not scroll if it would move this adapter
   4835      *          position out of view.
   4836      */
   4837     public void smoothScrollToPosition(int position, int boundPosition) {
   4838         if (mPositionScroller == null) {
   4839             mPositionScroller = new PositionScroller();
   4840         }
   4841         mPositionScroller.start(position, boundPosition);
   4842     }
   4843 
   4844     /**
   4845      * Smoothly scroll by distance pixels over duration milliseconds.
   4846      * @param distance Distance to scroll in pixels.
   4847      * @param duration Duration of the scroll animation in milliseconds.
   4848      */
   4849     public void smoothScrollBy(int distance, int duration) {
   4850         smoothScrollBy(distance, duration, false);
   4851     }
   4852 
   4853     void smoothScrollBy(int distance, int duration, boolean linear) {
   4854         if (mFlingRunnable == null) {
   4855             mFlingRunnable = new FlingRunnable();
   4856         }
   4857 
   4858         // No sense starting to scroll if we're not going anywhere
   4859         final int firstPos = mFirstPosition;
   4860         final int childCount = getChildCount();
   4861         final int lastPos = firstPos + childCount;
   4862         final int topLimit = getPaddingTop();
   4863         final int bottomLimit = getHeight() - getPaddingBottom();
   4864 
   4865         if (distance == 0 || mItemCount == 0 || childCount == 0 ||
   4866                 (firstPos == 0 && getChildAt(0).getTop() == topLimit && distance < 0) ||
   4867                 (lastPos == mItemCount &&
   4868                         getChildAt(childCount - 1).getBottom() == bottomLimit && distance > 0)) {
   4869             mFlingRunnable.endFling();
   4870             if (mPositionScroller != null) {
   4871                 mPositionScroller.stop();
   4872             }
   4873         } else {
   4874             reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
   4875             mFlingRunnable.startScroll(distance, duration, linear);
   4876         }
   4877     }
   4878 
   4879     /**
   4880      * Allows RemoteViews to scroll relatively to a position.
   4881      */
   4882     void smoothScrollByOffset(int position) {
   4883         int index = -1;
   4884         if (position < 0) {
   4885             index = getFirstVisiblePosition();
   4886         } else if (position > 0) {
   4887             index = getLastVisiblePosition();
   4888         }
   4889 
   4890         if (index > -1) {
   4891             View child = getChildAt(index - getFirstVisiblePosition());
   4892             if (child != null) {
   4893                 Rect visibleRect = new Rect();
   4894                 if (child.getGlobalVisibleRect(visibleRect)) {
   4895                     // the child is partially visible
   4896                     int childRectArea = child.getWidth() * child.getHeight();
   4897                     int visibleRectArea = visibleRect.width() * visibleRect.height();
   4898                     float visibleArea = (visibleRectArea / (float) childRectArea);
   4899                     final float visibleThreshold = 0.75f;
   4900                     if ((position < 0) && (visibleArea < visibleThreshold)) {
   4901                         // the top index is not perceivably visible so offset
   4902                         // to account for showing that top index as well
   4903                         ++index;
   4904                     } else if ((position > 0) && (visibleArea < visibleThreshold)) {
   4905                         // the bottom index is not perceivably visible so offset
   4906                         // to account for showing that bottom index as well
   4907                         --index;
   4908                     }
   4909                 }
   4910                 smoothScrollToPosition(Math.max(0, Math.min(getCount(), index + position)));
   4911             }
   4912         }
   4913     }
   4914 
   4915     private void createScrollingCache() {
   4916         if (mScrollingCacheEnabled && !mCachingStarted && !isHardwareAccelerated()) {
   4917             setChildrenDrawnWithCacheEnabled(true);
   4918             setChildrenDrawingCacheEnabled(true);
   4919             mCachingStarted = mCachingActive = true;
   4920         }
   4921     }
   4922 
   4923     private void clearScrollingCache() {
   4924         if (!isHardwareAccelerated()) {
   4925             if (mClearScrollingCache == null) {
   4926                 mClearScrollingCache = new Runnable() {
   4927                     @Override
   4928                     public void run() {
   4929                         if (mCachingStarted) {
   4930                             mCachingStarted = mCachingActive = false;
   4931                             setChildrenDrawnWithCacheEnabled(false);
   4932                             if ((mPersistentDrawingCache & PERSISTENT_SCROLLING_CACHE) == 0) {
   4933                                 setChildrenDrawingCacheEnabled(false);
   4934                             }
   4935                             if (!isAlwaysDrawnWithCacheEnabled()) {
   4936                                 invalidate();
   4937                             }
   4938                         }
   4939                     }
   4940                 };
   4941             }
   4942             post(mClearScrollingCache);
   4943         }
   4944     }
   4945 
   4946     /**
   4947      * Scrolls the list items within the view by a specified number of pixels.
   4948      *
   4949      * @param y the amount of pixels to scroll by vertically
   4950      * @see #canScrollList(int)
   4951      */
   4952     public void scrollListBy(int y) {
   4953         trackMotionScroll(-y, -y);
   4954     }
   4955 
   4956     /**
   4957      * Check if the items in the list can be scrolled in a certain direction.
   4958      *
   4959      * @param direction Negative to check scrolling up, positive to check
   4960      *            scrolling down.
   4961      * @return true if the list can be scrolled in the specified direction,
   4962      *         false otherwise.
   4963      * @see #scrollListBy(int)
   4964      */
   4965     public boolean canScrollList(int direction) {
   4966         final int childCount = getChildCount();
   4967         if (childCount == 0) {
   4968             return false;
   4969         }
   4970 
   4971         final int firstPosition = mFirstPosition;
   4972         final Rect listPadding = mListPadding;
   4973         if (direction > 0) {
   4974             final int lastBottom = getChildAt(childCount - 1).getBottom();
   4975             final int lastPosition = firstPosition + childCount;
   4976             return lastPosition < mItemCount || lastBottom > getHeight() - listPadding.bottom;
   4977         } else {
   4978             final int firstTop = getChildAt(0).getTop();
   4979             return firstPosition > 0 || firstTop < listPadding.top;
   4980         }
   4981     }
   4982 
   4983     /**
   4984      * Track a motion scroll
   4985      *
   4986      * @param deltaY Amount to offset mMotionView. This is the accumulated delta since the motion
   4987      *        began. Positive numbers mean the user's finger is moving down the screen.
   4988      * @param incrementalDeltaY Change in deltaY from the previous event.
   4989      * @return true if we're already at the beginning/end of the list and have nothing to do.
   4990      */
   4991     boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
   4992         final int childCount = getChildCount();
   4993         if (childCount == 0) {
   4994             return true;
   4995         }
   4996 
   4997         final int firstTop = getChildAt(0).getTop();
   4998         final int lastBottom = getChildAt(childCount - 1).getBottom();
   4999 
   5000         final Rect listPadding = mListPadding;
   5001 
   5002         // "effective padding" In this case is the amount of padding that affects
   5003         // how much space should not be filled by items. If we don't clip to padding
   5004         // there is no effective padding.
   5005         int effectivePaddingTop = 0;
   5006         int effectivePaddingBottom = 0;
   5007         if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
   5008             effectivePaddingTop = listPadding.top;
   5009             effectivePaddingBottom = listPadding.bottom;
   5010         }
   5011 
   5012          // FIXME account for grid vertical spacing too?
   5013         final int spaceAbove = effectivePaddingTop - firstTop;
   5014         final int end = getHeight() - effectivePaddingBottom;
   5015         final int spaceBelow = lastBottom - end;
   5016 
   5017         final int height = getHeight() - mPaddingBottom - mPaddingTop;
   5018         if (deltaY < 0) {
   5019             deltaY = Math.max(-(height - 1), deltaY);
   5020         } else {
   5021             deltaY = Math.min(height - 1, deltaY);
   5022         }
   5023 
   5024         if (incrementalDeltaY < 0) {
   5025             incrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY);
   5026         } else {
   5027             incrementalDeltaY = Math.min(height - 1, incrementalDeltaY);
   5028         }
   5029 
   5030         final int firstPosition = mFirstPosition;
   5031 
   5032         // Update our guesses for where the first and last views are
   5033         if (firstPosition == 0) {
   5034             mFirstPositionDistanceGuess = firstTop - listPadding.top;
   5035         } else {
   5036             mFirstPositionDistanceGuess += incrementalDeltaY;
   5037         }
   5038         if (firstPosition + childCount == mItemCount) {
   5039             mLastPositionDistanceGuess = lastBottom + listPadding.bottom;
   5040         } else {
   5041             mLastPositionDistanceGuess += incrementalDeltaY;
   5042         }
   5043 
   5044         final boolean cannotScrollDown = (firstPosition == 0 &&
   5045                 firstTop >= listPadding.top && incrementalDeltaY >= 0);
   5046         final boolean cannotScrollUp = (firstPosition + childCount == mItemCount &&
   5047                 lastBottom <= getHeight() - listPadding.bottom && incrementalDeltaY <= 0);
   5048 
   5049         if (cannotScrollDown || cannotScrollUp) {
   5050             return incrementalDeltaY != 0;
   5051         }
   5052 
   5053         final boolean down = incrementalDeltaY < 0;
   5054 
   5055         final boolean inTouchMode = isInTouchMode();
   5056         if (inTouchMode) {
   5057             hideSelector();
   5058         }
   5059 
   5060         final int headerViewsCount = getHeaderViewsCount();
   5061         final int footerViewsStart = mItemCount - getFooterViewsCount();
   5062 
   5063         int start = 0;
   5064         int count = 0;
   5065 
   5066         if (down) {
   5067             int top = -incrementalDeltaY;
   5068             if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
   5069                 top += listPadding.top;
   5070             }
   5071             for (int i = 0; i < childCount; i++) {
   5072                 final View child = getChildAt(i);
   5073                 if (child.getBottom() >= top) {
   5074                     break;
   5075                 } else {
   5076                     count++;
   5077                     int position = firstPosition + i;
   5078                     if (position >= headerViewsCount && position < footerViewsStart) {
   5079                         // The view will be rebound to new data, clear any
   5080                         // system-managed transient state.
   5081                         if (child.isAccessibilityFocused()) {
   5082                             child.clearAccessibilityFocus();
   5083                         }
   5084                         mRecycler.addScrapView(child, position);
   5085                     }
   5086                 }
   5087             }
   5088         } else {
   5089             int bottom = getHeight() - incrementalDeltaY;
   5090             if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
   5091                 bottom -= listPadding.bottom;
   5092             }
   5093             for (int i = childCount - 1; i >= 0; i--) {
   5094                 final View child = getChildAt(i);
   5095                 if (child.getTop() <= bottom) {
   5096                     break;
   5097                 } else {
   5098                     start = i;
   5099                     count++;
   5100                     int position = firstPosition + i;
   5101                     if (position >= headerViewsCount && position < footerViewsStart) {
   5102                         // The view will be rebound to new data, clear any
   5103                         // system-managed transient state.
   5104                         if (child.isAccessibilityFocused()) {
   5105                             child.clearAccessibilityFocus();
   5106                         }
   5107                         mRecycler.addScrapView(child, position);
   5108                     }
   5109                 }
   5110             }
   5111         }
   5112 
   5113         mMotionViewNewTop = mMotionViewOriginalTop + deltaY;
   5114 
   5115         mBlockLayoutRequests = true;
   5116 
   5117         if (count > 0) {
   5118             detachViewsFromParent(start, count);
   5119             mRecycler.removeSkippedScrap();
   5120         }
   5121 
   5122         // invalidate before moving the children to avoid unnecessary invalidate
   5123         // calls to bubble up from the children all the way to the top
   5124         if (!awakenScrollBars()) {
   5125            invalidate();
   5126         }
   5127 
   5128         offsetChildrenTopAndBottom(incrementalDeltaY);
   5129 
   5130         if (down) {
   5131             mFirstPosition += count;
   5132         }
   5133 
   5134         final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
   5135         if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
   5136             fillGap(down);
   5137         }
   5138 
   5139         if (!inTouchMode && mSelectedPosition != INVALID_POSITION) {
   5140             final int childIndex = mSelectedPosition - mFirstPosition;
   5141             if (childIndex >= 0 && childIndex < getChildCount()) {
   5142                 positionSelector(mSelectedPosition, getChildAt(childIndex));
   5143             }
   5144         } else if (mSelectorPosition != INVALID_POSITION) {
   5145             final int childIndex = mSelectorPosition - mFirstPosition;
   5146             if (childIndex >= 0 && childIndex < getChildCount()) {
   5147                 positionSelector(INVALID_POSITION, getChildAt(childIndex));
   5148             }
   5149         } else {
   5150             mSelectorRect.setEmpty();
   5151         }
   5152 
   5153         mBlockLayoutRequests = false;
   5154 
   5155         invokeOnItemScrollListener();
   5156 
   5157         return false;
   5158     }
   5159 
   5160     /**
   5161      * Returns the number of header views in the list. Header views are special views
   5162      * at the top of the list that should not be recycled during a layout.
   5163      *
   5164      * @return The number of header views, 0 in the default implementation.
   5165      */
   5166     int getHeaderViewsCount() {
   5167         return 0;
   5168     }
   5169 
   5170     /**
   5171      * Returns the number of footer views in the list. Footer views are special views
   5172      * at the bottom of the list that should not be recycled during a layout.
   5173      *
   5174      * @return The number of footer views, 0 in the default implementation.
   5175      */
   5176     int getFooterViewsCount() {
   5177         return 0;
   5178     }
   5179 
   5180     /**
   5181      * Fills the gap left open by a touch-scroll. During a touch scroll, children that
   5182      * remain on screen are shifted and the other ones are discarded. The role of this
   5183      * method is to fill the gap thus created by performing a partial layout in the
   5184      * empty space.
   5185      *
   5186      * @param down true if the scroll is going down, false if it is going up
   5187      */
   5188     abstract void fillGap(boolean down);
   5189 
   5190     void hideSelector() {
   5191         if (mSelectedPosition != INVALID_POSITION) {
   5192             if (mLayoutMode != LAYOUT_SPECIFIC) {
   5193                 mResurrectToPosition = mSelectedPosition;
   5194             }
   5195             if (mNextSelectedPosition >= 0 && mNextSelectedPosition != mSelectedPosition) {
   5196                 mResurrectToPosition = mNextSelectedPosition;
   5197             }
   5198             setSelectedPositionInt(INVALID_POSITION);
   5199             setNextSelectedPositionInt(INVALID_POSITION);
   5200             mSelectedTop = 0;
   5201         }
   5202     }
   5203 
   5204     /**
   5205      * @return A position to select. First we try mSelectedPosition. If that has been clobbered by
   5206      * entering touch mode, we then try mResurrectToPosition. Values are pinned to the range
   5207      * of items available in the adapter
   5208      */
   5209     int reconcileSelectedPosition() {
   5210         int position = mSelectedPosition;
   5211         if (position < 0) {
   5212             position = mResurrectToPosition;
   5213         }
   5214         position = Math.max(0, position);
   5215         position = Math.min(position, mItemCount - 1);
   5216         return position;
   5217     }
   5218 
   5219     /**
   5220      * Find the row closest to y. This row will be used as the motion row when scrolling
   5221      *
   5222      * @param y Where the user touched
   5223      * @return The position of the first (or only) item in the row containing y
   5224      */
   5225     abstract int findMotionRow(int y);
   5226 
   5227     /**
   5228      * Find the row closest to y. This row will be used as the motion row when scrolling.
   5229      *
   5230      * @param y Where the user touched
   5231      * @return The position of the first (or only) item in the row closest to y
   5232      */
   5233     int findClosestMotionRow(int y) {
   5234         final int childCount = getChildCount();
   5235         if (childCount == 0) {
   5236             return INVALID_POSITION;
   5237         }
   5238 
   5239         final int motionRow = findMotionRow(y);
   5240         return motionRow != INVALID_POSITION ? motionRow : mFirstPosition + childCount - 1;
   5241     }
   5242 
   5243     /**
   5244      * Causes all the views to be rebuilt and redrawn.
   5245      */
   5246     public void invalidateViews() {
   5247         mDataChanged = true;
   5248         rememberSyncState();
   5249         requestLayout();
   5250         invalidate();
   5251     }
   5252 
   5253     /**
   5254      * If there is a selection returns false.
   5255      * Otherwise resurrects the selection and returns true if resurrected.
   5256      */
   5257     boolean resurrectSelectionIfNeeded() {
   5258         if (mSelectedPosition < 0 && resurrectSelection()) {
   5259             updateSelectorState();
   5260             return true;
   5261         }
   5262         return false;
   5263     }
   5264 
   5265     /**
   5266      * Makes the item at the supplied position selected.
   5267      *
   5268      * @param position the position of the new selection
   5269      */
   5270     abstract void setSelectionInt(int position);
   5271 
   5272     /**
   5273      * Attempt to bring the selection back if the user is switching from touch
   5274      * to trackball mode
   5275      * @return Whether selection was set to something.
   5276      */
   5277     boolean resurrectSelection() {
   5278         final int childCount = getChildCount();
   5279 
   5280         if (childCount <= 0) {
   5281             return false;
   5282         }
   5283 
   5284         int selectedTop = 0;
   5285         int selectedPos;
   5286         int childrenTop = mListPadding.top;
   5287         int childrenBottom = mBottom - mTop - mListPadding.bottom;
   5288         final int firstPosition = mFirstPosition;
   5289         final int toPosition = mResurrectToPosition;
   5290         boolean down = true;
   5291 
   5292         if (toPosition >= firstPosition && toPosition < firstPosition + childCount) {
   5293             selectedPos = toPosition;
   5294 
   5295             final View selected = getChildAt(selectedPos - mFirstPosition);
   5296             selectedTop = selected.getTop();
   5297             int selectedBottom = selected.getBottom();
   5298 
   5299             // We are scrolled, don't get in the fade
   5300             if (selectedTop < childrenTop) {
   5301                 selectedTop = childrenTop + getVerticalFadingEdgeLength();
   5302             } else if (selectedBottom > childrenBottom) {
   5303                 selectedTop = childrenBottom - selected.getMeasuredHeight()
   5304                         - getVerticalFadingEdgeLength();
   5305             }
   5306         } else {
   5307             if (toPosition < firstPosition) {
   5308                 // Default to selecting whatever is first
   5309                 selectedPos = firstPosition;
   5310                 for (int i = 0; i < childCount; i++) {
   5311                     final View v = getChildAt(i);
   5312                     final int top = v.getTop();
   5313 
   5314                     if (i == 0) {
   5315                         // Remember the position of the first item
   5316                         selectedTop = top;
   5317                         // See if we are scrolled at all
   5318                         if (firstPosition > 0 || top < childrenTop) {
   5319                             // If we are scrolled, don't select anything that is
   5320                             // in the fade region
   5321                             childrenTop += getVerticalFadingEdgeLength();
   5322                         }
   5323                     }
   5324                     if (top >= childrenTop) {
   5325                         // Found a view whose top is fully visisble
   5326                         selectedPos = firstPosition + i;
   5327                         selectedTop = top;
   5328                         break;
   5329                     }
   5330                 }
   5331             } else {
   5332                 final int itemCount = mItemCount;
   5333                 down = false;
   5334                 selectedPos = firstPosition + childCount - 1;
   5335 
   5336                 for (int i = childCount - 1; i >= 0; i--) {
   5337                     final View v = getChildAt(i);
   5338                     final int top = v.getTop();
   5339                     final int bottom = v.getBottom();
   5340 
   5341                     if (i == childCount - 1) {
   5342                         selectedTop = top;
   5343                         if (firstPosition + childCount < itemCount || bottom > childrenBottom) {
   5344                             childrenBottom -= getVerticalFadingEdgeLength();
   5345                         }
   5346                     }
   5347 
   5348                     if (bottom <= childrenBottom) {
   5349                         selectedPos = firstPosition + i;
   5350                         selectedTop = top;
   5351                         break;
   5352                     }
   5353                 }
   5354             }
   5355         }
   5356 
   5357         mResurrectToPosition = INVALID_POSITION;
   5358         removeCallbacks(mFlingRunnable);
   5359         if (mPositionScroller != null) {
   5360             mPositionScroller.stop();
   5361         }
   5362         mTouchMode = TOUCH_MODE_REST;
   5363         clearScrollingCache();
   5364         mSpecificTop = selectedTop;
   5365         selectedPos = lookForSelectablePosition(selectedPos, down);
   5366         if (selectedPos >= firstPosition && selectedPos <= getLastVisiblePosition()) {
   5367             mLayoutMode = LAYOUT_SPECIFIC;
   5368             updateSelectorState();
   5369             setSelectionInt(selectedPos);
   5370             invokeOnItemScrollListener();
   5371         } else {
   5372             selectedPos = INVALID_POSITION;
   5373         }
   5374         reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
   5375 
   5376         return selectedPos >= 0;
   5377     }
   5378 
   5379     void confirmCheckedPositionsById() {
   5380         // Clear out the positional check states, we'll rebuild it below from IDs.
   5381         mCheckStates.clear();
   5382 
   5383         boolean checkedCountChanged = false;
   5384         for (int checkedIndex = 0; checkedIndex < mCheckedIdStates.size(); checkedIndex++) {
   5385             final long id = mCheckedIdStates.keyAt(checkedIndex);
   5386             final int lastPos = mCheckedIdStates.valueAt(checkedIndex);
   5387 
   5388             final long lastPosId = mAdapter.getItemId(lastPos);
   5389             if (id != lastPosId) {
   5390                 // Look around to see if the ID is nearby. If not, uncheck it.
   5391                 final int start = Math.max(0, lastPos - CHECK_POSITION_SEARCH_DISTANCE);
   5392                 final int end = Math.min(lastPos + CHECK_POSITION_SEARCH_DISTANCE, mItemCount);
   5393                 boolean found = false;
   5394                 for (int searchPos = start; searchPos < end; searchPos++) {
   5395                     final long searchId = mAdapter.getItemId(searchPos);
   5396                     if (id == searchId) {
   5397                         found = true;
   5398                         mCheckStates.put(searchPos, true);
   5399                         mCheckedIdStates.setValueAt(checkedIndex, searchPos);
   5400                         break;
   5401                     }
   5402                 }
   5403 
   5404                 if (!found) {
   5405                     mCheckedIdStates.delete(id);
   5406                     checkedIndex--;
   5407                     mCheckedItemCount--;
   5408                     checkedCountChanged = true;
   5409                     if (mChoiceActionMode != null && mMultiChoiceModeCallback != null) {
   5410                         mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
   5411                                 lastPos, id, false);
   5412                     }
   5413                 }
   5414             } else {
   5415                 mCheckStates.put(lastPos, true);
   5416             }
   5417         }
   5418 
   5419         if (checkedCountChanged && mChoiceActionMode != null) {
   5420             mChoiceActionMode.invalidate();
   5421         }
   5422     }
   5423 
   5424     @Override
   5425     protected void handleDataChanged() {
   5426         int count = mItemCount;
   5427         int lastHandledItemCount = mLastHandledItemCount;
   5428         mLastHandledItemCount = mItemCount;
   5429 
   5430         if (mChoiceMode != CHOICE_MODE_NONE && mAdapter != null && mAdapter.hasStableIds()) {
   5431             confirmCheckedPositionsById();
   5432         }
   5433 
   5434         // TODO: In the future we can recycle these views based on stable ID instead.
   5435         mRecycler.clearTransientStateViews();
   5436 
   5437         if (count > 0) {
   5438             int newPos;
   5439             int selectablePos;
   5440 
   5441             // Find the row we are supposed to sync to
   5442             if (mNeedSync) {
   5443                 // Update this first, since setNextSelectedPositionInt inspects it
   5444                 mNeedSync = false;
   5445                 mPendingSync = null;
   5446 
   5447                 if (mTranscriptMode == TRANSCRIPT_MODE_ALWAYS_SCROLL) {
   5448                     mLayoutMode = LAYOUT_FORCE_BOTTOM;
   5449                     return;
   5450                 } else if (mTranscriptMode == TRANSCRIPT_MODE_NORMAL) {
   5451                     if (mForceTranscriptScroll) {
   5452                         mForceTranscriptScroll = false;
   5453                         mLayoutMode = LAYOUT_FORCE_BOTTOM;
   5454                         return;
   5455                     }
   5456                     final int childCount = getChildCount();
   5457                     final int listBottom = getHeight() - getPaddingBottom();
   5458                     final View lastChild = getChildAt(childCount - 1);
   5459                     final int lastBottom = lastChild != null ? lastChild.getBottom() : listBottom;
   5460                     if (mFirstPosition + childCount >= lastHandledItemCount &&
   5461                             lastBottom <= listBottom) {
   5462                         mLayoutMode = LAYOUT_FORCE_BOTTOM;
   5463                         return;
   5464                     }
   5465                     // Something new came in and we didn't scroll; give the user a clue that
   5466                     // there's something new.
   5467                     awakenScrollBars();
   5468                 }
   5469 
   5470                 switch (mSyncMode) {
   5471                 case SYNC_SELECTED_POSITION:
   5472                     if (isInTouchMode()) {
   5473                         // We saved our state when not in touch mode. (We know this because
   5474                         // mSyncMode is SYNC_SELECTED_POSITION.) Now we are trying to
   5475                         // restore in touch mode. Just leave mSyncPosition as it is (possibly
   5476                         // adjusting if the available range changed) and return.
   5477                         mLayoutMode = LAYOUT_SYNC;
   5478                         mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
   5479 
   5480                         return;
   5481                     } else {
   5482                         // See if we can find a position in the new data with the same
   5483                         // id as the old selection. This will change mSyncPosition.
   5484                         newPos = findSyncPosition();
   5485                         if (newPos >= 0) {
   5486                             // Found it. Now verify that new selection is still selectable
   5487                             selectablePos = lookForSelectablePosition(newPos, true);
   5488                             if (selectablePos == newPos) {
   5489                                 // Same row id is selected
   5490                                 mSyncPosition = newPos;
   5491 
   5492                                 if (mSyncHeight == getHeight()) {
   5493                                     // If we are at the same height as when we saved state, try
   5494                                     // to restore the scroll position too.
   5495                                     mLayoutMode = LAYOUT_SYNC;
   5496                                 } else {
   5497                                     // We are not the same height as when the selection was saved, so
   5498                                     // don't try to restore the exact position
   5499                                     mLayoutMode = LAYOUT_SET_SELECTION;
   5500                                 }
   5501 
   5502                                 // Restore selection
   5503                                 setNextSelectedPositionInt(newPos);
   5504                                 return;
   5505                             }
   5506                         }
   5507                     }
   5508                     break;
   5509                 case SYNC_FIRST_POSITION:
   5510                     // Leave mSyncPosition as it is -- just pin to available range
   5511                     mLayoutMode = LAYOUT_SYNC;
   5512                     mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
   5513 
   5514                     return;
   5515                 }
   5516             }
   5517 
   5518             if (!isInTouchMode()) {
   5519                 // We couldn't find matching data -- try to use the same position
   5520                 newPos = getSelectedItemPosition();
   5521 
   5522                 // Pin position to the available range
   5523                 if (newPos >= count) {
   5524                     newPos = count - 1;
   5525                 }
   5526                 if (newPos < 0) {
   5527                     newPos = 0;
   5528                 }
   5529 
   5530                 // Make sure we select something selectable -- first look down
   5531                 selectablePos = lookForSelectablePosition(newPos, true);
   5532 
   5533                 if (selectablePos >= 0) {
   5534                     setNextSelectedPositionInt(selectablePos);
   5535                     return;
   5536                 } else {
   5537                     // Looking down didn't work -- try looking up
   5538                     selectablePos = lookForSelectablePosition(newPos, false);
   5539                     if (selectablePos >= 0) {
   5540                         setNextSelectedPositionInt(selectablePos);
   5541                         return;
   5542                     }
   5543                 }
   5544             } else {
   5545 
   5546                 // We already know where we want to resurrect the selection
   5547                 if (mResurrectToPosition >= 0) {
   5548                     return;
   5549                 }
   5550             }
   5551 
   5552         }
   5553 
   5554         // Nothing is selected. Give up and reset everything.
   5555         mLayoutMode = mStackFromBottom ? LAYOUT_FORCE_BOTTOM : LAYOUT_FORCE_TOP;
   5556         mSelectedPosition = INVALID_POSITION;
   5557         mSelectedRowId = INVALID_ROW_ID;
   5558         mNextSelectedPosition = INVALID_POSITION;
   5559         mNextSelectedRowId = INVALID_ROW_ID;
   5560         mNeedSync = false;
   5561         mPendingSync = null;
   5562         mSelectorPosition = INVALID_POSITION;
   5563         checkSelectionChanged();
   5564     }
   5565 
   5566     @Override
   5567     protected void onDisplayHint(int hint) {
   5568         super.onDisplayHint(hint);
   5569         switch (hint) {
   5570             case INVISIBLE:
   5571                 if (mPopup != null && mPopup.isShowing()) {
   5572                     dismissPopup();
   5573                 }
   5574                 break;
   5575             case VISIBLE:
   5576                 if (mFiltered && mPopup != null && !mPopup.isShowing()) {
   5577                     showPopup();
   5578                 }
   5579                 break;
   5580         }
   5581         mPopupHidden = hint == INVISIBLE;
   5582     }
   5583 
   5584     /**
   5585      * Removes the filter window
   5586      */
   5587     private void dismissPopup() {
   5588         if (mPopup != null) {
   5589             mPopup.dismiss();
   5590         }
   5591     }
   5592 
   5593     /**
   5594      * Shows the filter window
   5595      */
   5596     private void showPopup() {
   5597         // Make sure we have a window before showing the popup
   5598         if (getWindowVisibility() == View.VISIBLE) {
   5599             createTextFilter(true);
   5600             positionPopup();
   5601             // Make sure we get focus if we are showing the popup
   5602             checkFocus();
   5603         }
   5604     }
   5605 
   5606     private void positionPopup() {
   5607         int screenHeight = getResources().getDisplayMetrics().heightPixels;
   5608         final int[] xy = new int[2];
   5609         getLocationOnScreen(xy);
   5610         // TODO: The 20 below should come from the theme
   5611         // TODO: And the gravity should be defined in the theme as well
   5612         final int bottomGap = screenHeight - xy[1] - getHeight() + (int) (mDensityScale * 20);
   5613         if (!mPopup.isShowing()) {
   5614             mPopup.showAtLocation(this, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL,
   5615                     xy[0], bottomGap);
   5616         } else {
   5617             mPopup.update(xy[0], bottomGap, -1, -1);
   5618         }
   5619     }
   5620 
   5621     /**
   5622      * What is the distance between the source and destination rectangles given the direction of
   5623      * focus navigation between them? The direction basically helps figure out more quickly what is
   5624      * self evident by the relationship between the rects...
   5625      *
   5626      * @param source the source rectangle
   5627      * @param dest the destination rectangle
   5628      * @param direction the direction
   5629      * @return the distance between the rectangles
   5630      */
   5631     static int getDistance(Rect source, Rect dest, int direction) {
   5632         int sX, sY; // source x, y
   5633         int dX, dY; // dest x, y
   5634         switch (direction) {
   5635         case View.FOCUS_RIGHT:
   5636             sX = source.right;
   5637             sY = source.top + source.height() / 2;
   5638             dX = dest.left;
   5639             dY = dest.top + dest.height() / 2;
   5640             break;
   5641         case View.FOCUS_DOWN:
   5642             sX = source.left + source.width() / 2;
   5643             sY = source.bottom;
   5644             dX = dest.left + dest.width() / 2;
   5645             dY = dest.top;
   5646             break;
   5647         case View.FOCUS_LEFT:
   5648             sX = source.left;
   5649             sY = source.top + source.height() / 2;
   5650             dX = dest.right;
   5651             dY = dest.top + dest.height() / 2;
   5652             break;
   5653         case View.FOCUS_UP:
   5654             sX = source.left + source.width() / 2;
   5655             sY = source.top;
   5656             dX = dest.left + dest.width() / 2;
   5657             dY = dest.bottom;
   5658             break;
   5659         case View.FOCUS_FORWARD:
   5660         case View.FOCUS_BACKWARD:
   5661             sX = source.right + source.width() / 2;
   5662             sY = source.top + source.height() / 2;
   5663             dX = dest.left + dest.width() / 2;
   5664             dY = dest.top + dest.height() / 2;
   5665             break;
   5666         default:
   5667             throw new IllegalArgumentException("direction must be one of "
   5668                     + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, "
   5669                     + "FOCUS_FORWARD, FOCUS_BACKWARD}.");
   5670         }
   5671         int deltaX = dX - sX;
   5672         int deltaY = dY - sY;
   5673         return deltaY * deltaY + deltaX * deltaX;
   5674     }
   5675 
   5676     @Override
   5677     protected boolean isInFilterMode() {
   5678         return mFiltered;
   5679     }
   5680 
   5681     /**
   5682      * Sends a key to the text filter window
   5683      *
   5684      * @param keyCode The keycode for the event
   5685      * @param event The actual key event
   5686      *
   5687      * @return True if the text filter handled the event, false otherwise.
   5688      */
   5689     boolean sendToTextFilter(int keyCode, int count, KeyEvent event) {
   5690         if (!acceptFilter()) {
   5691             return false;
   5692         }
   5693 
   5694         boolean handled = false;
   5695         boolean okToSend = true;
   5696         switch (keyCode) {
   5697         case KeyEvent.KEYCODE_DPAD_UP:
   5698         case KeyEvent.KEYCODE_DPAD_DOWN:
   5699         case KeyEvent.KEYCODE_DPAD_LEFT:
   5700         case KeyEvent.KEYCODE_DPAD_RIGHT:
   5701         case KeyEvent.KEYCODE_DPAD_CENTER:
   5702         case KeyEvent.KEYCODE_ENTER:
   5703             okToSend = false;
   5704             break;
   5705         case KeyEvent.KEYCODE_BACK:
   5706             if (mFiltered && mPopup != null && mPopup.isShowing()) {
   5707                 if (event.getAction() == KeyEvent.ACTION_DOWN
   5708                         && event.getRepeatCount() == 0) {
   5709                     KeyEvent.DispatcherState state = getKeyDispatcherState();
   5710                     if (state != null) {
   5711                         state.startTracking(event, this);
   5712                     }
   5713                     handled = true;
   5714                 } else if (event.getAction() == KeyEvent.ACTION_UP
   5715                         && event.isTracking() && !event.isCanceled()) {
   5716                     handled = true;
   5717                     mTextFilter.setText("");
   5718                 }
   5719             }
   5720             okToSend = false;
   5721             break;
   5722         case KeyEvent.KEYCODE_SPACE:
   5723             // Only send spaces once we are filtered
   5724             okToSend = mFiltered;
   5725             break;
   5726         }
   5727 
   5728         if (okToSend) {
   5729             createTextFilter(true);
   5730 
   5731             KeyEvent forwardEvent = event;
   5732             if (forwardEvent.getRepeatCount() > 0) {
   5733                 forwardEvent = KeyEvent.changeTimeRepeat(event, event.getEventTime(), 0);
   5734             }
   5735 
   5736             int action = event.getAction();
   5737             switch (action) {
   5738                 case KeyEvent.ACTION_DOWN:
   5739                     handled = mTextFilter.onKeyDown(keyCode, forwardEvent);
   5740                     break;
   5741 
   5742                 case KeyEvent.ACTION_UP:
   5743                     handled = mTextFilter.onKeyUp(keyCode, forwardEvent);
   5744                     break;
   5745 
   5746                 case KeyEvent.ACTION_MULTIPLE:
   5747                     handled = mTextFilter.onKeyMultiple(keyCode, count, event);
   5748                     break;
   5749             }
   5750         }
   5751         return handled;
   5752     }
   5753 
   5754     /**
   5755      * Return an InputConnection for editing of the filter text.
   5756      */
   5757     @Override
   5758     public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
   5759         if (isTextFilterEnabled()) {
   5760             if (mPublicInputConnection == null) {
   5761                 mDefInputConnection = new BaseInputConnection(this, false);
   5762                 mPublicInputConnection = new InputConnectionWrapper(outAttrs);
   5763             }
   5764             outAttrs.inputType = EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_FILTER;
   5765             outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE;
   5766             return mPublicInputConnection;
   5767         }
   5768         return null;
   5769     }
   5770 
   5771     private class InputConnectionWrapper implements InputConnection {
   5772         private final EditorInfo mOutAttrs;
   5773         private InputConnection mTarget;
   5774 
   5775         public InputConnectionWrapper(EditorInfo outAttrs) {
   5776             mOutAttrs = outAttrs;
   5777         }
   5778 
   5779         private InputConnection getTarget() {
   5780             if (mTarget == null) {
   5781                 mTarget = getTextFilterInput().onCreateInputConnection(mOutAttrs);
   5782             }
   5783             return mTarget;
   5784         }
   5785 
   5786         @Override
   5787         public boolean reportFullscreenMode(boolean enabled) {
   5788             // Use our own input connection, since it is
   5789             // the "real" one the IME is talking with.
   5790             return mDefInputConnection.reportFullscreenMode(enabled);
   5791         }
   5792 
   5793         @Override
   5794         public boolean performEditorAction(int editorAction) {
   5795             // The editor is off in its own window; we need to be
   5796             // the one that does this.
   5797             if (editorAction == EditorInfo.IME_ACTION_DONE) {
   5798                 InputMethodManager imm = (InputMethodManager)
   5799                         getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
   5800                 if (imm != null) {
   5801                     imm.hideSoftInputFromWindow(getWindowToken(), 0);
   5802                 }
   5803                 return true;
   5804             }
   5805             return false;
   5806         }
   5807 
   5808         @Override
   5809         public boolean sendKeyEvent(KeyEvent event) {
   5810             // Use our own input connection, since the filter
   5811             // text view may not be shown in a window so has
   5812             // no ViewAncestor to dispatch events with.
   5813             return mDefInputConnection.sendKeyEvent(event);
   5814         }
   5815 
   5816         @Override
   5817         public CharSequence getTextBeforeCursor(int n, int flags) {
   5818             if (mTarget == null) return "";
   5819             return mTarget.getTextBeforeCursor(n, flags);
   5820         }
   5821 
   5822         @Override
   5823         public CharSequence getTextAfterCursor(int n, int flags) {
   5824             if (mTarget == null) return "";
   5825             return mTarget.getTextAfterCursor(n, flags);
   5826         }
   5827 
   5828         @Override
   5829         public CharSequence getSelectedText(int flags) {
   5830             if (mTarget == null) return "";
   5831             return mTarget.getSelectedText(flags);
   5832         }
   5833 
   5834         @Override
   5835         public int getCursorCapsMode(int reqModes) {
   5836             if (mTarget == null) return InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
   5837             return mTarget.getCursorCapsMode(reqModes);
   5838         }
   5839 
   5840         @Override
   5841         public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
   5842             return getTarget().getExtractedText(request, flags);
   5843         }
   5844 
   5845         @Override
   5846         public boolean deleteSurroundingText(int beforeLength, int afterLength) {
   5847             return getTarget().deleteSurroundingText(beforeLength, afterLength);
   5848         }
   5849 
   5850         @Override
   5851         public boolean setComposingText(CharSequence text, int newCursorPosition) {
   5852             return getTarget().setComposingText(text, newCursorPosition);
   5853         }
   5854 
   5855         @Override
   5856         public boolean setComposingRegion(int start, int end) {
   5857             return getTarget().setComposingRegion(start, end);
   5858         }
   5859 
   5860         @Override
   5861         public boolean finishComposingText() {
   5862             return mTarget == null || mTarget.finishComposingText();
   5863         }
   5864 
   5865         @Override
   5866         public boolean commitText(CharSequence text, int newCursorPosition) {
   5867             return getTarget().commitText(text, newCursorPosition);
   5868         }
   5869 
   5870         @Override
   5871         public boolean commitCompletion(CompletionInfo text) {
   5872             return getTarget().commitCompletion(text);
   5873         }
   5874 
   5875         @Override
   5876         public boolean commitCorrection(CorrectionInfo correctionInfo) {
   5877             return getTarget().commitCorrection(correctionInfo);
   5878         }
   5879 
   5880         @Override
   5881         public boolean setSelection(int start, int end) {
   5882             return getTarget().setSelection(start, end);
   5883         }
   5884 
   5885         @Override
   5886         public boolean performContextMenuAction(int id) {
   5887             return getTarget().performContextMenuAction(id);
   5888         }
   5889 
   5890         @Override
   5891         public boolean beginBatchEdit() {
   5892             return getTarget().beginBatchEdit();
   5893         }
   5894 
   5895         @Override
   5896         public boolean endBatchEdit() {
   5897             return getTarget().endBatchEdit();
   5898         }
   5899 
   5900         @Override
   5901         public boolean clearMetaKeyStates(int states) {
   5902             return getTarget().clearMetaKeyStates(states);
   5903         }
   5904 
   5905         @Override
   5906         public boolean performPrivateCommand(String action, Bundle data) {
   5907             return getTarget().performPrivateCommand(action, data);
   5908         }
   5909     }
   5910 
   5911     /**
   5912      * For filtering we proxy an input connection to an internal text editor,
   5913      * and this allows the proxying to happen.
   5914      */
   5915     @Override
   5916     public boolean checkInputConnectionProxy(View view) {
   5917         return view == mTextFilter;
   5918     }
   5919 
   5920     /**
   5921      * Creates the window for the text filter and populates it with an EditText field;
   5922      *
   5923      * @param animateEntrance true if the window should appear with an animation
   5924      */
   5925     private void createTextFilter(boolean animateEntrance) {
   5926         if (mPopup == null) {
   5927             PopupWindow p = new PopupWindow(getContext());
   5928             p.setFocusable(false);
   5929             p.setTouchable(false);
   5930             p.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
   5931             p.setContentView(getTextFilterInput());
   5932             p.setWidth(LayoutParams.WRAP_CONTENT);
   5933             p.setHeight(LayoutParams.WRAP_CONTENT);
   5934             p.setBackgroundDrawable(null);
   5935             mPopup = p;
   5936             getViewTreeObserver().addOnGlobalLayoutListener(this);
   5937             mGlobalLayoutListenerAddedFilter = true;
   5938         }
   5939         if (animateEntrance) {
   5940             mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilter);
   5941         } else {
   5942             mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilterRestore);
   5943         }
   5944     }
   5945 
   5946     private EditText getTextFilterInput() {
   5947         if (mTextFilter == null) {
   5948             final LayoutInflater layoutInflater = LayoutInflater.from(getContext());
   5949             mTextFilter = (EditText) layoutInflater.inflate(
   5950                     com.android.internal.R.layout.typing_filter, null);
   5951             // For some reason setting this as the "real" input type changes
   5952             // the text view in some way that it doesn't work, and I don't
   5953             // want to figure out why this is.
   5954             mTextFilter.setRawInputType(EditorInfo.TYPE_CLASS_TEXT
   5955                     | EditorInfo.TYPE_TEXT_VARIATION_FILTER);
   5956             mTextFilter.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI);
   5957             mTextFilter.addTextChangedListener(this);
   5958         }
   5959         return mTextFilter;
   5960     }
   5961 
   5962     /**
   5963      * Clear the text filter.
   5964      */
   5965     public void clearTextFilter() {
   5966         if (mFiltered) {
   5967             getTextFilterInput().setText("");
   5968             mFiltered = false;
   5969             if (mPopup != null && mPopup.isShowing()) {
   5970                 dismissPopup();
   5971             }
   5972         }
   5973     }
   5974 
   5975     /**
   5976      * Returns if the ListView currently has a text filter.
   5977      */
   5978     public boolean hasTextFilter() {
   5979         return mFiltered;
   5980     }
   5981 
   5982     @Override
   5983     public void onGlobalLayout() {
   5984         if (isShown()) {
   5985             // Show the popup if we are filtered
   5986             if (mFiltered && mPopup != null && !mPopup.isShowing() && !mPopupHidden) {
   5987                 showPopup();
   5988             }
   5989         } else {
   5990             // Hide the popup when we are no longer visible
   5991             if (mPopup != null && mPopup.isShowing()) {
   5992                 dismissPopup();
   5993             }
   5994         }
   5995 
   5996     }
   5997 
   5998     /**
   5999      * For our text watcher that is associated with the text filter.  Does
   6000      * nothing.
   6001      */
   6002     @Override
   6003     public void beforeTextChanged(CharSequence s, int start, int count, int after) {
   6004     }
   6005 
   6006     /**
   6007      * For our text watcher that is associated with the text filter. Performs
   6008      * the actual filtering as the text changes, and takes care of hiding and
   6009      * showing the popup displaying the currently entered filter text.
   6010      */
   6011     @Override
   6012     public void onTextChanged(CharSequence s, int start, int before, int count) {
   6013         if (isTextFilterEnabled()) {
   6014             createTextFilter(true);
   6015             int length = s.length();
   6016             boolean showing = mPopup.isShowing();
   6017             if (!showing && length > 0) {
   6018                 // Show the filter popup if necessary
   6019                 showPopup();
   6020                 mFiltered = true;
   6021             } else if (showing && length == 0) {
   6022                 // Remove the filter popup if the user has cleared all text
   6023                 dismissPopup();
   6024                 mFiltered = false;
   6025             }
   6026             if (mAdapter instanceof Filterable) {
   6027                 Filter f = ((Filterable) mAdapter).getFilter();
   6028                 // Filter should not be null when we reach this part
   6029                 if (f != null) {
   6030                     f.filter(s, this);
   6031                 } else {
   6032                     throw new IllegalStateException("You cannot call onTextChanged with a non "
   6033                             + "filterable adapter");
   6034                 }
   6035             }
   6036         }
   6037     }
   6038 
   6039     /**
   6040      * For our text watcher that is associated with the text filter.  Does
   6041      * nothing.
   6042      */
   6043     @Override
   6044     public void afterTextChanged(Editable s) {
   6045     }
   6046 
   6047     @Override
   6048     public void onFilterComplete(int count) {
   6049         if (mSelectedPosition < 0 && count > 0) {
   6050             mResurrectToPosition = INVALID_POSITION;
   6051             resurrectSelection();
   6052         }
   6053     }
   6054 
   6055     @Override
   6056     protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
   6057         return new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
   6058                 ViewGroup.LayoutParams.WRAP_CONTENT, 0);
   6059     }
   6060 
   6061     @Override
   6062     protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
   6063         return new LayoutParams(p);
   6064     }
   6065 
   6066     @Override
   6067     public LayoutParams generateLayoutParams(AttributeSet attrs) {
   6068         return new AbsListView.LayoutParams(getContext(), attrs);
   6069     }
   6070 
   6071     @Override
   6072     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
   6073         return p instanceof AbsListView.LayoutParams;
   6074     }
   6075 
   6076     /**
   6077      * Puts the list or grid into transcript mode. In this mode the list or grid will always scroll
   6078      * to the bottom to show new items.
   6079      *
   6080      * @param mode the transcript mode to set
   6081      *
   6082      * @see #TRANSCRIPT_MODE_DISABLED
   6083      * @see #TRANSCRIPT_MODE_NORMAL
   6084      * @see #TRANSCRIPT_MODE_ALWAYS_SCROLL
   6085      */
   6086     public void setTranscriptMode(int mode) {
   6087         mTranscriptMode = mode;
   6088     }
   6089 
   6090     /**
   6091      * Returns the current transcript mode.
   6092      *
   6093      * @return {@link #TRANSCRIPT_MODE_DISABLED}, {@link #TRANSCRIPT_MODE_NORMAL} or
   6094      *         {@link #TRANSCRIPT_MODE_ALWAYS_SCROLL}
   6095      */
   6096     public int getTranscriptMode() {
   6097         return mTranscriptMode;
   6098     }
   6099 
   6100     @Override
   6101     public int getSolidColor() {
   6102         return mCacheColorHint;
   6103     }
   6104 
   6105     /**
   6106      * When set to a non-zero value, the cache color hint indicates that this list is always drawn
   6107      * on top of a solid, single-color, opaque background.
   6108      *
   6109      * Zero means that what's behind this object is translucent (non solid) or is not made of a
   6110      * single color. This hint will not affect any existing background drawable set on this view (
   6111      * typically set via {@link #setBackgroundDrawable(Drawable)}).
   6112      *
   6113      * @param color The background color
   6114      */
   6115     public void setCacheColorHint(int color) {
   6116         if (color != mCacheColorHint) {
   6117             mCacheColorHint = color;
   6118             int count = getChildCount();
   6119             for (int i = 0; i < count; i++) {
   6120                 getChildAt(i).setDrawingCacheBackgroundColor(color);
   6121             }
   6122             mRecycler.setCacheColorHint(color);
   6123         }
   6124     }
   6125 
   6126     /**
   6127      * When set to a non-zero value, the cache color hint indicates that this list is always drawn
   6128      * on top of a solid, single-color, opaque background
   6129      *
   6130      * @return The cache color hint
   6131      */
   6132     @ViewDebug.ExportedProperty(category = "drawing")
   6133     public int getCacheColorHint() {
   6134         return mCacheColorHint;
   6135     }
   6136 
   6137     /**
   6138      * Move all views (excluding headers and footers) held by this AbsListView into the supplied
   6139      * List. This includes views displayed on the screen as well as views stored in AbsListView's
   6140      * internal view recycler.
   6141      *
   6142      * @param views A list into which to put the reclaimed views
   6143      */
   6144     public void reclaimViews(List<View> views) {
   6145         int childCount = getChildCount();
   6146         RecyclerListener listener = mRecycler.mRecyclerListener;
   6147 
   6148         // Reclaim views on screen
   6149         for (int i = 0; i < childCount; i++) {
   6150             View child = getChildAt(i);
   6151             AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
   6152             // Don't reclaim header or footer views, or views that should be ignored
   6153             if (lp != null && mRecycler.shouldRecycleViewType(lp.viewType)) {
   6154                 views.add(child);
   6155                 child.setAccessibilityDelegate(null);
   6156                 if (listener != null) {
   6157                     // Pretend they went through the scrap heap
   6158                     listener.onMovedToScrapHeap(child);
   6159                 }
   6160             }
   6161         }
   6162         mRecycler.reclaimScrapViews(views);
   6163         removeAllViewsInLayout();
   6164     }
   6165 
   6166     private void finishGlows() {
   6167         if (mEdgeGlowTop != null) {
   6168             mEdgeGlowTop.finish();
   6169             mEdgeGlowBottom.finish();
   6170         }
   6171     }
   6172 
   6173     /**
   6174      * Sets up this AbsListView to use a remote views adapter which connects to a RemoteViewsService
   6175      * through the specified intent.
   6176      * @param intent the intent used to identify the RemoteViewsService for the adapter to connect to.
   6177      */
   6178     public void setRemoteViewsAdapter(Intent intent) {
   6179         // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
   6180         // service handling the specified intent.
   6181         if (mRemoteAdapter != null) {
   6182             Intent.FilterComparison fcNew = new Intent.FilterComparison(intent);
   6183             Intent.FilterComparison fcOld = new Intent.FilterComparison(
   6184                     mRemoteAdapter.getRemoteViewsServiceIntent());
   6185             if (fcNew.equals(fcOld)) {
   6186                 return;
   6187             }
   6188         }
   6189         mDeferNotifyDataSetChanged = false;
   6190         // Otherwise, create a new RemoteViewsAdapter for binding
   6191         mRemoteAdapter = new RemoteViewsAdapter(getContext(), intent, this);
   6192         if (mRemoteAdapter.isDataReady()) {
   6193             setAdapter(mRemoteAdapter);
   6194         }
   6195     }
   6196 
   6197     /**
   6198      * Sets up the onClickHandler to be used by the RemoteViewsAdapter when inflating RemoteViews
   6199      *
   6200      * @param handler The OnClickHandler to use when inflating RemoteViews.
   6201      *
   6202      * @hide
   6203      */
   6204     public void setRemoteViewsOnClickHandler(OnClickHandler handler) {
   6205         // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
   6206         // service handling the specified intent.
   6207         if (mRemoteAdapter != null) {
   6208             mRemoteAdapter.setRemoteViewsOnClickHandler(handler);
   6209         }
   6210     }
   6211 
   6212     /**
   6213      * This defers a notifyDataSetChanged on the pending RemoteViewsAdapter if it has not
   6214      * connected yet.
   6215      */
   6216     @Override
   6217     public void deferNotifyDataSetChanged() {
   6218         mDeferNotifyDataSetChanged = true;
   6219     }
   6220 
   6221     /**
   6222      * Called back when the adapter connects to the RemoteViewsService.
   6223      */
   6224     @Override
   6225     public boolean onRemoteAdapterConnected() {
   6226         if (mRemoteAdapter != mAdapter) {
   6227             setAdapter(mRemoteAdapter);
   6228             if (mDeferNotifyDataSetChanged) {
   6229                 mRemoteAdapter.notifyDataSetChanged();
   6230                 mDeferNotifyDataSetChanged = false;
   6231             }
   6232             return false;
   6233         } else if (mRemoteAdapter != null) {
   6234             mRemoteAdapter.superNotifyDataSetChanged();
   6235             return true;
   6236         }
   6237         return false;
   6238     }
   6239 
   6240     /**
   6241      * Called back when the adapter disconnects from the RemoteViewsService.
   6242      */
   6243     @Override
   6244     public void onRemoteAdapterDisconnected() {
   6245         // If the remote adapter disconnects, we keep it around
   6246         // since the currently displayed items are still cached.
   6247         // Further, we want the service to eventually reconnect
   6248         // when necessary, as triggered by this view requesting
   6249         // items from the Adapter.
   6250     }
   6251 
   6252     /**
   6253      * Hints the RemoteViewsAdapter, if it exists, about which views are currently
   6254      * being displayed by the AbsListView.
   6255      */
   6256     void setVisibleRangeHint(int start, int end) {
   6257         if (mRemoteAdapter != null) {
   6258             mRemoteAdapter.setVisibleRangeHint(start, end);
   6259         }
   6260     }
   6261 
   6262     /**
   6263      * Sets the recycler listener to be notified whenever a View is set aside in
   6264      * the recycler for later reuse. This listener can be used to free resources
   6265      * associated to the View.
   6266      *
   6267      * @param listener The recycler listener to be notified of views set aside
   6268      *        in the recycler.
   6269      *
   6270      * @see android.widget.AbsListView.RecycleBin
   6271      * @see android.widget.AbsListView.RecyclerListener
   6272      */
   6273     public void setRecyclerListener(RecyclerListener listener) {
   6274         mRecycler.mRecyclerListener = listener;
   6275     }
   6276 
   6277     class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver {
   6278         @Override
   6279         public void onChanged() {
   6280             super.onChanged();
   6281             if (mFastScroller != null) {
   6282                 mFastScroller.onSectionsChanged();
   6283             }
   6284         }
   6285 
   6286         @Override
   6287         public void onInvalidated() {
   6288             super.onInvalidated();
   6289             if (mFastScroller != null) {
   6290                 mFastScroller.onSectionsChanged();
   6291             }
   6292         }
   6293     }
   6294 
   6295     /**
   6296      * A MultiChoiceModeListener receives events for {@link AbsListView#CHOICE_MODE_MULTIPLE_MODAL}.
   6297      * It acts as the {@link ActionMode.Callback} for the selection mode and also receives
   6298      * {@link #onItemCheckedStateChanged(ActionMode, int, long, boolean)} events when the user
   6299      * selects and deselects list items.
   6300      */
   6301     public interface MultiChoiceModeListener extends ActionMode.Callback {
   6302         /**
   6303          * Called when an item is checked or unchecked during selection mode.
   6304          *
   6305          * @param mode The {@link ActionMode} providing the selection mode
   6306          * @param position Adapter position of the item that was checked or unchecked
   6307          * @param id Adapter ID of the item that was checked or unchecked
   6308          * @param checked <code>true</code> if the item is now checked, <code>false</code>
   6309          *                if the item is now unchecked.
   6310          */
   6311         public void onItemCheckedStateChanged(ActionMode mode,
   6312                 int position, long id, boolean checked);
   6313     }
   6314 
   6315     class MultiChoiceModeWrapper implements MultiChoiceModeListener {
   6316         private MultiChoiceModeListener mWrapped;
   6317 
   6318         public void setWrapped(MultiChoiceModeListener wrapped) {
   6319             mWrapped = wrapped;
   6320         }
   6321 
   6322         public boolean hasWrappedCallback() {
   6323             return mWrapped != null;
   6324         }
   6325 
   6326         @Override
   6327         public boolean onCreateActionMode(ActionMode mode, Menu menu) {
   6328             if (mWrapped.onCreateActionMode(mode, menu)) {
   6329                 // Initialize checked graphic state?
   6330                 setLongClickable(false);
   6331                 return true;
   6332             }
   6333             return false;
   6334         }
   6335 
   6336         @Override
   6337         public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
   6338             return mWrapped.onPrepareActionMode(mode, menu);
   6339         }
   6340 
   6341         @Override
   6342         public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
   6343             return mWrapped.onActionItemClicked(mode, item);
   6344         }
   6345 
   6346         @Override
   6347         public void onDestroyActionMode(ActionMode mode) {
   6348             mWrapped.onDestroyActionMode(mode);
   6349             mChoiceActionMode = null;
   6350 
   6351             // Ending selection mode means deselecting everything.
   6352             clearChoices();
   6353 
   6354             mDataChanged = true;
   6355             rememberSyncState();
   6356             requestLayout();
   6357 
   6358             setLongClickable(true);
   6359         }
   6360 
   6361         @Override
   6362         public void onItemCheckedStateChanged(ActionMode mode,
   6363                 int position, long id, boolean checked) {
   6364             mWrapped.onItemCheckedStateChanged(mode, position, id, checked);
   6365 
   6366             // If there are no items selected we no longer need the selection mode.
   6367             if (getCheckedItemCount() == 0) {
   6368                 mode.finish();
   6369             }
   6370         }
   6371     }
   6372 
   6373     /**
   6374      * AbsListView extends LayoutParams to provide a place to hold the view type.
   6375      */
   6376     public static class LayoutParams extends ViewGroup.LayoutParams {
   6377         /**
   6378          * View type for this view, as returned by
   6379          * {@link android.widget.Adapter#getItemViewType(int) }
   6380          */
   6381         @ViewDebug.ExportedProperty(category = "list", mapping = {
   6382             @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_IGNORE, to = "ITEM_VIEW_TYPE_IGNORE"),
   6383             @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_HEADER_OR_FOOTER, to = "ITEM_VIEW_TYPE_HEADER_OR_FOOTER")
   6384         })
   6385         int viewType;
   6386 
   6387         /**
   6388          * When this boolean is set, the view has been added to the AbsListView
   6389          * at least once. It is used to know whether headers/footers have already
   6390          * been added to the list view and whether they should be treated as
   6391          * recycled views or not.
   6392          */
   6393         @ViewDebug.ExportedProperty(category = "list")
   6394         boolean recycledHeaderFooter;
   6395 
   6396         /**
   6397          * When an AbsListView is measured with an AT_MOST measure spec, it needs
   6398          * to obtain children views to measure itself. When doing so, the children
   6399          * are not attached to the window, but put in the recycler which assumes
   6400          * they've been attached before. Setting this flag will force the reused
   6401          * view to be attached to the window rather than just attached to the
   6402          * parent.
   6403          */
   6404         @ViewDebug.ExportedProperty(category = "list")
   6405         boolean forceAdd;
   6406 
   6407         /**
   6408          * The position the view was removed from when pulled out of the
   6409          * scrap heap.
   6410          * @hide
   6411          */
   6412         int scrappedFromPosition;
   6413 
   6414         /**
   6415          * The ID the view represents
   6416          */
   6417         long itemId = -1;
   6418 
   6419         public LayoutParams(Context c, AttributeSet attrs) {
   6420             super(c, attrs);
   6421         }
   6422 
   6423         public LayoutParams(int w, int h) {
   6424             super(w, h);
   6425         }
   6426 
   6427         public LayoutParams(int w, int h, int viewType) {
   6428             super(w, h);
   6429             this.viewType = viewType;
   6430         }
   6431 
   6432         public LayoutParams(ViewGroup.LayoutParams source) {
   6433             super(source);
   6434         }
   6435     }
   6436 
   6437     /**
   6438      * A RecyclerListener is used to receive a notification whenever a View is placed
   6439      * inside the RecycleBin's scrap heap. This listener is used to free resources
   6440      * associated to Views placed in the RecycleBin.
   6441      *
   6442      * @see android.widget.AbsListView.RecycleBin
   6443      * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
   6444      */
   6445     public static interface RecyclerListener {
   6446         /**
   6447          * Indicates that the specified View was moved into the recycler's scrap heap.
   6448          * The view is not displayed on screen any more and any expensive resource
   6449          * associated with the view should be discarded.
   6450          *
   6451          * @param view
   6452          */
   6453         void onMovedToScrapHeap(View view);
   6454     }
   6455 
   6456     /**
   6457      * The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of
   6458      * storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the
   6459      * start of a layout. By construction, they are displaying current information. At the end of
   6460      * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that
   6461      * could potentially be used by the adapter to avoid allocating views unnecessarily.
   6462      *
   6463      * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
   6464      * @see android.widget.AbsListView.RecyclerListener
   6465      */
   6466     class RecycleBin {
   6467         private RecyclerListener mRecyclerListener;
   6468 
   6469         /**
   6470          * The position of the first view stored in mActiveViews.
   6471          */
   6472         private int mFirstActivePosition;
   6473 
   6474         /**
   6475          * Views that were on screen at the start of layout. This array is populated at the start of
   6476          * layout, and at the end of layout all view in mActiveViews are moved to mScrapViews.
   6477          * Views in mActiveViews represent a contiguous range of Views, with position of the first
   6478          * view store in mFirstActivePosition.
   6479          */
   6480         private View[] mActiveViews = new View[0];
   6481 
   6482         /**
   6483          * Unsorted views that can be used by the adapter as a convert view.
   6484          */
   6485         private ArrayList<View>[] mScrapViews;
   6486 
   6487         private int mViewTypeCount;
   6488 
   6489         private ArrayList<View> mCurrentScrap;
   6490 
   6491         private ArrayList<View> mSkippedScrap;
   6492 
   6493         private SparseArray<View> mTransientStateViews;
   6494         private LongSparseArray<View> mTransientStateViewsById;
   6495 
   6496         public void setViewTypeCount(int viewTypeCount) {
   6497             if (viewTypeCount < 1) {
   6498                 throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
   6499             }
   6500             //noinspection unchecked
   6501             ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
   6502             for (int i = 0; i < viewTypeCount; i++) {
   6503                 scrapViews[i] = new ArrayList<View>();
   6504             }
   6505             mViewTypeCount = viewTypeCount;
   6506             mCurrentScrap = scrapViews[0];
   6507             mScrapViews = scrapViews;
   6508         }
   6509 
   6510         public void markChildrenDirty() {
   6511             if (mViewTypeCount == 1) {
   6512                 final ArrayList<View> scrap = mCurrentScrap;
   6513                 final int scrapCount = scrap.size();
   6514                 for (int i = 0; i < scrapCount; i++) {
   6515                     scrap.get(i).forceLayout();
   6516                 }
   6517             } else {
   6518                 final int typeCount = mViewTypeCount;
   6519                 for (int i = 0; i < typeCount; i++) {
   6520                     final ArrayList<View> scrap = mScrapViews[i];
   6521                     final int scrapCount = scrap.size();
   6522                     for (int j = 0; j < scrapCount; j++) {
   6523                         scrap.get(j).forceLayout();
   6524                     }
   6525                 }
   6526             }
   6527             if (mTransientStateViews != null) {
   6528                 final int count = mTransientStateViews.size();
   6529                 for (int i = 0; i < count; i++) {
   6530                     mTransientStateViews.valueAt(i).forceLayout();
   6531                 }
   6532             }
   6533             if (mTransientStateViewsById != null) {
   6534                 final int count = mTransientStateViewsById.size();
   6535                 for (int i = 0; i < count; i++) {
   6536                     mTransientStateViewsById.valueAt(i).forceLayout();
   6537                 }
   6538             }
   6539         }
   6540 
   6541         public boolean shouldRecycleViewType(int viewType) {
   6542             return viewType >= 0;
   6543         }
   6544 
   6545         /**
   6546          * Clears the scrap heap.
   6547          */
   6548         void clear() {
   6549             if (mViewTypeCount == 1) {
   6550                 final ArrayList<View> scrap = mCurrentScrap;
   6551                 final int scrapCount = scrap.size();
   6552                 for (int i = 0; i < scrapCount; i++) {
   6553                     removeDetachedView(scrap.remove(scrapCount - 1 - i), false);
   6554                 }
   6555             } else {
   6556                 final int typeCount = mViewTypeCount;
   6557                 for (int i = 0; i < typeCount; i++) {
   6558                     final ArrayList<View> scrap = mScrapViews[i];
   6559                     final int scrapCount = scrap.size();
   6560                     for (int j = 0; j < scrapCount; j++) {
   6561                         removeDetachedView(scrap.remove(scrapCount - 1 - j), false);
   6562                     }
   6563                 }
   6564             }
   6565             if (mTransientStateViews != null) {
   6566                 mTransientStateViews.clear();
   6567             }
   6568             if (mTransientStateViewsById != null) {
   6569                 mTransientStateViewsById.clear();
   6570             }
   6571         }
   6572 
   6573         /**
   6574          * Fill ActiveViews with all of the children of the AbsListView.
   6575          *
   6576          * @param childCount The minimum number of views mActiveViews should hold
   6577          * @param firstActivePosition The position of the first view that will be stored in
   6578          *        mActiveViews
   6579          */
   6580         void fillActiveViews(int childCount, int firstActivePosition) {
   6581             if (mActiveViews.length < childCount) {
   6582                 mActiveViews = new View[childCount];
   6583             }
   6584             mFirstActivePosition = firstActivePosition;
   6585 
   6586             //noinspection MismatchedReadAndWriteOfArray
   6587             final View[] activeViews = mActiveViews;
   6588             for (int i = 0; i < childCount; i++) {
   6589                 View child = getChildAt(i);
   6590                 AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
   6591                 // Don't put header or footer views into the scrap heap
   6592                 if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
   6593                     // Note:  We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views.
   6594                     //        However, we will NOT place them into scrap views.
   6595                     activeViews[i] = child;
   6596                 }
   6597             }
   6598         }
   6599 
   6600         /**
   6601          * Get the view corresponding to the specified position. The view will be removed from
   6602          * mActiveViews if it is found.
   6603          *
   6604          * @param position The position to look up in mActiveViews
   6605          * @return The view if it is found, null otherwise
   6606          */
   6607         View getActiveView(int position) {
   6608             int index = position - mFirstActivePosition;
   6609             final View[] activeViews = mActiveViews;
   6610             if (index >=0 && index < activeViews.length) {
   6611                 final View match = activeViews[index];
   6612                 activeViews[index] = null;
   6613                 return match;
   6614             }
   6615             return null;
   6616         }
   6617 
   6618         View getTransientStateView(int position) {
   6619             if (mAdapter != null && mAdapterHasStableIds && mTransientStateViewsById != null) {
   6620                 long id = mAdapter.getItemId(position);
   6621                 View result = mTransientStateViewsById.get(id);
   6622                 mTransientStateViewsById.remove(id);
   6623                 return result;
   6624             }
   6625             if (mTransientStateViews != null) {
   6626                 final int index = mTransientStateViews.indexOfKey(position);
   6627                 if (index >= 0) {
   6628                     View result = mTransientStateViews.valueAt(index);
   6629                     mTransientStateViews.removeAt(index);
   6630                     return result;
   6631                 }
   6632             }
   6633             return null;
   6634         }
   6635 
   6636         /**
   6637          * Dump any currently saved views with transient state.
   6638          */
   6639         void clearTransientStateViews() {
   6640             if (mTransientStateViews != null) {
   6641                 mTransientStateViews.clear();
   6642             }
   6643             if (mTransientStateViewsById != null) {
   6644                 mTransientStateViewsById.clear();
   6645             }
   6646         }
   6647 
   6648         /**
   6649          * @return A view from the ScrapViews collection. These are unordered.
   6650          */
   6651         View getScrapView(int position) {
   6652             if (mViewTypeCount == 1) {
   6653                 return retrieveFromScrap(mCurrentScrap, position);
   6654             } else {
   6655                 int whichScrap = mAdapter.getItemViewType(position);
   6656                 if (whichScrap >= 0 && whichScrap < mScrapViews.length) {
   6657                     return retrieveFromScrap(mScrapViews[whichScrap], position);
   6658                 }
   6659             }
   6660             return null;
   6661         }
   6662 
   6663         /**
   6664          * Puts a view into the list of scrap views.
   6665          * <p>
   6666          * If the list data hasn't changed or the adapter has stable IDs, views
   6667          * with transient state will be preserved for later retrieval.
   6668          *
   6669          * @param scrap The view to add
   6670          * @param position The view's position within its parent
   6671          */
   6672         void addScrapView(View scrap, int position) {
   6673             final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
   6674             if (lp == null) {
   6675                 return;
   6676             }
   6677 
   6678             lp.scrappedFromPosition = position;
   6679 
   6680             // Remove but don't scrap header or footer views, or views that
   6681             // should otherwise not be recycled.
   6682             final int viewType = lp.viewType;
   6683             if (!shouldRecycleViewType(viewType)) {
   6684                 return;
   6685             }
   6686 
   6687             scrap.dispatchStartTemporaryDetach();
   6688 
   6689             // The the accessibility state of the view may change while temporary
   6690             // detached and we do not allow detached views to fire accessibility
   6691             // events. So we are announcing that the subtree changed giving a chance
   6692             // to clients holding on to a view in this subtree to refresh it.
   6693             notifyViewAccessibilityStateChangedIfNeeded(
   6694                     AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
   6695 
   6696             // Don't scrap views that have transient state.
   6697             final boolean scrapHasTransientState = scrap.hasTransientState();
   6698             if (scrapHasTransientState) {
   6699                 if (mAdapter != null && mAdapterHasStableIds) {
   6700                     // If the adapter has stable IDs, we can reuse the view for
   6701                     // the same data.
   6702                     if (mTransientStateViewsById == null) {
   6703                         mTransientStateViewsById = new LongSparseArray<View>();
   6704                     }
   6705                     mTransientStateViewsById.put(lp.itemId, scrap);
   6706                 } else if (!mDataChanged) {
   6707                     // If the data hasn't changed, we can reuse the views at
   6708                     // their old positions.
   6709                     if (mTransientStateViews == null) {
   6710                         mTransientStateViews = new SparseArray<View>();
   6711                     }
   6712                     mTransientStateViews.put(position, scrap);
   6713                 } else {
   6714                     // Otherwise, we'll have to remove the view and start over.
   6715                     if (mSkippedScrap == null) {
   6716                         mSkippedScrap = new ArrayList<View>();
   6717                     }
   6718                     mSkippedScrap.add(scrap);
   6719                 }
   6720             } else {
   6721                 if (mViewTypeCount == 1) {
   6722                     mCurrentScrap.add(scrap);
   6723                 } else {
   6724                     mScrapViews[viewType].add(scrap);
   6725                 }
   6726 
   6727                 // Clear any system-managed transient state.
   6728                 if (scrap.isAccessibilityFocused()) {
   6729                     scrap.clearAccessibilityFocus();
   6730                 }
   6731 
   6732                 scrap.setAccessibilityDelegate(null);
   6733 
   6734                 if (mRecyclerListener != null) {
   6735                     mRecyclerListener.onMovedToScrapHeap(scrap);
   6736                 }
   6737             }
   6738         }
   6739 
   6740         /**
   6741          * Finish the removal of any views that skipped the scrap heap.
   6742          */
   6743         void removeSkippedScrap() {
   6744             if (mSkippedScrap == null) {
   6745                 return;
   6746             }
   6747             final int count = mSkippedScrap.size();
   6748             for (int i = 0; i < count; i++) {
   6749                 removeDetachedView(mSkippedScrap.get(i), false);
   6750             }
   6751             mSkippedScrap.clear();
   6752         }
   6753 
   6754         /**
   6755          * Move all views remaining in mActiveViews to mScrapViews.
   6756          */
   6757         void scrapActiveViews() {
   6758             final View[] activeViews = mActiveViews;
   6759             final boolean hasListener = mRecyclerListener != null;
   6760             final boolean multipleScraps = mViewTypeCount > 1;
   6761 
   6762             ArrayList<View> scrapViews = mCurrentScrap;
   6763             final int count = activeViews.length;
   6764             for (int i = count - 1; i >= 0; i--) {
   6765                 final View victim = activeViews[i];
   6766                 if (victim != null) {
   6767                     final AbsListView.LayoutParams lp
   6768                             = (AbsListView.LayoutParams) victim.getLayoutParams();
   6769                     int whichScrap = lp.viewType;
   6770 
   6771                     activeViews[i] = null;
   6772 
   6773                     final boolean scrapHasTransientState = victim.hasTransientState();
   6774                     if (!shouldRecycleViewType(whichScrap) || scrapHasTransientState) {
   6775                         // Do not move views that should be ignored
   6776                         if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER &&
   6777                                 scrapHasTransientState) {
   6778                             removeDetachedView(victim, false);
   6779                         }
   6780                         if (scrapHasTransientState) {
   6781                             if (mAdapter != null && mAdapterHasStableIds) {
   6782                                 if (mTransientStateViewsById == null) {
   6783                                     mTransientStateViewsById = new LongSparseArray<View>();
   6784                                 }
   6785                                 long id = mAdapter.getItemId(mFirstActivePosition + i);
   6786                                 mTransientStateViewsById.put(id, victim);
   6787                             } else {
   6788                                 if (mTransientStateViews == null) {
   6789                                     mTransientStateViews = new SparseArray<View>();
   6790                                 }
   6791                                 mTransientStateViews.put(mFirstActivePosition + i, victim);
   6792                             }
   6793                         }
   6794                         continue;
   6795                     }
   6796 
   6797                     if (multipleScraps) {
   6798                         scrapViews = mScrapViews[whichScrap];
   6799                     }
   6800                     victim.dispatchStartTemporaryDetach();
   6801                     lp.scrappedFromPosition = mFirstActivePosition + i;
   6802                     scrapViews.add(victim);
   6803 
   6804                     victim.setAccessibilityDelegate(null);
   6805                     if (hasListener) {
   6806                         mRecyclerListener.onMovedToScrapHeap(victim);
   6807                     }
   6808                 }
   6809             }
   6810 
   6811             pruneScrapViews();
   6812         }
   6813 
   6814         /**
   6815          * Makes sure that the size of mScrapViews does not exceed the size of mActiveViews.
   6816          * (This can happen if an adapter does not recycle its views).
   6817          */
   6818         private void pruneScrapViews() {
   6819             final int maxViews = mActiveViews.length;
   6820             final int viewTypeCount = mViewTypeCount;
   6821             final ArrayList<View>[] scrapViews = mScrapViews;
   6822             for (int i = 0; i < viewTypeCount; ++i) {
   6823                 final ArrayList<View> scrapPile = scrapViews[i];
   6824                 int size = scrapPile.size();
   6825                 final int extras = size - maxViews;
   6826                 size--;
   6827                 for (int j = 0; j < extras; j++) {
   6828                     removeDetachedView(scrapPile.remove(size--), false);
   6829                 }
   6830             }
   6831 
   6832             if (mTransientStateViews != null) {
   6833                 for (int i = 0; i < mTransientStateViews.size(); i++) {
   6834                     final View v = mTransientStateViews.valueAt(i);
   6835                     if (!v.hasTransientState()) {
   6836                         mTransientStateViews.removeAt(i);
   6837                         i--;
   6838                     }
   6839                 }
   6840             }
   6841             if (mTransientStateViewsById != null) {
   6842                 for (int i = 0; i < mTransientStateViewsById.size(); i++) {
   6843                     final View v = mTransientStateViewsById.valueAt(i);
   6844                     if (!v.hasTransientState()) {
   6845                         mTransientStateViewsById.removeAt(i);
   6846                         i--;
   6847                     }
   6848                 }
   6849             }
   6850         }
   6851 
   6852         /**
   6853          * Puts all views in the scrap heap into the supplied list.
   6854          */
   6855         void reclaimScrapViews(List<View> views) {
   6856             if (mViewTypeCount == 1) {
   6857                 views.addAll(mCurrentScrap);
   6858             } else {
   6859                 final int viewTypeCount = mViewTypeCount;
   6860                 final ArrayList<View>[] scrapViews = mScrapViews;
   6861                 for (int i = 0; i < viewTypeCount; ++i) {
   6862                     final ArrayList<View> scrapPile = scrapViews[i];
   6863                     views.addAll(scrapPile);
   6864                 }
   6865             }
   6866         }
   6867 
   6868         /**
   6869          * Updates the cache color hint of all known views.
   6870          *
   6871          * @param color The new cache color hint.
   6872          */
   6873         void setCacheColorHint(int color) {
   6874             if (mViewTypeCount == 1) {
   6875                 final ArrayList<View> scrap = mCurrentScrap;
   6876                 final int scrapCount = scrap.size();
   6877                 for (int i = 0; i < scrapCount; i++) {
   6878                     scrap.get(i).setDrawingCacheBackgroundColor(color);
   6879                 }
   6880             } else {
   6881                 final int typeCount = mViewTypeCount;
   6882                 for (int i = 0; i < typeCount; i++) {
   6883                     final ArrayList<View> scrap = mScrapViews[i];
   6884                     final int scrapCount = scrap.size();
   6885                     for (int j = 0; j < scrapCount; j++) {
   6886                         scrap.get(j).setDrawingCacheBackgroundColor(color);
   6887                     }
   6888                 }
   6889             }
   6890             // Just in case this is called during a layout pass
   6891             final View[] activeViews = mActiveViews;
   6892             final int count = activeViews.length;
   6893             for (int i = 0; i < count; ++i) {
   6894                 final View victim = activeViews[i];
   6895                 if (victim != null) {
   6896                     victim.setDrawingCacheBackgroundColor(color);
   6897                 }
   6898             }
   6899         }
   6900     }
   6901 
   6902     static View retrieveFromScrap(ArrayList<View> scrapViews, int position) {
   6903         int size = scrapViews.size();
   6904         if (size > 0) {
   6905             // See if we still have a view for this position.
   6906             for (int i=0; i<size; i++) {
   6907                 View view = scrapViews.get(i);
   6908                 if (((AbsListView.LayoutParams)view.getLayoutParams())
   6909                         .scrappedFromPosition == position) {
   6910                     scrapViews.remove(i);
   6911                     return view;
   6912                 }
   6913             }
   6914             return scrapViews.remove(size - 1);
   6915         } else {
   6916             return null;
   6917         }
   6918     }
   6919 }
   6920