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