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