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