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