Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2006 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.widget;
     18 
     19 import com.android.internal.R;
     20 
     21 import android.content.Context;
     22 import android.content.res.Resources;
     23 import android.content.res.TypedArray;
     24 import android.graphics.Canvas;
     25 import android.graphics.Rect;
     26 import android.graphics.drawable.Drawable;
     27 import android.graphics.drawable.TransitionDrawable;
     28 import android.os.Debug;
     29 import android.os.Handler;
     30 import android.os.Parcel;
     31 import android.os.Parcelable;
     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.view.ContextMenu.ContextMenuInfo;
     38 import android.view.Gravity;
     39 import android.view.HapticFeedbackConstants;
     40 import android.view.KeyEvent;
     41 import android.view.LayoutInflater;
     42 import android.view.MotionEvent;
     43 import android.view.VelocityTracker;
     44 import android.view.View;
     45 import android.view.ViewConfiguration;
     46 import android.view.ViewDebug;
     47 import android.view.ViewGroup;
     48 import android.view.ViewTreeObserver;
     49 import android.view.animation.AnimationUtils;
     50 import android.view.inputmethod.BaseInputConnection;
     51 import android.view.inputmethod.EditorInfo;
     52 import android.view.inputmethod.InputConnection;
     53 import android.view.inputmethod.InputConnectionWrapper;
     54 import android.view.inputmethod.InputMethodManager;
     55 
     56 import java.util.ArrayList;
     57 import java.util.List;
     58 
     59 /**
     60  * Base class that can be used to implement virtualized lists of items. A list does
     61  * not have a spatial definition here. For instance, subclases of this class can
     62  * display the content of the list in a grid, in a carousel, as stack, etc.
     63  *
     64  * @attr ref android.R.styleable#AbsListView_listSelector
     65  * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop
     66  * @attr ref android.R.styleable#AbsListView_stackFromBottom
     67  * @attr ref android.R.styleable#AbsListView_scrollingCache
     68  * @attr ref android.R.styleable#AbsListView_textFilterEnabled
     69  * @attr ref android.R.styleable#AbsListView_transcriptMode
     70  * @attr ref android.R.styleable#AbsListView_cacheColorHint
     71  * @attr ref android.R.styleable#AbsListView_fastScrollEnabled
     72  * @attr ref android.R.styleable#AbsListView_smoothScrollbar
     73  */
     74 public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher,
     75         ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener,
     76         ViewTreeObserver.OnTouchModeChangeListener {
     77 
     78     /**
     79      * Disables the transcript mode.
     80      *
     81      * @see #setTranscriptMode(int)
     82      */
     83     public static final int TRANSCRIPT_MODE_DISABLED = 0;
     84     /**
     85      * The list will automatically scroll to the bottom when a data set change
     86      * notification is received and only if the last item is already visible
     87      * on screen.
     88      *
     89      * @see #setTranscriptMode(int)
     90      */
     91     public static final int TRANSCRIPT_MODE_NORMAL = 1;
     92     /**
     93      * The list will automatically scroll to the bottom, no matter what items
     94      * are currently visible.
     95      *
     96      * @see #setTranscriptMode(int)
     97      */
     98     public static final int TRANSCRIPT_MODE_ALWAYS_SCROLL = 2;
     99 
    100     /**
    101      * Indicates that we are not in the middle of a touch gesture
    102      */
    103     static final int TOUCH_MODE_REST = -1;
    104 
    105     /**
    106      * Indicates we just received the touch event and we are waiting to see if the it is a tap or a
    107      * scroll gesture.
    108      */
    109     static final int TOUCH_MODE_DOWN = 0;
    110 
    111     /**
    112      * Indicates the touch has been recognized as a tap and we are now waiting to see if the touch
    113      * is a longpress
    114      */
    115     static final int TOUCH_MODE_TAP = 1;
    116 
    117     /**
    118      * Indicates we have waited for everything we can wait for, but the user's finger is still down
    119      */
    120     static final int TOUCH_MODE_DONE_WAITING = 2;
    121 
    122     /**
    123      * Indicates the touch gesture is a scroll
    124      */
    125     static final int TOUCH_MODE_SCROLL = 3;
    126 
    127     /**
    128      * Indicates the view is in the process of being flung
    129      */
    130     static final int TOUCH_MODE_FLING = 4;
    131 
    132     /**
    133      * Indicates the touch gesture is an overscroll - a scroll beyond the beginning or end.
    134      */
    135     static final int TOUCH_MODE_OVERSCROLL = 5;
    136 
    137     /**
    138      * Indicates the view is being flung outside of normal content bounds
    139      * and will spring back.
    140      */
    141     static final int TOUCH_MODE_OVERFLING = 6;
    142 
    143     /**
    144      * Regular layout - usually an unsolicited layout from the view system
    145      */
    146     static final int LAYOUT_NORMAL = 0;
    147 
    148     /**
    149      * Show the first item
    150      */
    151     static final int LAYOUT_FORCE_TOP = 1;
    152 
    153     /**
    154      * Force the selected item to be on somewhere on the screen
    155      */
    156     static final int LAYOUT_SET_SELECTION = 2;
    157 
    158     /**
    159      * Show the last item
    160      */
    161     static final int LAYOUT_FORCE_BOTTOM = 3;
    162 
    163     /**
    164      * Make a mSelectedItem appear in a specific location and build the rest of
    165      * the views from there. The top is specified by mSpecificTop.
    166      */
    167     static final int LAYOUT_SPECIFIC = 4;
    168 
    169     /**
    170      * Layout to sync as a result of a data change. Restore mSyncPosition to have its top
    171      * at mSpecificTop
    172      */
    173     static final int LAYOUT_SYNC = 5;
    174 
    175     /**
    176      * Layout as a result of using the navigation keys
    177      */
    178     static final int LAYOUT_MOVE_SELECTION = 6;
    179 
    180     /**
    181      * Controls how the next layout will happen
    182      */
    183     int mLayoutMode = LAYOUT_NORMAL;
    184 
    185     /**
    186      * Should be used by subclasses to listen to changes in the dataset
    187      */
    188     AdapterDataSetObserver mDataSetObserver;
    189 
    190     /**
    191      * The adapter containing the data to be displayed by this view
    192      */
    193     ListAdapter mAdapter;
    194 
    195     /**
    196      * Indicates whether the list selector should be drawn on top of the children or behind
    197      */
    198     boolean mDrawSelectorOnTop = false;
    199 
    200     /**
    201      * The drawable used to draw the selector
    202      */
    203     Drawable mSelector;
    204 
    205     /**
    206      * Defines the selector's location and dimension at drawing time
    207      */
    208     Rect mSelectorRect = new Rect();
    209 
    210     /**
    211      * The data set used to store unused views that should be reused during the next layout
    212      * to avoid creating new ones
    213      */
    214     final RecycleBin mRecycler = new RecycleBin();
    215 
    216     /**
    217      * The selection's left padding
    218      */
    219     int mSelectionLeftPadding = 0;
    220 
    221     /**
    222      * The selection's top padding
    223      */
    224     int mSelectionTopPadding = 0;
    225 
    226     /**
    227      * The selection's right padding
    228      */
    229     int mSelectionRightPadding = 0;
    230 
    231     /**
    232      * The selection's bottom padding
    233      */
    234     int mSelectionBottomPadding = 0;
    235 
    236     /**
    237      * This view's padding
    238      */
    239     Rect mListPadding = new Rect();
    240 
    241     /**
    242      * Subclasses must retain their measure spec from onMeasure() into this member
    243      */
    244     int mWidthMeasureSpec = 0;
    245 
    246     /**
    247      * The top scroll indicator
    248      */
    249     View mScrollUp;
    250 
    251     /**
    252      * The down scroll indicator
    253      */
    254     View mScrollDown;
    255 
    256     /**
    257      * When the view is scrolling, this flag is set to true to indicate subclasses that
    258      * the drawing cache was enabled on the children
    259      */
    260     boolean mCachingStarted;
    261 
    262     /**
    263      * The position of the view that received the down motion event
    264      */
    265     int mMotionPosition;
    266 
    267     /**
    268      * The offset to the top of the mMotionPosition view when the down motion event was received
    269      */
    270     int mMotionViewOriginalTop;
    271 
    272     /**
    273      * The desired offset to the top of the mMotionPosition view after a scroll
    274      */
    275     int mMotionViewNewTop;
    276 
    277     /**
    278      * The X value associated with the the down motion event
    279      */
    280     int mMotionX;
    281 
    282     /**
    283      * The Y value associated with the the down motion event
    284      */
    285     int mMotionY;
    286 
    287     /**
    288      * One of TOUCH_MODE_REST, TOUCH_MODE_DOWN, TOUCH_MODE_TAP, TOUCH_MODE_SCROLL, or
    289      * TOUCH_MODE_DONE_WAITING
    290      */
    291     int mTouchMode = TOUCH_MODE_REST;
    292 
    293     /**
    294      * Y value from on the previous motion event (if any)
    295      */
    296     int mLastY;
    297 
    298     /**
    299      * How far the finger moved before we started scrolling
    300      */
    301     int mMotionCorrection;
    302 
    303     /**
    304      * Determines speed during touch scrolling
    305      */
    306     private VelocityTracker mVelocityTracker;
    307 
    308     /**
    309      * Handles one frame of a fling
    310      */
    311     private FlingRunnable mFlingRunnable;
    312 
    313     /**
    314      * Handles scrolling between positions within the list.
    315      */
    316     private PositionScroller mPositionScroller;
    317 
    318     /**
    319      * The offset in pixels form the top of the AdapterView to the top
    320      * of the currently selected view. Used to save and restore state.
    321      */
    322     int mSelectedTop = 0;
    323 
    324     /**
    325      * Indicates whether the list is stacked from the bottom edge or
    326      * the top edge.
    327      */
    328     boolean mStackFromBottom;
    329 
    330     /**
    331      * When set to true, the list automatically discards the children's
    332      * bitmap cache after scrolling.
    333      */
    334     boolean mScrollingCacheEnabled;
    335 
    336     /**
    337      * Whether or not to enable the fast scroll feature on this list
    338      */
    339     boolean mFastScrollEnabled;
    340 
    341     /**
    342      * Optional callback to notify client when scroll position has changed
    343      */
    344     private OnScrollListener mOnScrollListener;
    345 
    346     /**
    347      * Keeps track of our accessory window
    348      */
    349     PopupWindow mPopup;
    350 
    351     /**
    352      * Used with type filter window
    353      */
    354     EditText mTextFilter;
    355 
    356     /**
    357      * Indicates whether to use pixels-based or position-based scrollbar
    358      * properties.
    359      */
    360     private boolean mSmoothScrollbarEnabled = true;
    361 
    362     /**
    363      * Indicates that this view supports filtering
    364      */
    365     private boolean mTextFilterEnabled;
    366 
    367     /**
    368      * Indicates that this view is currently displaying a filtered view of the data
    369      */
    370     private boolean mFiltered;
    371 
    372     /**
    373      * Rectangle used for hit testing children
    374      */
    375     private Rect mTouchFrame;
    376 
    377     /**
    378      * The position to resurrect the selected position to.
    379      */
    380     int mResurrectToPosition = INVALID_POSITION;
    381 
    382     private ContextMenuInfo mContextMenuInfo = null;
    383 
    384     /**
    385      * Maximum distance to record overscroll
    386      */
    387     int mOverscrollMax;
    388 
    389     /**
    390      * Content height divided by this is the overscroll limit.
    391      */
    392     static final int OVERSCROLL_LIMIT_DIVISOR = 3;
    393 
    394     /**
    395      * Used to request a layout when we changed touch mode
    396      */
    397     private static final int TOUCH_MODE_UNKNOWN = -1;
    398     private static final int TOUCH_MODE_ON = 0;
    399     private static final int TOUCH_MODE_OFF = 1;
    400 
    401     private int mLastTouchMode = TOUCH_MODE_UNKNOWN;
    402 
    403     private static final boolean PROFILE_SCROLLING = false;
    404     private boolean mScrollProfilingStarted = false;
    405 
    406     private static final boolean PROFILE_FLINGING = false;
    407     private boolean mFlingProfilingStarted = false;
    408 
    409     /**
    410      * The last CheckForLongPress runnable we posted, if any
    411      */
    412     private CheckForLongPress mPendingCheckForLongPress;
    413 
    414     /**
    415      * The last CheckForTap runnable we posted, if any
    416      */
    417     private Runnable mPendingCheckForTap;
    418 
    419     /**
    420      * The last CheckForKeyLongPress runnable we posted, if any
    421      */
    422     private CheckForKeyLongPress mPendingCheckForKeyLongPress;
    423 
    424     /**
    425      * Acts upon click
    426      */
    427     private AbsListView.PerformClick mPerformClick;
    428 
    429     /**
    430      * This view is in transcript mode -- it shows the bottom of the list when the data
    431      * changes
    432      */
    433     private int mTranscriptMode;
    434 
    435     /**
    436      * Indicates that this list is always drawn on top of a solid, single-color, opaque
    437      * background
    438      */
    439     private int mCacheColorHint;
    440 
    441     /**
    442      * The select child's view (from the adapter's getView) is enabled.
    443      */
    444     private boolean mIsChildViewEnabled;
    445 
    446     /**
    447      * The last scroll state reported to clients through {@link OnScrollListener}.
    448      */
    449     private int mLastScrollState = OnScrollListener.SCROLL_STATE_IDLE;
    450 
    451     /**
    452      * Helper object that renders and controls the fast scroll thumb.
    453      */
    454     private FastScroller mFastScroller;
    455 
    456     private boolean mGlobalLayoutListenerAddedFilter;
    457 
    458     private int mTouchSlop;
    459     private float mDensityScale;
    460 
    461     private InputConnection mDefInputConnection;
    462     private InputConnectionWrapper mPublicInputConnection;
    463 
    464     private Runnable mClearScrollingCache;
    465     private int mMinimumVelocity;
    466     private int mMaximumVelocity;
    467 
    468     final boolean[] mIsScrap = new boolean[1];
    469 
    470     // True when the popup should be hidden because of a call to
    471     // dispatchDisplayHint()
    472     private boolean mPopupHidden;
    473 
    474     /**
    475      * ID of the active pointer. This is used to retain consistency during
    476      * drags/flings if multiple pointers are used.
    477      */
    478     private int mActivePointerId = INVALID_POINTER;
    479 
    480     /**
    481      * Sentinel value for no current active pointer.
    482      * Used by {@link #mActivePointerId}.
    483      */
    484     private static final int INVALID_POINTER = -1;
    485 
    486     /**
    487      * Maximum distance to overscroll by during edge effects
    488      */
    489     int mOverscrollDistance;
    490 
    491     /**
    492      * Maximum distance to overfling during edge effects
    493      */
    494     int mOverflingDistance;
    495 
    496     // These two EdgeGlows are always set and used together.
    497     // Checking one for null is as good as checking both.
    498 
    499     /**
    500      * Tracks the state of the top edge glow.
    501      */
    502     private EdgeGlow mEdgeGlowTop;
    503 
    504     /**
    505      * Tracks the state of the bottom edge glow.
    506      */
    507     private EdgeGlow mEdgeGlowBottom;
    508 
    509     /**
    510      * An estimate of how many pixels are between the top of the list and
    511      * the top of the first position in the adapter, based on the last time
    512      * we saw it. Used to hint where to draw edge glows.
    513      */
    514     private int mFirstPositionDistanceGuess;
    515 
    516     /**
    517      * An estimate of how many pixels are between the bottom of the list and
    518      * the bottom of the last position in the adapter, based on the last time
    519      * we saw it. Used to hint where to draw edge glows.
    520      */
    521     private int mLastPositionDistanceGuess;
    522 
    523     /**
    524      * Used for determining when to cancel out of overscroll.
    525      */
    526     private int mDirection = 0;
    527 
    528     /**
    529      * Interface definition for a callback to be invoked when the list or grid
    530      * has been scrolled.
    531      */
    532     public interface OnScrollListener {
    533 
    534         /**
    535          * The view is not scrolling. Note navigating the list using the trackball counts as
    536          * being in the idle state since these transitions are not animated.
    537          */
    538         public static int SCROLL_STATE_IDLE = 0;
    539 
    540         /**
    541          * The user is scrolling using touch, and their finger is still on the screen
    542          */
    543         public static int SCROLL_STATE_TOUCH_SCROLL = 1;
    544 
    545         /**
    546          * The user had previously been scrolling using touch and had performed a fling. The
    547          * animation is now coasting to a stop
    548          */
    549         public static int SCROLL_STATE_FLING = 2;
    550 
    551         /**
    552          * Callback method to be invoked while the list view or grid view is being scrolled. If the
    553          * view is being scrolled, this method will be called before the next frame of the scroll is
    554          * rendered. In particular, it will be called before any calls to
    555          * {@link Adapter#getView(int, View, ViewGroup)}.
    556          *
    557          * @param view The view whose scroll state is being reported
    558          *
    559          * @param scrollState The current scroll state. One of {@link #SCROLL_STATE_IDLE},
    560          * {@link #SCROLL_STATE_TOUCH_SCROLL} or {@link #SCROLL_STATE_IDLE}.
    561          */
    562         public void onScrollStateChanged(AbsListView view, int scrollState);
    563 
    564         /**
    565          * Callback method to be invoked when the list or grid has been scrolled. This will be
    566          * called after the scroll has completed
    567          * @param view The view whose scroll state is being reported
    568          * @param firstVisibleItem the index of the first visible cell (ignore if
    569          *        visibleItemCount == 0)
    570          * @param visibleItemCount the number of visible cells
    571          * @param totalItemCount the number of items in the list adaptor
    572          */
    573         public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
    574                 int totalItemCount);
    575     }
    576 
    577     public AbsListView(Context context) {
    578         super(context);
    579         initAbsListView();
    580 
    581         setVerticalScrollBarEnabled(true);
    582         TypedArray a = context.obtainStyledAttributes(R.styleable.View);
    583         initializeScrollbars(a);
    584         a.recycle();
    585     }
    586 
    587     public AbsListView(Context context, AttributeSet attrs) {
    588         this(context, attrs, com.android.internal.R.attr.absListViewStyle);
    589     }
    590 
    591     public AbsListView(Context context, AttributeSet attrs, int defStyle) {
    592         super(context, attrs, defStyle);
    593         initAbsListView();
    594 
    595         TypedArray a = context.obtainStyledAttributes(attrs,
    596                 com.android.internal.R.styleable.AbsListView, defStyle, 0);
    597 
    598         Drawable d = a.getDrawable(com.android.internal.R.styleable.AbsListView_listSelector);
    599         if (d != null) {
    600             setSelector(d);
    601         }
    602 
    603         mDrawSelectorOnTop = a.getBoolean(
    604                 com.android.internal.R.styleable.AbsListView_drawSelectorOnTop, false);
    605 
    606         boolean stackFromBottom = a.getBoolean(R.styleable.AbsListView_stackFromBottom, false);
    607         setStackFromBottom(stackFromBottom);
    608 
    609         boolean scrollingCacheEnabled = a.getBoolean(R.styleable.AbsListView_scrollingCache, true);
    610         setScrollingCacheEnabled(scrollingCacheEnabled);
    611 
    612         boolean useTextFilter = a.getBoolean(R.styleable.AbsListView_textFilterEnabled, false);
    613         setTextFilterEnabled(useTextFilter);
    614 
    615         int transcriptMode = a.getInt(R.styleable.AbsListView_transcriptMode,
    616                 TRANSCRIPT_MODE_DISABLED);
    617         setTranscriptMode(transcriptMode);
    618 
    619         int color = a.getColor(R.styleable.AbsListView_cacheColorHint, 0);
    620         setCacheColorHint(color);
    621 
    622         boolean enableFastScroll = a.getBoolean(R.styleable.AbsListView_fastScrollEnabled, false);
    623         setFastScrollEnabled(enableFastScroll);
    624 
    625         boolean smoothScrollbar = a.getBoolean(R.styleable.AbsListView_smoothScrollbar, true);
    626         setSmoothScrollbarEnabled(smoothScrollbar);
    627 
    628         a.recycle();
    629     }
    630 
    631     private void initAbsListView() {
    632         // Setting focusable in touch mode will set the focusable property to true
    633         setClickable(true);
    634         setFocusableInTouchMode(true);
    635         setWillNotDraw(false);
    636         setAlwaysDrawnWithCacheEnabled(false);
    637         setScrollingCacheEnabled(true);
    638 
    639         final ViewConfiguration configuration = ViewConfiguration.get(mContext);
    640         mTouchSlop = configuration.getScaledTouchSlop();
    641         mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
    642         mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
    643         mOverscrollDistance = configuration.getScaledOverscrollDistance();
    644         mOverflingDistance = configuration.getScaledOverflingDistance();
    645 
    646         mDensityScale = getContext().getResources().getDisplayMetrics().density;
    647     }
    648 
    649     @Override
    650     public void setOverScrollMode(int mode) {
    651         if (mode != OVER_SCROLL_NEVER) {
    652             if (mEdgeGlowTop == null) {
    653                 final Resources res = getContext().getResources();
    654                 final Drawable edge = res.getDrawable(R.drawable.overscroll_edge);
    655                 final Drawable glow = res.getDrawable(R.drawable.overscroll_glow);
    656                 mEdgeGlowTop = new EdgeGlow(edge, glow);
    657                 mEdgeGlowBottom = new EdgeGlow(edge, glow);
    658             }
    659         } else {
    660             mEdgeGlowTop = null;
    661             mEdgeGlowBottom = null;
    662         }
    663         super.setOverScrollMode(mode);
    664     }
    665 
    666     /**
    667      * @return true if all list content currently fits within the view boundaries
    668      */
    669     private boolean contentFits() {
    670         final int childCount = getChildCount();
    671         if (childCount != mItemCount) {
    672             return false;
    673         }
    674 
    675         return getChildAt(0).getTop() >= 0 && getChildAt(childCount - 1).getBottom() <= mBottom;
    676     }
    677 
    678     /**
    679      * Enables fast scrolling by letting the user quickly scroll through lists by
    680      * dragging the fast scroll thumb. The adapter attached to the list may want
    681      * to implement {@link SectionIndexer} if it wishes to display alphabet preview and
    682      * jump between sections of the list.
    683      * @see SectionIndexer
    684      * @see #isFastScrollEnabled()
    685      * @param enabled whether or not to enable fast scrolling
    686      */
    687     public void setFastScrollEnabled(boolean enabled) {
    688         mFastScrollEnabled = enabled;
    689         if (enabled) {
    690             if (mFastScroller == null) {
    691                 mFastScroller = new FastScroller(getContext(), this);
    692             }
    693         } else {
    694             if (mFastScroller != null) {
    695                 mFastScroller.stop();
    696                 mFastScroller = null;
    697             }
    698         }
    699     }
    700 
    701     /**
    702      * Returns the current state of the fast scroll feature.
    703      * @see #setFastScrollEnabled(boolean)
    704      * @return true if fast scroll is enabled, false otherwise
    705      */
    706     @ViewDebug.ExportedProperty
    707     public boolean isFastScrollEnabled() {
    708         return mFastScrollEnabled;
    709     }
    710 
    711     /**
    712      * If fast scroll is visible, then don't draw the vertical scrollbar.
    713      * @hide
    714      */
    715     @Override
    716     protected boolean isVerticalScrollBarHidden() {
    717         return mFastScroller != null && mFastScroller.isVisible();
    718     }
    719 
    720     /**
    721      * When smooth scrollbar is enabled, the position and size of the scrollbar thumb
    722      * is computed based on the number of visible pixels in the visible items. This
    723      * however assumes that all list items have the same height. If you use a list in
    724      * which items have different heights, the scrollbar will change appearance as the
    725      * user scrolls through the list. To avoid this issue, you need to disable this
    726      * property.
    727      *
    728      * When smooth scrollbar is disabled, the position and size of the scrollbar thumb
    729      * is based solely on the number of items in the adapter and the position of the
    730      * visible items inside the adapter. This provides a stable scrollbar as the user
    731      * navigates through a list of items with varying heights.
    732      *
    733      * @param enabled Whether or not to enable smooth scrollbar.
    734      *
    735      * @see #setSmoothScrollbarEnabled(boolean)
    736      * @attr ref android.R.styleable#AbsListView_smoothScrollbar
    737      */
    738     public void setSmoothScrollbarEnabled(boolean enabled) {
    739         mSmoothScrollbarEnabled = enabled;
    740     }
    741 
    742     /**
    743      * Returns the current state of the fast scroll feature.
    744      *
    745      * @return True if smooth scrollbar is enabled is enabled, false otherwise.
    746      *
    747      * @see #setSmoothScrollbarEnabled(boolean)
    748      */
    749     @ViewDebug.ExportedProperty
    750     public boolean isSmoothScrollbarEnabled() {
    751         return mSmoothScrollbarEnabled;
    752     }
    753 
    754     /**
    755      * Set the listener that will receive notifications every time the list scrolls.
    756      *
    757      * @param l the scroll listener
    758      */
    759     public void setOnScrollListener(OnScrollListener l) {
    760         mOnScrollListener = l;
    761         invokeOnItemScrollListener();
    762     }
    763 
    764     /**
    765      * Notify our scroll listener (if there is one) of a change in scroll state
    766      */
    767     void invokeOnItemScrollListener() {
    768         if (mFastScroller != null) {
    769             mFastScroller.onScroll(this, mFirstPosition, getChildCount(), mItemCount);
    770         }
    771         if (mOnScrollListener != null) {
    772             mOnScrollListener.onScroll(this, mFirstPosition, getChildCount(), mItemCount);
    773         }
    774     }
    775 
    776     /**
    777      * Indicates whether the children's drawing cache is used during a scroll.
    778      * By default, the drawing cache is enabled but this will consume more memory.
    779      *
    780      * @return true if the scrolling cache is enabled, false otherwise
    781      *
    782      * @see #setScrollingCacheEnabled(boolean)
    783      * @see View#setDrawingCacheEnabled(boolean)
    784      */
    785     @ViewDebug.ExportedProperty
    786     public boolean isScrollingCacheEnabled() {
    787         return mScrollingCacheEnabled;
    788     }
    789 
    790     /**
    791      * Enables or disables the children's drawing cache during a scroll.
    792      * By default, the drawing cache is enabled but this will use more memory.
    793      *
    794      * When the scrolling cache is enabled, the caches are kept after the
    795      * first scrolling. You can manually clear the cache by calling
    796      * {@link android.view.ViewGroup#setChildrenDrawingCacheEnabled(boolean)}.
    797      *
    798      * @param enabled true to enable the scroll cache, false otherwise
    799      *
    800      * @see #isScrollingCacheEnabled()
    801      * @see View#setDrawingCacheEnabled(boolean)
    802      */
    803     public void setScrollingCacheEnabled(boolean enabled) {
    804         if (mScrollingCacheEnabled && !enabled) {
    805             clearScrollingCache();
    806         }
    807         mScrollingCacheEnabled = enabled;
    808     }
    809 
    810     /**
    811      * Enables or disables the type filter window. If enabled, typing when
    812      * this view has focus will filter the children to match the users input.
    813      * Note that the {@link Adapter} used by this view must implement the
    814      * {@link Filterable} interface.
    815      *
    816      * @param textFilterEnabled true to enable type filtering, false otherwise
    817      *
    818      * @see Filterable
    819      */
    820     public void setTextFilterEnabled(boolean textFilterEnabled) {
    821         mTextFilterEnabled = textFilterEnabled;
    822     }
    823 
    824     /**
    825      * Indicates whether type filtering is enabled for this view
    826      *
    827      * @return true if type filtering is enabled, false otherwise
    828      *
    829      * @see #setTextFilterEnabled(boolean)
    830      * @see Filterable
    831      */
    832     @ViewDebug.ExportedProperty
    833     public boolean isTextFilterEnabled() {
    834         return mTextFilterEnabled;
    835     }
    836 
    837     @Override
    838     public void getFocusedRect(Rect r) {
    839         View view = getSelectedView();
    840         if (view != null && view.getParent() == this) {
    841             // the focused rectangle of the selected view offset into the
    842             // coordinate space of this view.
    843             view.getFocusedRect(r);
    844             offsetDescendantRectToMyCoords(view, r);
    845         } else {
    846             // otherwise, just the norm
    847             super.getFocusedRect(r);
    848         }
    849     }
    850 
    851     private void useDefaultSelector() {
    852         setSelector(getResources().getDrawable(
    853                 com.android.internal.R.drawable.list_selector_background));
    854     }
    855 
    856     /**
    857      * Indicates whether the content of this view is pinned to, or stacked from,
    858      * the bottom edge.
    859      *
    860      * @return true if the content is stacked from the bottom edge, false otherwise
    861      */
    862     @ViewDebug.ExportedProperty
    863     public boolean isStackFromBottom() {
    864         return mStackFromBottom;
    865     }
    866 
    867     /**
    868      * When stack from bottom is set to true, the list fills its content starting from
    869      * the bottom of the view.
    870      *
    871      * @param stackFromBottom true to pin the view's content to the bottom edge,
    872      *        false to pin the view's content to the top edge
    873      */
    874     public void setStackFromBottom(boolean stackFromBottom) {
    875         if (mStackFromBottom != stackFromBottom) {
    876             mStackFromBottom = stackFromBottom;
    877             requestLayoutIfNecessary();
    878         }
    879     }
    880 
    881     void requestLayoutIfNecessary() {
    882         if (getChildCount() > 0) {
    883             resetList();
    884             requestLayout();
    885             invalidate();
    886         }
    887     }
    888 
    889     static class SavedState extends BaseSavedState {
    890         long selectedId;
    891         long firstId;
    892         int viewTop;
    893         int position;
    894         int height;
    895         String filter;
    896 
    897         /**
    898          * Constructor called from {@link AbsListView#onSaveInstanceState()}
    899          */
    900         SavedState(Parcelable superState) {
    901             super(superState);
    902         }
    903 
    904         /**
    905          * Constructor called from {@link #CREATOR}
    906          */
    907         private SavedState(Parcel in) {
    908             super(in);
    909             selectedId = in.readLong();
    910             firstId = in.readLong();
    911             viewTop = in.readInt();
    912             position = in.readInt();
    913             height = in.readInt();
    914             filter = in.readString();
    915         }
    916 
    917         @Override
    918         public void writeToParcel(Parcel out, int flags) {
    919             super.writeToParcel(out, flags);
    920             out.writeLong(selectedId);
    921             out.writeLong(firstId);
    922             out.writeInt(viewTop);
    923             out.writeInt(position);
    924             out.writeInt(height);
    925             out.writeString(filter);
    926         }
    927 
    928         @Override
    929         public String toString() {
    930             return "AbsListView.SavedState{"
    931                     + Integer.toHexString(System.identityHashCode(this))
    932                     + " selectedId=" + selectedId
    933                     + " firstId=" + firstId
    934                     + " viewTop=" + viewTop
    935                     + " position=" + position
    936                     + " height=" + height
    937                     + " filter=" + filter + "}";
    938         }
    939 
    940         public static final Parcelable.Creator<SavedState> CREATOR
    941                 = new Parcelable.Creator<SavedState>() {
    942             public SavedState createFromParcel(Parcel in) {
    943                 return new SavedState(in);
    944             }
    945 
    946             public SavedState[] newArray(int size) {
    947                 return new SavedState[size];
    948             }
    949         };
    950     }
    951 
    952     @Override
    953     public Parcelable onSaveInstanceState() {
    954         /*
    955          * This doesn't really make sense as the place to dismiss the
    956          * popups, but there don't seem to be any other useful hooks
    957          * that happen early enough to keep from getting complaints
    958          * about having leaked the window.
    959          */
    960         dismissPopup();
    961 
    962         Parcelable superState = super.onSaveInstanceState();
    963 
    964         SavedState ss = new SavedState(superState);
    965 
    966         boolean haveChildren = getChildCount() > 0;
    967         long selectedId = getSelectedItemId();
    968         ss.selectedId = selectedId;
    969         ss.height = getHeight();
    970 
    971         if (selectedId >= 0) {
    972             // Remember the selection
    973             ss.viewTop = mSelectedTop;
    974             ss.position = getSelectedItemPosition();
    975             ss.firstId = INVALID_POSITION;
    976         } else {
    977             if (haveChildren) {
    978                 // Remember the position of the first child
    979                 View v = getChildAt(0);
    980                 ss.viewTop = v.getTop();
    981                 ss.position = mFirstPosition;
    982                 ss.firstId = mAdapter.getItemId(mFirstPosition);
    983             } else {
    984                 ss.viewTop = 0;
    985                 ss.firstId = INVALID_POSITION;
    986                 ss.position = 0;
    987             }
    988         }
    989 
    990         ss.filter = null;
    991         if (mFiltered) {
    992             final EditText textFilter = mTextFilter;
    993             if (textFilter != null) {
    994                 Editable filterText = textFilter.getText();
    995                 if (filterText != null) {
    996                     ss.filter = filterText.toString();
    997                 }
    998             }
    999         }
   1000 
   1001         return ss;
   1002     }
   1003 
   1004     @Override
   1005     public void onRestoreInstanceState(Parcelable state) {
   1006         SavedState ss = (SavedState) state;
   1007 
   1008         super.onRestoreInstanceState(ss.getSuperState());
   1009         mDataChanged = true;
   1010 
   1011         mSyncHeight = ss.height;
   1012 
   1013         if (ss.selectedId >= 0) {
   1014             mNeedSync = true;
   1015             mSyncRowId = ss.selectedId;
   1016             mSyncPosition = ss.position;
   1017             mSpecificTop = ss.viewTop;
   1018             mSyncMode = SYNC_SELECTED_POSITION;
   1019         } else if (ss.firstId >= 0) {
   1020             setSelectedPositionInt(INVALID_POSITION);
   1021             // Do this before setting mNeedSync since setNextSelectedPosition looks at mNeedSync
   1022             setNextSelectedPositionInt(INVALID_POSITION);
   1023             mNeedSync = true;
   1024             mSyncRowId = ss.firstId;
   1025             mSyncPosition = ss.position;
   1026             mSpecificTop = ss.viewTop;
   1027             mSyncMode = SYNC_FIRST_POSITION;
   1028         }
   1029 
   1030         setFilterText(ss.filter);
   1031 
   1032         requestLayout();
   1033     }
   1034 
   1035     private boolean acceptFilter() {
   1036         return mTextFilterEnabled && getAdapter() instanceof Filterable &&
   1037                 ((Filterable) getAdapter()).getFilter() != null;
   1038     }
   1039 
   1040     /**
   1041      * Sets the initial value for the text filter.
   1042      * @param filterText The text to use for the filter.
   1043      *
   1044      * @see #setTextFilterEnabled
   1045      */
   1046     public void setFilterText(String filterText) {
   1047         // TODO: Should we check for acceptFilter()?
   1048         if (mTextFilterEnabled && !TextUtils.isEmpty(filterText)) {
   1049             createTextFilter(false);
   1050             // This is going to call our listener onTextChanged, but we might not
   1051             // be ready to bring up a window yet
   1052             mTextFilter.setText(filterText);
   1053             mTextFilter.setSelection(filterText.length());
   1054             if (mAdapter instanceof Filterable) {
   1055                 // if mPopup is non-null, then onTextChanged will do the filtering
   1056                 if (mPopup == null) {
   1057                     Filter f = ((Filterable) mAdapter).getFilter();
   1058                     f.filter(filterText);
   1059                 }
   1060                 // Set filtered to true so we will display the filter window when our main
   1061                 // window is ready
   1062                 mFiltered = true;
   1063                 mDataSetObserver.clearSavedState();
   1064             }
   1065         }
   1066     }
   1067 
   1068     /**
   1069      * Returns the list's text filter, if available.
   1070      * @return the list's text filter or null if filtering isn't enabled
   1071      */
   1072     public CharSequence getTextFilter() {
   1073         if (mTextFilterEnabled && mTextFilter != null) {
   1074             return mTextFilter.getText();
   1075         }
   1076         return null;
   1077     }
   1078 
   1079     @Override
   1080     protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
   1081         super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
   1082         if (gainFocus && mSelectedPosition < 0 && !isInTouchMode()) {
   1083             resurrectSelection();
   1084         }
   1085     }
   1086 
   1087     @Override
   1088     public void requestLayout() {
   1089         if (!mBlockLayoutRequests && !mInLayout) {
   1090             super.requestLayout();
   1091         }
   1092     }
   1093 
   1094     /**
   1095      * The list is empty. Clear everything out.
   1096      */
   1097     void resetList() {
   1098         removeAllViewsInLayout();
   1099         mFirstPosition = 0;
   1100         mDataChanged = false;
   1101         mNeedSync = false;
   1102         mOldSelectedPosition = INVALID_POSITION;
   1103         mOldSelectedRowId = INVALID_ROW_ID;
   1104         setSelectedPositionInt(INVALID_POSITION);
   1105         setNextSelectedPositionInt(INVALID_POSITION);
   1106         mSelectedTop = 0;
   1107         mSelectorRect.setEmpty();
   1108         invalidate();
   1109     }
   1110 
   1111     @Override
   1112     protected int computeVerticalScrollExtent() {
   1113         final int count = getChildCount();
   1114         if (count > 0) {
   1115             if (mSmoothScrollbarEnabled) {
   1116                 int extent = count * 100;
   1117 
   1118                 View view = getChildAt(0);
   1119                 final int top = view.getTop();
   1120                 int height = view.getHeight();
   1121                 if (height > 0) {
   1122                     extent += (top * 100) / height;
   1123                 }
   1124 
   1125                 view = getChildAt(count - 1);
   1126                 final int bottom = view.getBottom();
   1127                 height = view.getHeight();
   1128                 if (height > 0) {
   1129                     extent -= ((bottom - getHeight()) * 100) / height;
   1130                 }
   1131 
   1132                 return extent;
   1133             } else {
   1134                 return 1;
   1135             }
   1136         }
   1137         return 0;
   1138     }
   1139 
   1140     @Override
   1141     protected int computeVerticalScrollOffset() {
   1142         final int firstPosition = mFirstPosition;
   1143         final int childCount = getChildCount();
   1144         if (firstPosition >= 0 && childCount > 0) {
   1145             if (mSmoothScrollbarEnabled) {
   1146                 final View view = getChildAt(0);
   1147                 final int top = view.getTop();
   1148                 int height = view.getHeight();
   1149                 if (height > 0) {
   1150                     return Math.max(firstPosition * 100 - (top * 100) / height +
   1151                             (int)((float)mScrollY / getHeight() * mItemCount * 100), 0);
   1152                 }
   1153             } else {
   1154                 int index;
   1155                 final int count = mItemCount;
   1156                 if (firstPosition == 0) {
   1157                     index = 0;
   1158                 } else if (firstPosition + childCount == count) {
   1159                     index = count;
   1160                 } else {
   1161                     index = firstPosition + childCount / 2;
   1162                 }
   1163                 return (int) (firstPosition + childCount * (index / (float) count));
   1164             }
   1165         }
   1166         return 0;
   1167     }
   1168 
   1169     @Override
   1170     protected int computeVerticalScrollRange() {
   1171         int result;
   1172         if (mSmoothScrollbarEnabled) {
   1173             result = Math.max(mItemCount * 100, 0);
   1174             if (mScrollY != 0) {
   1175                 // Compensate for overscroll
   1176                 result += Math.abs((int) ((float) mScrollY / getHeight() * mItemCount * 100));
   1177             }
   1178         } else {
   1179             result = mItemCount;
   1180         }
   1181         return result;
   1182     }
   1183 
   1184     @Override
   1185     protected float getTopFadingEdgeStrength() {
   1186         final int count = getChildCount();
   1187         final float fadeEdge = super.getTopFadingEdgeStrength();
   1188         if (count == 0) {
   1189             return fadeEdge;
   1190         } else {
   1191             if (mFirstPosition > 0) {
   1192                 return 1.0f;
   1193             }
   1194 
   1195             final int top = getChildAt(0).getTop();
   1196             final float fadeLength = (float) getVerticalFadingEdgeLength();
   1197             return top < mPaddingTop ? (float) -(top - mPaddingTop) / fadeLength : fadeEdge;
   1198         }
   1199     }
   1200 
   1201     @Override
   1202     protected float getBottomFadingEdgeStrength() {
   1203         final int count = getChildCount();
   1204         final float fadeEdge = super.getBottomFadingEdgeStrength();
   1205         if (count == 0) {
   1206             return fadeEdge;
   1207         } else {
   1208             if (mFirstPosition + count - 1 < mItemCount - 1) {
   1209                 return 1.0f;
   1210             }
   1211 
   1212             final int bottom = getChildAt(count - 1).getBottom();
   1213             final int height = getHeight();
   1214             final float fadeLength = (float) getVerticalFadingEdgeLength();
   1215             return bottom > height - mPaddingBottom ?
   1216                     (float) (bottom - height + mPaddingBottom) / fadeLength : fadeEdge;
   1217         }
   1218     }
   1219 
   1220     @Override
   1221     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
   1222         if (mSelector == null) {
   1223             useDefaultSelector();
   1224         }
   1225         final Rect listPadding = mListPadding;
   1226         listPadding.left = mSelectionLeftPadding + mPaddingLeft;
   1227         listPadding.top = mSelectionTopPadding + mPaddingTop;
   1228         listPadding.right = mSelectionRightPadding + mPaddingRight;
   1229         listPadding.bottom = mSelectionBottomPadding + mPaddingBottom;
   1230     }
   1231 
   1232     /**
   1233      * Subclasses should NOT override this method but
   1234      *  {@link #layoutChildren()} instead.
   1235      */
   1236     @Override
   1237     protected void onLayout(boolean changed, int l, int t, int r, int b) {
   1238         super.onLayout(changed, l, t, r, b);
   1239         mInLayout = true;
   1240         if (changed) {
   1241             int childCount = getChildCount();
   1242             for (int i = 0; i < childCount; i++) {
   1243                 getChildAt(i).forceLayout();
   1244             }
   1245             mRecycler.markChildrenDirty();
   1246         }
   1247 
   1248         layoutChildren();
   1249         mInLayout = false;
   1250 
   1251         mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;
   1252     }
   1253 
   1254     /**
   1255      * @hide
   1256      */
   1257     @Override
   1258     protected boolean setFrame(int left, int top, int right, int bottom) {
   1259         final boolean changed = super.setFrame(left, top, right, bottom);
   1260 
   1261         if (changed) {
   1262             // Reposition the popup when the frame has changed. This includes
   1263             // translating the widget, not just changing its dimension. The
   1264             // filter popup needs to follow the widget.
   1265             final boolean visible = getWindowVisibility() == View.VISIBLE;
   1266             if (mFiltered && visible && mPopup != null && mPopup.isShowing()) {
   1267                 positionPopup();
   1268             }
   1269         }
   1270 
   1271         return changed;
   1272     }
   1273 
   1274     /**
   1275      * Subclasses must override this method to layout their children.
   1276      */
   1277     protected void layoutChildren() {
   1278     }
   1279 
   1280     void updateScrollIndicators() {
   1281         if (mScrollUp != null) {
   1282             boolean canScrollUp;
   1283             // 0th element is not visible
   1284             canScrollUp = mFirstPosition > 0;
   1285 
   1286             // ... Or top of 0th element is not visible
   1287             if (!canScrollUp) {
   1288                 if (getChildCount() > 0) {
   1289                     View child = getChildAt(0);
   1290                     canScrollUp = child.getTop() < mListPadding.top;
   1291                 }
   1292             }
   1293 
   1294             mScrollUp.setVisibility(canScrollUp ? View.VISIBLE : View.INVISIBLE);
   1295         }
   1296 
   1297         if (mScrollDown != null) {
   1298             boolean canScrollDown;
   1299             int count = getChildCount();
   1300 
   1301             // Last item is not visible
   1302             canScrollDown = (mFirstPosition + count) < mItemCount;
   1303 
   1304             // ... Or bottom of the last element is not visible
   1305             if (!canScrollDown && count > 0) {
   1306                 View child = getChildAt(count - 1);
   1307                 canScrollDown = child.getBottom() > mBottom - mListPadding.bottom;
   1308             }
   1309 
   1310             mScrollDown.setVisibility(canScrollDown ? View.VISIBLE : View.INVISIBLE);
   1311         }
   1312     }
   1313 
   1314     @Override
   1315     @ViewDebug.ExportedProperty
   1316     public View getSelectedView() {
   1317         if (mItemCount > 0 && mSelectedPosition >= 0) {
   1318             return getChildAt(mSelectedPosition - mFirstPosition);
   1319         } else {
   1320             return null;
   1321         }
   1322     }
   1323 
   1324     /**
   1325      * List padding is the maximum of the normal view's padding and the padding of the selector.
   1326      *
   1327      * @see android.view.View#getPaddingTop()
   1328      * @see #getSelector()
   1329      *
   1330      * @return The top list padding.
   1331      */
   1332     public int getListPaddingTop() {
   1333         return mListPadding.top;
   1334     }
   1335 
   1336     /**
   1337      * List padding is the maximum of the normal view's padding and the padding of the selector.
   1338      *
   1339      * @see android.view.View#getPaddingBottom()
   1340      * @see #getSelector()
   1341      *
   1342      * @return The bottom list padding.
   1343      */
   1344     public int getListPaddingBottom() {
   1345         return mListPadding.bottom;
   1346     }
   1347 
   1348     /**
   1349      * List padding is the maximum of the normal view's padding and the padding of the selector.
   1350      *
   1351      * @see android.view.View#getPaddingLeft()
   1352      * @see #getSelector()
   1353      *
   1354      * @return The left list padding.
   1355      */
   1356     public int getListPaddingLeft() {
   1357         return mListPadding.left;
   1358     }
   1359 
   1360     /**
   1361      * List padding is the maximum of the normal view's padding and the padding of the selector.
   1362      *
   1363      * @see android.view.View#getPaddingRight()
   1364      * @see #getSelector()
   1365      *
   1366      * @return The right list padding.
   1367      */
   1368     public int getListPaddingRight() {
   1369         return mListPadding.right;
   1370     }
   1371 
   1372     /**
   1373      * Get a view and have it show the data associated with the specified
   1374      * position. This is called when we have already discovered that the view is
   1375      * not available for reuse in the recycle bin. The only choices left are
   1376      * converting an old view or making a new one.
   1377      *
   1378      * @param position The position to display
   1379      * @param isScrap Array of at least 1 boolean, the first entry will become true if
   1380      *                the returned view was taken from the scrap heap, false if otherwise.
   1381      *
   1382      * @return A view displaying the data associated with the specified position
   1383      */
   1384     View obtainView(int position, boolean[] isScrap) {
   1385         isScrap[0] = false;
   1386         View scrapView;
   1387 
   1388         scrapView = mRecycler.getScrapView(position);
   1389 
   1390         View child;
   1391         if (scrapView != null) {
   1392             if (ViewDebug.TRACE_RECYCLER) {
   1393                 ViewDebug.trace(scrapView, ViewDebug.RecyclerTraceType.RECYCLE_FROM_SCRAP_HEAP,
   1394                         position, -1);
   1395             }
   1396 
   1397             child = mAdapter.getView(position, scrapView, this);
   1398 
   1399             if (ViewDebug.TRACE_RECYCLER) {
   1400                 ViewDebug.trace(child, ViewDebug.RecyclerTraceType.BIND_VIEW,
   1401                         position, getChildCount());
   1402             }
   1403 
   1404             if (child != scrapView) {
   1405                 mRecycler.addScrapView(scrapView);
   1406                 if (mCacheColorHint != 0) {
   1407                     child.setDrawingCacheBackgroundColor(mCacheColorHint);
   1408                 }
   1409                 if (ViewDebug.TRACE_RECYCLER) {
   1410                     ViewDebug.trace(scrapView, ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP,
   1411                             position, -1);
   1412                 }
   1413             } else {
   1414                 isScrap[0] = true;
   1415                 child.dispatchFinishTemporaryDetach();
   1416             }
   1417         } else {
   1418             child = mAdapter.getView(position, null, this);
   1419             if (mCacheColorHint != 0) {
   1420                 child.setDrawingCacheBackgroundColor(mCacheColorHint);
   1421             }
   1422             if (ViewDebug.TRACE_RECYCLER) {
   1423                 ViewDebug.trace(child, ViewDebug.RecyclerTraceType.NEW_VIEW,
   1424                         position, getChildCount());
   1425             }
   1426         }
   1427 
   1428         return child;
   1429     }
   1430 
   1431     void positionSelector(View sel) {
   1432         final Rect selectorRect = mSelectorRect;
   1433         selectorRect.set(sel.getLeft(), sel.getTop(), sel.getRight(), sel.getBottom());
   1434         positionSelector(selectorRect.left, selectorRect.top, selectorRect.right,
   1435                 selectorRect.bottom);
   1436 
   1437         final boolean isChildViewEnabled = mIsChildViewEnabled;
   1438         if (sel.isEnabled() != isChildViewEnabled) {
   1439             mIsChildViewEnabled = !isChildViewEnabled;
   1440             refreshDrawableState();
   1441         }
   1442     }
   1443 
   1444     private void positionSelector(int l, int t, int r, int b) {
   1445         mSelectorRect.set(l - mSelectionLeftPadding, t - mSelectionTopPadding, r
   1446                 + mSelectionRightPadding, b + mSelectionBottomPadding);
   1447     }
   1448 
   1449     @Override
   1450     protected void dispatchDraw(Canvas canvas) {
   1451         int saveCount = 0;
   1452         final boolean clipToPadding = (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
   1453         if (clipToPadding) {
   1454             saveCount = canvas.save();
   1455             final int scrollX = mScrollX;
   1456             final int scrollY = mScrollY;
   1457             canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,
   1458                     scrollX + mRight - mLeft - mPaddingRight,
   1459                     scrollY + mBottom - mTop - mPaddingBottom);
   1460             mGroupFlags &= ~CLIP_TO_PADDING_MASK;
   1461         }
   1462 
   1463         final boolean drawSelectorOnTop = mDrawSelectorOnTop;
   1464         if (!drawSelectorOnTop) {
   1465             drawSelector(canvas);
   1466         }
   1467 
   1468         super.dispatchDraw(canvas);
   1469 
   1470         if (drawSelectorOnTop) {
   1471             drawSelector(canvas);
   1472         }
   1473 
   1474         if (clipToPadding) {
   1475             canvas.restoreToCount(saveCount);
   1476             mGroupFlags |= CLIP_TO_PADDING_MASK;
   1477         }
   1478     }
   1479 
   1480     @Override
   1481     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
   1482         if (getChildCount() > 0) {
   1483             mDataChanged = true;
   1484             rememberSyncState();
   1485         }
   1486 
   1487         if (mFastScroller != null) {
   1488             mFastScroller.onSizeChanged(w, h, oldw, oldh);
   1489         }
   1490     }
   1491 
   1492     /**
   1493      * @return True if the current touch mode requires that we draw the selector in the pressed
   1494      *         state.
   1495      */
   1496     boolean touchModeDrawsInPressedState() {
   1497         // FIXME use isPressed for this
   1498         switch (mTouchMode) {
   1499         case TOUCH_MODE_TAP:
   1500         case TOUCH_MODE_DONE_WAITING:
   1501             return true;
   1502         default:
   1503             return false;
   1504         }
   1505     }
   1506 
   1507     /**
   1508      * Indicates whether this view is in a state where the selector should be drawn. This will
   1509      * happen if we have focus but are not in touch mode, or we are in the middle of displaying
   1510      * the pressed state for an item.
   1511      *
   1512      * @return True if the selector should be shown
   1513      */
   1514     boolean shouldShowSelector() {
   1515         return (hasFocus() && !isInTouchMode()) || touchModeDrawsInPressedState();
   1516     }
   1517 
   1518     private void drawSelector(Canvas canvas) {
   1519         if (shouldShowSelector() && mSelectorRect != null && !mSelectorRect.isEmpty()) {
   1520             final Drawable selector = mSelector;
   1521             selector.setBounds(mSelectorRect);
   1522             selector.draw(canvas);
   1523         }
   1524     }
   1525 
   1526     /**
   1527      * Controls whether the selection highlight drawable should be drawn on top of the item or
   1528      * behind it.
   1529      *
   1530      * @param onTop If true, the selector will be drawn on the item it is highlighting. The default
   1531      *        is false.
   1532      *
   1533      * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop
   1534      */
   1535     public void setDrawSelectorOnTop(boolean onTop) {
   1536         mDrawSelectorOnTop = onTop;
   1537     }
   1538 
   1539     /**
   1540      * Set a Drawable that should be used to highlight the currently selected item.
   1541      *
   1542      * @param resID A Drawable resource to use as the selection highlight.
   1543      *
   1544      * @attr ref android.R.styleable#AbsListView_listSelector
   1545      */
   1546     public void setSelector(int resID) {
   1547         setSelector(getResources().getDrawable(resID));
   1548     }
   1549 
   1550     public void setSelector(Drawable sel) {
   1551         if (mSelector != null) {
   1552             mSelector.setCallback(null);
   1553             unscheduleDrawable(mSelector);
   1554         }
   1555         mSelector = sel;
   1556         Rect padding = new Rect();
   1557         sel.getPadding(padding);
   1558         mSelectionLeftPadding = padding.left;
   1559         mSelectionTopPadding = padding.top;
   1560         mSelectionRightPadding = padding.right;
   1561         mSelectionBottomPadding = padding.bottom;
   1562         sel.setCallback(this);
   1563         sel.setState(getDrawableState());
   1564     }
   1565 
   1566     /**
   1567      * Returns the selector {@link android.graphics.drawable.Drawable} that is used to draw the
   1568      * selection in the list.
   1569      *
   1570      * @return the drawable used to display the selector
   1571      */
   1572     public Drawable getSelector() {
   1573         return mSelector;
   1574     }
   1575 
   1576     /**
   1577      * Sets the selector state to "pressed" and posts a CheckForKeyLongPress to see if
   1578      * this is a long press.
   1579      */
   1580     void keyPressed() {
   1581         if (!isEnabled() || !isClickable()) {
   1582             return;
   1583         }
   1584 
   1585         Drawable selector = mSelector;
   1586         Rect selectorRect = mSelectorRect;
   1587         if (selector != null && (isFocused() || touchModeDrawsInPressedState())
   1588                 && selectorRect != null && !selectorRect.isEmpty()) {
   1589 
   1590             final View v = getChildAt(mSelectedPosition - mFirstPosition);
   1591 
   1592             if (v != null) {
   1593                 if (v.hasFocusable()) return;
   1594                 v.setPressed(true);
   1595             }
   1596             setPressed(true);
   1597 
   1598             final boolean longClickable = isLongClickable();
   1599             Drawable d = selector.getCurrent();
   1600             if (d != null && d instanceof TransitionDrawable) {
   1601                 if (longClickable) {
   1602                     ((TransitionDrawable) d).startTransition(
   1603                             ViewConfiguration.getLongPressTimeout());
   1604                 } else {
   1605                     ((TransitionDrawable) d).resetTransition();
   1606                 }
   1607             }
   1608             if (longClickable && !mDataChanged) {
   1609                 if (mPendingCheckForKeyLongPress == null) {
   1610                     mPendingCheckForKeyLongPress = new CheckForKeyLongPress();
   1611                 }
   1612                 mPendingCheckForKeyLongPress.rememberWindowAttachCount();
   1613                 postDelayed(mPendingCheckForKeyLongPress, ViewConfiguration.getLongPressTimeout());
   1614             }
   1615         }
   1616     }
   1617 
   1618     public void setScrollIndicators(View up, View down) {
   1619         mScrollUp = up;
   1620         mScrollDown = down;
   1621     }
   1622 
   1623     @Override
   1624     protected void drawableStateChanged() {
   1625         super.drawableStateChanged();
   1626         if (mSelector != null) {
   1627             mSelector.setState(getDrawableState());
   1628         }
   1629     }
   1630 
   1631     @Override
   1632     protected int[] onCreateDrawableState(int extraSpace) {
   1633         // If the child view is enabled then do the default behavior.
   1634         if (mIsChildViewEnabled) {
   1635             // Common case
   1636             return super.onCreateDrawableState(extraSpace);
   1637         }
   1638 
   1639         // The selector uses this View's drawable state. The selected child view
   1640         // is disabled, so we need to remove the enabled state from the drawable
   1641         // states.
   1642         final int enabledState = ENABLED_STATE_SET[0];
   1643 
   1644         // If we don't have any extra space, it will return one of the static state arrays,
   1645         // and clearing the enabled state on those arrays is a bad thing!  If we specify
   1646         // we need extra space, it will create+copy into a new array that safely mutable.
   1647         int[] state = super.onCreateDrawableState(extraSpace + 1);
   1648         int enabledPos = -1;
   1649         for (int i = state.length - 1; i >= 0; i--) {
   1650             if (state[i] == enabledState) {
   1651                 enabledPos = i;
   1652                 break;
   1653             }
   1654         }
   1655 
   1656         // Remove the enabled state
   1657         if (enabledPos >= 0) {
   1658             System.arraycopy(state, enabledPos + 1, state, enabledPos,
   1659                     state.length - enabledPos - 1);
   1660         }
   1661 
   1662         return state;
   1663     }
   1664 
   1665     @Override
   1666     public boolean verifyDrawable(Drawable dr) {
   1667         return mSelector == dr || super.verifyDrawable(dr);
   1668     }
   1669 
   1670     @Override
   1671     protected void onAttachedToWindow() {
   1672         super.onAttachedToWindow();
   1673 
   1674         final ViewTreeObserver treeObserver = getViewTreeObserver();
   1675         if (treeObserver != null) {
   1676             treeObserver.addOnTouchModeChangeListener(this);
   1677             if (mTextFilterEnabled && mPopup != null && !mGlobalLayoutListenerAddedFilter) {
   1678                 treeObserver.addOnGlobalLayoutListener(this);
   1679             }
   1680         }
   1681     }
   1682 
   1683     @Override
   1684     protected void onDetachedFromWindow() {
   1685         super.onDetachedFromWindow();
   1686 
   1687         // Dismiss the popup in case onSaveInstanceState() was not invoked
   1688         dismissPopup();
   1689 
   1690         // Detach any view left in the scrap heap
   1691         mRecycler.clear();
   1692 
   1693         final ViewTreeObserver treeObserver = getViewTreeObserver();
   1694         if (treeObserver != null) {
   1695             treeObserver.removeOnTouchModeChangeListener(this);
   1696             if (mTextFilterEnabled && mPopup != null) {
   1697                 treeObserver.removeGlobalOnLayoutListener(this);
   1698                 mGlobalLayoutListenerAddedFilter = false;
   1699             }
   1700         }
   1701     }
   1702 
   1703     @Override
   1704     public void onWindowFocusChanged(boolean hasWindowFocus) {
   1705         super.onWindowFocusChanged(hasWindowFocus);
   1706 
   1707         final int touchMode = isInTouchMode() ? TOUCH_MODE_ON : TOUCH_MODE_OFF;
   1708 
   1709         if (!hasWindowFocus) {
   1710             setChildrenDrawingCacheEnabled(false);
   1711             if (mFlingRunnable != null) {
   1712                 removeCallbacks(mFlingRunnable);
   1713                 // let the fling runnable report it's new state which
   1714                 // should be idle
   1715                 mFlingRunnable.endFling();
   1716                 if (mScrollY != 0) {
   1717                     mScrollY = 0;
   1718                     finishGlows();
   1719                     invalidate();
   1720                 }
   1721             }
   1722             // Always hide the type filter
   1723             dismissPopup();
   1724 
   1725             if (touchMode == TOUCH_MODE_OFF) {
   1726                 // Remember the last selected element
   1727                 mResurrectToPosition = mSelectedPosition;
   1728             }
   1729         } else {
   1730             if (mFiltered && !mPopupHidden) {
   1731                 // Show the type filter only if a filter is in effect
   1732                 showPopup();
   1733             }
   1734 
   1735             // If we changed touch mode since the last time we had focus
   1736             if (touchMode != mLastTouchMode && mLastTouchMode != TOUCH_MODE_UNKNOWN) {
   1737                 // If we come back in trackball mode, we bring the selection back
   1738                 if (touchMode == TOUCH_MODE_OFF) {
   1739                     // This will trigger a layout
   1740                     resurrectSelection();
   1741 
   1742                 // If we come back in touch mode, then we want to hide the selector
   1743                 } else {
   1744                     hideSelector();
   1745                     mLayoutMode = LAYOUT_NORMAL;
   1746                     layoutChildren();
   1747                 }
   1748             }
   1749         }
   1750 
   1751         mLastTouchMode = touchMode;
   1752     }
   1753 
   1754     /**
   1755      * Creates the ContextMenuInfo returned from {@link #getContextMenuInfo()}. This
   1756      * methods knows the view, position and ID of the item that received the
   1757      * long press.
   1758      *
   1759      * @param view The view that received the long press.
   1760      * @param position The position of the item that received the long press.
   1761      * @param id The ID of the item that received the long press.
   1762      * @return The extra information that should be returned by
   1763      *         {@link #getContextMenuInfo()}.
   1764      */
   1765     ContextMenuInfo createContextMenuInfo(View view, int position, long id) {
   1766         return new AdapterContextMenuInfo(view, position, id);
   1767     }
   1768 
   1769     /**
   1770      * A base class for Runnables that will check that their view is still attached to
   1771      * the original window as when the Runnable was created.
   1772      *
   1773      */
   1774     private class WindowRunnnable {
   1775         private int mOriginalAttachCount;
   1776 
   1777         public void rememberWindowAttachCount() {
   1778             mOriginalAttachCount = getWindowAttachCount();
   1779         }
   1780 
   1781         public boolean sameWindow() {
   1782             return hasWindowFocus() && getWindowAttachCount() == mOriginalAttachCount;
   1783         }
   1784     }
   1785 
   1786     private class PerformClick extends WindowRunnnable implements Runnable {
   1787         View mChild;
   1788         int mClickMotionPosition;
   1789 
   1790         public void run() {
   1791             // The data has changed since we posted this action in the event queue,
   1792             // bail out before bad things happen
   1793             if (mDataChanged) return;
   1794 
   1795             final ListAdapter adapter = mAdapter;
   1796             final int motionPosition = mClickMotionPosition;
   1797             if (adapter != null && mItemCount > 0 &&
   1798                     motionPosition != INVALID_POSITION &&
   1799                     motionPosition < adapter.getCount() && sameWindow()) {
   1800                 performItemClick(mChild, motionPosition, adapter.getItemId(motionPosition));
   1801             }
   1802         }
   1803     }
   1804 
   1805     private class CheckForLongPress extends WindowRunnnable implements Runnable {
   1806         public void run() {
   1807             final int motionPosition = mMotionPosition;
   1808             final View child = getChildAt(motionPosition - mFirstPosition);
   1809             if (child != null) {
   1810                 final int longPressPosition = mMotionPosition;
   1811                 final long longPressId = mAdapter.getItemId(mMotionPosition);
   1812 
   1813                 boolean handled = false;
   1814                 if (sameWindow() && !mDataChanged) {
   1815                     handled = performLongPress(child, longPressPosition, longPressId);
   1816                 }
   1817                 if (handled) {
   1818                     mTouchMode = TOUCH_MODE_REST;
   1819                     setPressed(false);
   1820                     child.setPressed(false);
   1821                 } else {
   1822                     mTouchMode = TOUCH_MODE_DONE_WAITING;
   1823                 }
   1824 
   1825             }
   1826         }
   1827     }
   1828 
   1829     private class CheckForKeyLongPress extends WindowRunnnable implements Runnable {
   1830         public void run() {
   1831             if (isPressed() && mSelectedPosition >= 0) {
   1832                 int index = mSelectedPosition - mFirstPosition;
   1833                 View v = getChildAt(index);
   1834 
   1835                 if (!mDataChanged) {
   1836                     boolean handled = false;
   1837                     if (sameWindow()) {
   1838                         handled = performLongPress(v, mSelectedPosition, mSelectedRowId);
   1839                     }
   1840                     if (handled) {
   1841                         setPressed(false);
   1842                         v.setPressed(false);
   1843                     }
   1844                 } else {
   1845                     setPressed(false);
   1846                     if (v != null) v.setPressed(false);
   1847                 }
   1848             }
   1849         }
   1850     }
   1851 
   1852     private boolean performLongPress(final View child,
   1853             final int longPressPosition, final long longPressId) {
   1854         boolean handled = false;
   1855 
   1856         if (mOnItemLongClickListener != null) {
   1857             handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, child,
   1858                     longPressPosition, longPressId);
   1859         }
   1860         if (!handled) {
   1861             mContextMenuInfo = createContextMenuInfo(child, longPressPosition, longPressId);
   1862             handled = super.showContextMenuForChild(AbsListView.this);
   1863         }
   1864         if (handled) {
   1865             performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
   1866         }
   1867         return handled;
   1868     }
   1869 
   1870     @Override
   1871     protected ContextMenuInfo getContextMenuInfo() {
   1872         return mContextMenuInfo;
   1873     }
   1874 
   1875     @Override
   1876     public boolean showContextMenuForChild(View originalView) {
   1877         final int longPressPosition = getPositionForView(originalView);
   1878         if (longPressPosition >= 0) {
   1879             final long longPressId = mAdapter.getItemId(longPressPosition);
   1880             boolean handled = false;
   1881 
   1882             if (mOnItemLongClickListener != null) {
   1883                 handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, originalView,
   1884                         longPressPosition, longPressId);
   1885             }
   1886             if (!handled) {
   1887                 mContextMenuInfo = createContextMenuInfo(
   1888                         getChildAt(longPressPosition - mFirstPosition),
   1889                         longPressPosition, longPressId);
   1890                 handled = super.showContextMenuForChild(originalView);
   1891             }
   1892 
   1893             return handled;
   1894         }
   1895         return false;
   1896     }
   1897 
   1898     @Override
   1899     public boolean onKeyDown(int keyCode, KeyEvent event) {
   1900         return false;
   1901     }
   1902 
   1903     @Override
   1904     public boolean onKeyUp(int keyCode, KeyEvent event) {
   1905         switch (keyCode) {
   1906         case KeyEvent.KEYCODE_DPAD_CENTER:
   1907         case KeyEvent.KEYCODE_ENTER:
   1908             if (!isEnabled()) {
   1909                 return true;
   1910             }
   1911             if (isClickable() && isPressed() &&
   1912                     mSelectedPosition >= 0 && mAdapter != null &&
   1913                     mSelectedPosition < mAdapter.getCount()) {
   1914 
   1915                 final View view = getChildAt(mSelectedPosition - mFirstPosition);
   1916                 if (view != null) {
   1917                     performItemClick(view, mSelectedPosition, mSelectedRowId);
   1918                     view.setPressed(false);
   1919                 }
   1920                 setPressed(false);
   1921                 return true;
   1922             }
   1923             break;
   1924         }
   1925         return super.onKeyUp(keyCode, event);
   1926     }
   1927 
   1928     @Override
   1929     protected void dispatchSetPressed(boolean pressed) {
   1930         // Don't dispatch setPressed to our children. We call setPressed on ourselves to
   1931         // get the selector in the right state, but we don't want to press each child.
   1932     }
   1933 
   1934     /**
   1935      * Maps a point to a position in the list.
   1936      *
   1937      * @param x X in local coordinate
   1938      * @param y Y in local coordinate
   1939      * @return The position of the item which contains the specified point, or
   1940      *         {@link #INVALID_POSITION} if the point does not intersect an item.
   1941      */
   1942     public int pointToPosition(int x, int y) {
   1943         Rect frame = mTouchFrame;
   1944         if (frame == null) {
   1945             mTouchFrame = new Rect();
   1946             frame = mTouchFrame;
   1947         }
   1948 
   1949         final int count = getChildCount();
   1950         for (int i = count - 1; i >= 0; i--) {
   1951             final View child = getChildAt(i);
   1952             if (child.getVisibility() == View.VISIBLE) {
   1953                 child.getHitRect(frame);
   1954                 if (frame.contains(x, y)) {
   1955                     return mFirstPosition + i;
   1956                 }
   1957             }
   1958         }
   1959         return INVALID_POSITION;
   1960     }
   1961 
   1962 
   1963     /**
   1964      * Maps a point to a the rowId of the item which intersects that point.
   1965      *
   1966      * @param x X in local coordinate
   1967      * @param y Y in local coordinate
   1968      * @return The rowId of the item which contains the specified point, or {@link #INVALID_ROW_ID}
   1969      *         if the point does not intersect an item.
   1970      */
   1971     public long pointToRowId(int x, int y) {
   1972         int position = pointToPosition(x, y);
   1973         if (position >= 0) {
   1974             return mAdapter.getItemId(position);
   1975         }
   1976         return INVALID_ROW_ID;
   1977     }
   1978 
   1979     final class CheckForTap implements Runnable {
   1980         public void run() {
   1981             if (mTouchMode == TOUCH_MODE_DOWN) {
   1982                 mTouchMode = TOUCH_MODE_TAP;
   1983                 final View child = getChildAt(mMotionPosition - mFirstPosition);
   1984                 if (child != null && !child.hasFocusable()) {
   1985                     mLayoutMode = LAYOUT_NORMAL;
   1986 
   1987                     if (!mDataChanged) {
   1988                         layoutChildren();
   1989                         child.setPressed(true);
   1990                         positionSelector(child);
   1991                         setPressed(true);
   1992 
   1993                         final int longPressTimeout = ViewConfiguration.getLongPressTimeout();
   1994                         final boolean longClickable = isLongClickable();
   1995 
   1996                         if (mSelector != null) {
   1997                             Drawable d = mSelector.getCurrent();
   1998                             if (d != null && d instanceof TransitionDrawable) {
   1999                                 if (longClickable) {
   2000                                     ((TransitionDrawable) d).startTransition(longPressTimeout);
   2001                                 } else {
   2002                                     ((TransitionDrawable) d).resetTransition();
   2003                                 }
   2004                             }
   2005                         }
   2006 
   2007                         if (longClickable) {
   2008                             if (mPendingCheckForLongPress == null) {
   2009                                 mPendingCheckForLongPress = new CheckForLongPress();
   2010                             }
   2011                             mPendingCheckForLongPress.rememberWindowAttachCount();
   2012                             postDelayed(mPendingCheckForLongPress, longPressTimeout);
   2013                         } else {
   2014                             mTouchMode = TOUCH_MODE_DONE_WAITING;
   2015                         }
   2016                     } else {
   2017                         mTouchMode = TOUCH_MODE_DONE_WAITING;
   2018                     }
   2019                 }
   2020             }
   2021         }
   2022     }
   2023 
   2024     private boolean startScrollIfNeeded(int deltaY) {
   2025         // Check if we have moved far enough that it looks more like a
   2026         // scroll than a tap
   2027         final int distance = Math.abs(deltaY);
   2028         final boolean overscroll = mScrollY != 0;
   2029         if (overscroll || distance > mTouchSlop) {
   2030             createScrollingCache();
   2031             mTouchMode = overscroll ? TOUCH_MODE_OVERSCROLL : TOUCH_MODE_SCROLL;
   2032             mMotionCorrection = deltaY;
   2033             final Handler handler = getHandler();
   2034             // Handler should not be null unless the AbsListView is not attached to a
   2035             // window, which would make it very hard to scroll it... but the monkeys
   2036             // say it's possible.
   2037             if (handler != null) {
   2038                 handler.removeCallbacks(mPendingCheckForLongPress);
   2039             }
   2040             setPressed(false);
   2041             View motionView = getChildAt(mMotionPosition - mFirstPosition);
   2042             if (motionView != null) {
   2043                 motionView.setPressed(false);
   2044             }
   2045             reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
   2046             // Time to start stealing events! Once we've stolen them, don't let anyone
   2047             // steal from us
   2048             requestDisallowInterceptTouchEvent(true);
   2049             return true;
   2050         }
   2051 
   2052         return false;
   2053     }
   2054 
   2055     public void onTouchModeChanged(boolean isInTouchMode) {
   2056         if (isInTouchMode) {
   2057             // Get rid of the selection when we enter touch mode
   2058             hideSelector();
   2059             // Layout, but only if we already have done so previously.
   2060             // (Otherwise may clobber a LAYOUT_SYNC layout that was requested to restore
   2061             // state.)
   2062             if (getHeight() > 0 && getChildCount() > 0) {
   2063                 // We do not lose focus initiating a touch (since AbsListView is focusable in
   2064                 // touch mode). Force an initial layout to get rid of the selection.
   2065                 layoutChildren();
   2066             }
   2067         } else {
   2068             int touchMode = mTouchMode;
   2069             if (touchMode == TOUCH_MODE_OVERSCROLL || touchMode == TOUCH_MODE_OVERFLING) {
   2070                 if (mFlingRunnable != null) {
   2071                     mFlingRunnable.endFling();
   2072                 }
   2073 
   2074                 if (mScrollY != 0) {
   2075                     mScrollY = 0;
   2076                     finishGlows();
   2077                     invalidate();
   2078                 }
   2079             }
   2080         }
   2081     }
   2082 
   2083     @Override
   2084     public boolean onTouchEvent(MotionEvent ev) {
   2085         if (!isEnabled()) {
   2086             // A disabled view that is clickable still consumes the touch
   2087             // events, it just doesn't respond to them.
   2088             return isClickable() || isLongClickable();
   2089         }
   2090 
   2091         if (mFastScroller != null) {
   2092             boolean intercepted = mFastScroller.onTouchEvent(ev);
   2093             if (intercepted) {
   2094                 return true;
   2095             }
   2096         }
   2097 
   2098         final int action = ev.getAction();
   2099 
   2100         View v;
   2101         int deltaY;
   2102 
   2103         if (mVelocityTracker == null) {
   2104             mVelocityTracker = VelocityTracker.obtain();
   2105         }
   2106         mVelocityTracker.addMovement(ev);
   2107 
   2108         switch (action & MotionEvent.ACTION_MASK) {
   2109         case MotionEvent.ACTION_DOWN: {
   2110             switch (mTouchMode) {
   2111             case TOUCH_MODE_OVERFLING: {
   2112                 mFlingRunnable.endFling();
   2113                 mTouchMode = TOUCH_MODE_OVERSCROLL;
   2114                 mMotionY = mLastY = (int) ev.getY();
   2115                 mMotionCorrection = 0;
   2116                 mActivePointerId = ev.getPointerId(0);
   2117                 break;
   2118             }
   2119 
   2120             default: {
   2121                 mActivePointerId = ev.getPointerId(0);
   2122                 final int x = (int) ev.getX();
   2123                 final int y = (int) ev.getY();
   2124                 int motionPosition = pointToPosition(x, y);
   2125                 if (!mDataChanged) {
   2126                     if ((mTouchMode != TOUCH_MODE_FLING) && (motionPosition >= 0)
   2127                             && (getAdapter().isEnabled(motionPosition))) {
   2128                         // User clicked on an actual view (and was not stopping a fling). It might be a
   2129                         // click or a scroll. Assume it is a click until proven otherwise
   2130                         mTouchMode = TOUCH_MODE_DOWN;
   2131                         // FIXME Debounce
   2132                         if (mPendingCheckForTap == null) {
   2133                             mPendingCheckForTap = new CheckForTap();
   2134                         }
   2135                         postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
   2136                     } else {
   2137                         if (ev.getEdgeFlags() != 0 && motionPosition < 0) {
   2138                             // If we couldn't find a view to click on, but the down event was touching
   2139                             // the edge, we will bail out and try again. This allows the edge correcting
   2140                             // code in ViewRoot to try to find a nearby view to select
   2141                             return false;
   2142                         }
   2143 
   2144                         if (mTouchMode == TOUCH_MODE_FLING) {
   2145                             // Stopped a fling. It is a scroll.
   2146                             createScrollingCache();
   2147                             mTouchMode = TOUCH_MODE_SCROLL;
   2148                             mMotionCorrection = 0;
   2149                             motionPosition = findMotionRow(y);
   2150                             reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
   2151                         }
   2152                     }
   2153                 }
   2154 
   2155                 if (motionPosition >= 0) {
   2156                     // Remember where the motion event started
   2157                     v = getChildAt(motionPosition - mFirstPosition);
   2158                     mMotionViewOriginalTop = v.getTop();
   2159                 }
   2160                 mMotionX = x;
   2161                 mMotionY = y;
   2162                 mMotionPosition = motionPosition;
   2163                 mLastY = Integer.MIN_VALUE;
   2164                 break;
   2165             }
   2166             }
   2167             break;
   2168         }
   2169 
   2170         case MotionEvent.ACTION_MOVE: {
   2171             final int pointerIndex = ev.findPointerIndex(mActivePointerId);
   2172             final int y = (int) ev.getY(pointerIndex);
   2173             deltaY = y - mMotionY;
   2174             switch (mTouchMode) {
   2175             case TOUCH_MODE_DOWN:
   2176             case TOUCH_MODE_TAP:
   2177             case TOUCH_MODE_DONE_WAITING:
   2178                 // Check if we have moved far enough that it looks more like a
   2179                 // scroll than a tap
   2180                 startScrollIfNeeded(deltaY);
   2181                 break;
   2182             case TOUCH_MODE_SCROLL:
   2183                 if (PROFILE_SCROLLING) {
   2184                     if (!mScrollProfilingStarted) {
   2185                         Debug.startMethodTracing("AbsListViewScroll");
   2186                         mScrollProfilingStarted = true;
   2187                     }
   2188                 }
   2189 
   2190                 if (y != mLastY) {
   2191                     // We may be here after stopping a fling and continuing to scroll.
   2192                     // If so, we haven't disallowed intercepting touch events yet.
   2193                     // Make sure that we do so in case we're in a parent that can intercept.
   2194                     if ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) == 0 &&
   2195                             Math.abs(deltaY) > mTouchSlop) {
   2196                         requestDisallowInterceptTouchEvent(true);
   2197                     }
   2198 
   2199                     final int rawDeltaY = deltaY;
   2200                     deltaY -= mMotionCorrection;
   2201                     int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY;
   2202 
   2203                     final int motionIndex;
   2204                     if (mMotionPosition >= 0) {
   2205                         motionIndex = mMotionPosition - mFirstPosition;
   2206                     } else {
   2207                         // If we don't have a motion position that we can reliably track,
   2208                         // pick something in the middle to make a best guess at things below.
   2209                         motionIndex = getChildCount() / 2;
   2210                     }
   2211 
   2212                     int motionViewPrevTop = 0;
   2213                     View motionView = this.getChildAt(motionIndex);
   2214                     if (motionView != null) {
   2215                         motionViewPrevTop = motionView.getTop();
   2216                     }
   2217 
   2218                     // No need to do all this work if we're not going to move anyway
   2219                     boolean atEdge = false;
   2220                     if (incrementalDeltaY != 0) {
   2221                         atEdge = trackMotionScroll(deltaY, incrementalDeltaY);
   2222                     }
   2223 
   2224                     // Check to see if we have bumped into the scroll limit
   2225                     motionView = this.getChildAt(motionIndex);
   2226                     if (motionView != null) {
   2227                         // Check if the top of the motion view is where it is
   2228                         // supposed to be
   2229                         final int motionViewRealTop = motionView.getTop();
   2230                         if (atEdge) {
   2231                             // Apply overscroll
   2232 
   2233                             int overscroll = -incrementalDeltaY -
   2234                                     (motionViewRealTop - motionViewPrevTop);
   2235                             overScrollBy(0, overscroll, 0, mScrollY, 0, 0,
   2236                                     0, mOverscrollDistance, true);
   2237                             if (Math.abs(mOverscrollDistance) == Math.abs(mScrollY)) {
   2238                                 // Don't allow overfling if we're at the edge.
   2239                                 mVelocityTracker.clear();
   2240                             }
   2241 
   2242                             final int overscrollMode = getOverScrollMode();
   2243                             if (overscrollMode == OVER_SCROLL_ALWAYS ||
   2244                                     (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
   2245                                             !contentFits())) {
   2246                                 mDirection = 0; // Reset when entering overscroll.
   2247                                 mTouchMode = TOUCH_MODE_OVERSCROLL;
   2248                                 if (rawDeltaY > 0) {
   2249                                     mEdgeGlowTop.onPull((float) overscroll / getHeight());
   2250                                     if (!mEdgeGlowBottom.isFinished()) {
   2251                                         mEdgeGlowBottom.onRelease();
   2252                                     }
   2253                                 } else if (rawDeltaY < 0) {
   2254                                     mEdgeGlowBottom.onPull((float) overscroll / getHeight());
   2255                                     if (!mEdgeGlowTop.isFinished()) {
   2256                                         mEdgeGlowTop.onRelease();
   2257                                     }
   2258                                 }
   2259                             }
   2260                         }
   2261                         mMotionY = y;
   2262                         invalidate();
   2263                     }
   2264                     mLastY = y;
   2265                 }
   2266                 break;
   2267 
   2268             case TOUCH_MODE_OVERSCROLL:
   2269                 if (y != mLastY) {
   2270                     final int rawDeltaY = deltaY;
   2271                     deltaY -= mMotionCorrection;
   2272                     int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY;
   2273 
   2274                     final int oldScroll = mScrollY;
   2275                     final int newScroll = oldScroll - incrementalDeltaY;
   2276                     int newDirection = y > mLastY ? 1 : -1;
   2277 
   2278                     if (mDirection == 0) {
   2279                         mDirection = newDirection;
   2280                     }
   2281 
   2282                     if (mDirection != newDirection) {
   2283                         // Coming back to 'real' list scrolling
   2284                         incrementalDeltaY = -newScroll;
   2285                         mScrollY = 0;
   2286 
   2287                         // No need to do all this work if we're not going to move anyway
   2288                         if (incrementalDeltaY != 0) {
   2289                             trackMotionScroll(incrementalDeltaY, incrementalDeltaY);
   2290                         }
   2291 
   2292                         // Check to see if we are back in
   2293                         View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
   2294                         if (motionView != null) {
   2295                             mTouchMode = TOUCH_MODE_SCROLL;
   2296 
   2297                             // We did not scroll the full amount. Treat this essentially like the
   2298                             // start of a new touch scroll
   2299                             final int motionPosition = findClosestMotionRow(y);
   2300 
   2301                             mMotionCorrection = 0;
   2302                             motionView = getChildAt(motionPosition - mFirstPosition);
   2303                             mMotionViewOriginalTop = motionView.getTop();
   2304                             mMotionY = y;
   2305                             mMotionPosition = motionPosition;
   2306                         }
   2307                     } else {
   2308                         overScrollBy(0, -incrementalDeltaY, 0, mScrollY, 0, 0,
   2309                                 0, mOverscrollDistance, true);
   2310                         final int overscrollMode = getOverScrollMode();
   2311                         if (overscrollMode == OVER_SCROLL_ALWAYS ||
   2312                                 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
   2313                                         !contentFits())) {
   2314                             if (rawDeltaY > 0) {
   2315                                 mEdgeGlowTop.onPull((float) -incrementalDeltaY / getHeight());
   2316                                 if (!mEdgeGlowBottom.isFinished()) {
   2317                                     mEdgeGlowBottom.onRelease();
   2318                                 }
   2319                             } else if (rawDeltaY < 0) {
   2320                                 mEdgeGlowBottom.onPull((float) -incrementalDeltaY / getHeight());
   2321                                 if (!mEdgeGlowTop.isFinished()) {
   2322                                     mEdgeGlowTop.onRelease();
   2323                                 }
   2324                             }
   2325                             invalidate();
   2326                         }
   2327                         if (Math.abs(mOverscrollDistance) == Math.abs(mScrollY)) {
   2328                             // Don't allow overfling if we're at the edge.
   2329                             mVelocityTracker.clear();
   2330                         }
   2331                     }
   2332                     mLastY = y;
   2333                     mDirection = newDirection;
   2334                 }
   2335                 break;
   2336             }
   2337 
   2338             break;
   2339         }
   2340 
   2341         case MotionEvent.ACTION_UP: {
   2342             switch (mTouchMode) {
   2343             case TOUCH_MODE_DOWN:
   2344             case TOUCH_MODE_TAP:
   2345             case TOUCH_MODE_DONE_WAITING:
   2346                 final int motionPosition = mMotionPosition;
   2347                 final View child = getChildAt(motionPosition - mFirstPosition);
   2348                 if (child != null && !child.hasFocusable()) {
   2349                     if (mTouchMode != TOUCH_MODE_DOWN) {
   2350                         child.setPressed(false);
   2351                     }
   2352 
   2353                     if (mPerformClick == null) {
   2354                         mPerformClick = new PerformClick();
   2355                     }
   2356 
   2357                     final AbsListView.PerformClick performClick = mPerformClick;
   2358                     performClick.mChild = child;
   2359                     performClick.mClickMotionPosition = motionPosition;
   2360                     performClick.rememberWindowAttachCount();
   2361 
   2362                     mResurrectToPosition = motionPosition;
   2363 
   2364                     if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) {
   2365                         final Handler handler = getHandler();
   2366                         if (handler != null) {
   2367                             handler.removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
   2368                                     mPendingCheckForTap : mPendingCheckForLongPress);
   2369                         }
   2370                         mLayoutMode = LAYOUT_NORMAL;
   2371                         if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
   2372                             mTouchMode = TOUCH_MODE_TAP;
   2373                             setSelectedPositionInt(mMotionPosition);
   2374                             layoutChildren();
   2375                             child.setPressed(true);
   2376                             positionSelector(child);
   2377                             setPressed(true);
   2378                             if (mSelector != null) {
   2379                                 Drawable d = mSelector.getCurrent();
   2380                                 if (d != null && d instanceof TransitionDrawable) {
   2381                                     ((TransitionDrawable) d).resetTransition();
   2382                                 }
   2383                             }
   2384                             postDelayed(new Runnable() {
   2385                                 public void run() {
   2386                                     child.setPressed(false);
   2387                                     setPressed(false);
   2388                                     if (!mDataChanged) {
   2389                                         post(performClick);
   2390                                     }
   2391                                     mTouchMode = TOUCH_MODE_REST;
   2392                                 }
   2393                             }, ViewConfiguration.getPressedStateDuration());
   2394                         } else {
   2395                             mTouchMode = TOUCH_MODE_REST;
   2396                         }
   2397                         return true;
   2398                     } else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
   2399                         post(performClick);
   2400                     }
   2401                 }
   2402                 mTouchMode = TOUCH_MODE_REST;
   2403                 break;
   2404             case TOUCH_MODE_SCROLL:
   2405                 final int childCount = getChildCount();
   2406                 if (childCount > 0) {
   2407                     final int firstChildTop = getChildAt(0).getTop();
   2408                     final int lastChildBottom = getChildAt(childCount - 1).getBottom();
   2409                     final int contentTop = mListPadding.top;
   2410                     final int contentBottom = getHeight() - mListPadding.bottom;
   2411                     if (mFirstPosition == 0 && firstChildTop >= contentTop &&
   2412                             mFirstPosition + childCount < mItemCount &&
   2413                             lastChildBottom <= getHeight() - contentBottom) {
   2414                         mTouchMode = TOUCH_MODE_REST;
   2415                         reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
   2416                     } else {
   2417                         final VelocityTracker velocityTracker = mVelocityTracker;
   2418                         velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
   2419                         final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
   2420 
   2421                         // Fling if we have enough velocity and we aren't at a boundary.
   2422                         // Since we can potentially overfling more than we can overscroll, don't
   2423                         // allow the weird behavior where you can scroll to a boundary then
   2424                         // fling further.
   2425                         if (Math.abs(initialVelocity) > mMinimumVelocity &&
   2426                                 !((mFirstPosition == 0 &&
   2427                                         firstChildTop == contentTop - mOverscrollDistance) ||
   2428                                   (mFirstPosition + childCount == mItemCount &&
   2429                                         lastChildBottom == contentBottom + mOverscrollDistance))) {
   2430                             if (mFlingRunnable == null) {
   2431                                 mFlingRunnable = new FlingRunnable();
   2432                             }
   2433                             reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
   2434 
   2435                             mFlingRunnable.start(-initialVelocity);
   2436                         } else {
   2437                             mTouchMode = TOUCH_MODE_REST;
   2438                             reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
   2439                         }
   2440                     }
   2441                 } else {
   2442                     mTouchMode = TOUCH_MODE_REST;
   2443                     reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
   2444                 }
   2445                 break;
   2446 
   2447             case TOUCH_MODE_OVERSCROLL:
   2448                 if (mFlingRunnable == null) {
   2449                     mFlingRunnable = new FlingRunnable();
   2450                 }
   2451                 final VelocityTracker velocityTracker = mVelocityTracker;
   2452                 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
   2453                 final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
   2454 
   2455                 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
   2456                 if (Math.abs(initialVelocity) > mMinimumVelocity) {
   2457                     mFlingRunnable.startOverfling(-initialVelocity);
   2458                 } else {
   2459                     mFlingRunnable.startSpringback();
   2460                 }
   2461 
   2462                 break;
   2463             }
   2464 
   2465             setPressed(false);
   2466 
   2467             if (mEdgeGlowTop != null) {
   2468                 mEdgeGlowTop.onRelease();
   2469                 mEdgeGlowBottom.onRelease();
   2470             }
   2471 
   2472             // Need to redraw since we probably aren't drawing the selector anymore
   2473             invalidate();
   2474 
   2475             final Handler handler = getHandler();
   2476             if (handler != null) {
   2477                 handler.removeCallbacks(mPendingCheckForLongPress);
   2478             }
   2479 
   2480             if (mVelocityTracker != null) {
   2481                 mVelocityTracker.recycle();
   2482                 mVelocityTracker = null;
   2483             }
   2484 
   2485             mActivePointerId = INVALID_POINTER;
   2486 
   2487             if (PROFILE_SCROLLING) {
   2488                 if (mScrollProfilingStarted) {
   2489                     Debug.stopMethodTracing();
   2490                     mScrollProfilingStarted = false;
   2491                 }
   2492             }
   2493             break;
   2494         }
   2495 
   2496         case MotionEvent.ACTION_CANCEL: {
   2497             switch (mTouchMode) {
   2498             case TOUCH_MODE_OVERSCROLL:
   2499                 if (mFlingRunnable == null) {
   2500                     mFlingRunnable = new FlingRunnable();
   2501                 }
   2502                 mFlingRunnable.startSpringback();
   2503                 break;
   2504 
   2505             case TOUCH_MODE_OVERFLING:
   2506                 // Do nothing - let it play out.
   2507                 break;
   2508 
   2509             default:
   2510                 mTouchMode = TOUCH_MODE_REST;
   2511                 setPressed(false);
   2512                 View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
   2513                 if (motionView != null) {
   2514                     motionView.setPressed(false);
   2515                 }
   2516                 clearScrollingCache();
   2517 
   2518                 final Handler handler = getHandler();
   2519                 if (handler != null) {
   2520                     handler.removeCallbacks(mPendingCheckForLongPress);
   2521                 }
   2522 
   2523                 if (mVelocityTracker != null) {
   2524                     mVelocityTracker.recycle();
   2525                     mVelocityTracker = null;
   2526                 }
   2527             }
   2528 
   2529             if (mEdgeGlowTop != null) {
   2530                 mEdgeGlowTop.onRelease();
   2531                 mEdgeGlowBottom.onRelease();
   2532             }
   2533             mActivePointerId = INVALID_POINTER;
   2534             break;
   2535         }
   2536 
   2537         case MotionEvent.ACTION_POINTER_UP: {
   2538             onSecondaryPointerUp(ev);
   2539             final int x = mMotionX;
   2540             final int y = mMotionY;
   2541             final int motionPosition = pointToPosition(x, y);
   2542             if (motionPosition >= 0) {
   2543                 // Remember where the motion event started
   2544                 v = getChildAt(motionPosition - mFirstPosition);
   2545                 mMotionViewOriginalTop = v.getTop();
   2546                 mMotionPosition = motionPosition;
   2547             }
   2548             mLastY = y;
   2549             break;
   2550         }
   2551         }
   2552 
   2553         return true;
   2554     }
   2555 
   2556     @Override
   2557     protected void onOverScrolled(int scrollX, int scrollY,
   2558             boolean clampedX, boolean clampedY) {
   2559         mScrollY = scrollY;
   2560 
   2561         if (clampedY) {
   2562             // Velocity is broken by hitting the limit; don't start a fling off of this.
   2563             if (mVelocityTracker != null) {
   2564                 mVelocityTracker.clear();
   2565             }
   2566         }
   2567         awakenScrollBars();
   2568     }
   2569 
   2570     @Override
   2571     public void draw(Canvas canvas) {
   2572         super.draw(canvas);
   2573         if (mEdgeGlowTop != null) {
   2574             final int scrollY = mScrollY;
   2575             if (!mEdgeGlowTop.isFinished()) {
   2576                 final int restoreCount = canvas.save();
   2577                 final int width = getWidth();
   2578 
   2579                 canvas.translate(-width / 2, Math.min(0, scrollY + mFirstPositionDistanceGuess));
   2580                 mEdgeGlowTop.setSize(width * 2, getHeight());
   2581                 if (mEdgeGlowTop.draw(canvas)) {
   2582                     invalidate();
   2583                 }
   2584                 canvas.restoreToCount(restoreCount);
   2585             }
   2586             if (!mEdgeGlowBottom.isFinished()) {
   2587                 final int restoreCount = canvas.save();
   2588                 final int width = getWidth();
   2589                 final int height = getHeight();
   2590 
   2591                 canvas.translate(-width / 2,
   2592                         Math.max(height, scrollY + mLastPositionDistanceGuess));
   2593                 canvas.rotate(180, width, 0);
   2594                 mEdgeGlowBottom.setSize(width * 2, height);
   2595                 if (mEdgeGlowBottom.draw(canvas)) {
   2596                     invalidate();
   2597                 }
   2598                 canvas.restoreToCount(restoreCount);
   2599             }
   2600         }
   2601         if (mFastScroller != null) {
   2602             final int scrollY = mScrollY;
   2603             if (scrollY != 0) {
   2604                 // Pin to the top/bottom during overscroll
   2605                 int restoreCount = canvas.save();
   2606                 canvas.translate(0, (float) scrollY);
   2607                 mFastScroller.draw(canvas);
   2608                 canvas.restoreToCount(restoreCount);
   2609             } else {
   2610                 mFastScroller.draw(canvas);
   2611             }
   2612         }
   2613     }
   2614 
   2615     @Override
   2616     public boolean onInterceptTouchEvent(MotionEvent ev) {
   2617         int action = ev.getAction();
   2618         View v;
   2619 
   2620         if (mFastScroller != null) {
   2621             boolean intercepted = mFastScroller.onInterceptTouchEvent(ev);
   2622             if (intercepted) {
   2623                 return true;
   2624             }
   2625         }
   2626 
   2627         switch (action & MotionEvent.ACTION_MASK) {
   2628         case MotionEvent.ACTION_DOWN: {
   2629             int touchMode = mTouchMode;
   2630             if (touchMode == TOUCH_MODE_OVERFLING || touchMode == TOUCH_MODE_OVERSCROLL) {
   2631                 mMotionCorrection = 0;
   2632                 return true;
   2633             }
   2634 
   2635             final int x = (int) ev.getX();
   2636             final int y = (int) ev.getY();
   2637             mActivePointerId = ev.getPointerId(0);
   2638 
   2639             int motionPosition = findMotionRow(y);
   2640             if (touchMode != TOUCH_MODE_FLING && motionPosition >= 0) {
   2641                 // User clicked on an actual view (and was not stopping a fling).
   2642                 // Remember where the motion event started
   2643                 v = getChildAt(motionPosition - mFirstPosition);
   2644                 mMotionViewOriginalTop = v.getTop();
   2645                 mMotionX = x;
   2646                 mMotionY = y;
   2647                 mMotionPosition = motionPosition;
   2648                 mTouchMode = TOUCH_MODE_DOWN;
   2649                 clearScrollingCache();
   2650             }
   2651             mLastY = Integer.MIN_VALUE;
   2652             if (touchMode == TOUCH_MODE_FLING) {
   2653                 return true;
   2654             }
   2655             break;
   2656         }
   2657 
   2658         case MotionEvent.ACTION_MOVE: {
   2659             switch (mTouchMode) {
   2660             case TOUCH_MODE_DOWN:
   2661                 final int pointerIndex = ev.findPointerIndex(mActivePointerId);
   2662                 final int y = (int) ev.getY(pointerIndex);
   2663                 if (startScrollIfNeeded(y - mMotionY)) {
   2664                     return true;
   2665                 }
   2666                 break;
   2667             }
   2668             break;
   2669         }
   2670 
   2671         case MotionEvent.ACTION_UP: {
   2672             mTouchMode = TOUCH_MODE_REST;
   2673             mActivePointerId = INVALID_POINTER;
   2674             reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
   2675             break;
   2676         }
   2677 
   2678         case MotionEvent.ACTION_POINTER_UP: {
   2679             onSecondaryPointerUp(ev);
   2680             break;
   2681         }
   2682         }
   2683 
   2684         return false;
   2685     }
   2686 
   2687     private void onSecondaryPointerUp(MotionEvent ev) {
   2688         final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
   2689                 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
   2690         final int pointerId = ev.getPointerId(pointerIndex);
   2691         if (pointerId == mActivePointerId) {
   2692             // This was our active pointer going up. Choose a new
   2693             // active pointer and adjust accordingly.
   2694             // TODO: Make this decision more intelligent.
   2695             final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
   2696             mMotionX = (int) ev.getX(newPointerIndex);
   2697             mMotionY = (int) ev.getY(newPointerIndex);
   2698             mMotionCorrection = 0;
   2699             mActivePointerId = ev.getPointerId(newPointerIndex);
   2700             if (mVelocityTracker != null) {
   2701                 mVelocityTracker.clear();
   2702             }
   2703         }
   2704     }
   2705 
   2706     /**
   2707      * {@inheritDoc}
   2708      */
   2709     @Override
   2710     public void addTouchables(ArrayList<View> views) {
   2711         final int count = getChildCount();
   2712         final int firstPosition = mFirstPosition;
   2713         final ListAdapter adapter = mAdapter;
   2714 
   2715         if (adapter == null) {
   2716             return;
   2717         }
   2718 
   2719         for (int i = 0; i < count; i++) {
   2720             final View child = getChildAt(i);
   2721             if (adapter.isEnabled(firstPosition + i)) {
   2722                 views.add(child);
   2723             }
   2724             child.addTouchables(views);
   2725         }
   2726     }
   2727 
   2728     /**
   2729      * Fires an "on scroll state changed" event to the registered
   2730      * {@link android.widget.AbsListView.OnScrollListener}, if any. The state change
   2731      * is fired only if the specified state is different from the previously known state.
   2732      *
   2733      * @param newState The new scroll state.
   2734      */
   2735     void reportScrollStateChange(int newState) {
   2736         if (newState != mLastScrollState) {
   2737             if (mOnScrollListener != null) {
   2738                 mOnScrollListener.onScrollStateChanged(this, newState);
   2739                 mLastScrollState = newState;
   2740             }
   2741         }
   2742     }
   2743 
   2744     /**
   2745      * Responsible for fling behavior. Use {@link #start(int)} to
   2746      * initiate a fling. Each frame of the fling is handled in {@link #run()}.
   2747      * A FlingRunnable will keep re-posting itself until the fling is done.
   2748      *
   2749      */
   2750     private class FlingRunnable implements Runnable {
   2751         /**
   2752          * Tracks the decay of a fling scroll
   2753          */
   2754         private final OverScroller mScroller;
   2755 
   2756         /**
   2757          * Y value reported by mScroller on the previous fling
   2758          */
   2759         private int mLastFlingY;
   2760 
   2761         FlingRunnable() {
   2762             mScroller = new OverScroller(getContext());
   2763         }
   2764 
   2765         void start(int initialVelocity) {
   2766             int initialY = initialVelocity < 0 ? Integer.MAX_VALUE : 0;
   2767             mLastFlingY = initialY;
   2768             mScroller.fling(0, initialY, 0, initialVelocity,
   2769                     0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE);
   2770             mTouchMode = TOUCH_MODE_FLING;
   2771             post(this);
   2772 
   2773             if (PROFILE_FLINGING) {
   2774                 if (!mFlingProfilingStarted) {
   2775                     Debug.startMethodTracing("AbsListViewFling");
   2776                     mFlingProfilingStarted = true;
   2777                 }
   2778             }
   2779         }
   2780 
   2781         void startSpringback() {
   2782             if (mScroller.springBack(0, mScrollY, 0, 0, 0, 0)) {
   2783                 mTouchMode = TOUCH_MODE_OVERFLING;
   2784                 invalidate();
   2785                 post(this);
   2786             } else {
   2787                 mTouchMode = TOUCH_MODE_REST;
   2788             }
   2789         }
   2790 
   2791         void startOverfling(int initialVelocity) {
   2792             final int min = mScrollY > 0 ? Integer.MIN_VALUE : 0;
   2793             final int max = mScrollY > 0 ? 0 : Integer.MAX_VALUE;
   2794             mScroller.fling(0, mScrollY, 0, initialVelocity, 0, 0, min, max, 0, getHeight());
   2795             mTouchMode = TOUCH_MODE_OVERFLING;
   2796             invalidate();
   2797             post(this);
   2798         }
   2799 
   2800         void edgeReached(int delta) {
   2801             mScroller.notifyVerticalEdgeReached(mScrollY, 0, mOverflingDistance);
   2802             final int overscrollMode = getOverScrollMode();
   2803             if (overscrollMode == OVER_SCROLL_ALWAYS ||
   2804                     (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && !contentFits())) {
   2805                 mTouchMode = TOUCH_MODE_OVERFLING;
   2806                 final int vel = (int) mScroller.getCurrVelocity();
   2807                 if (delta > 0) {
   2808                     mEdgeGlowTop.onAbsorb(vel);
   2809                 } else {
   2810                     mEdgeGlowBottom.onAbsorb(vel);
   2811                 }
   2812             }
   2813             invalidate();
   2814             post(this);
   2815         }
   2816 
   2817         void startScroll(int distance, int duration) {
   2818             int initialY = distance < 0 ? Integer.MAX_VALUE : 0;
   2819             mLastFlingY = initialY;
   2820             mScroller.startScroll(0, initialY, 0, distance, duration);
   2821             mTouchMode = TOUCH_MODE_FLING;
   2822             post(this);
   2823         }
   2824 
   2825         private void endFling() {
   2826             mTouchMode = TOUCH_MODE_REST;
   2827 
   2828             reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
   2829             clearScrollingCache();
   2830 
   2831             removeCallbacks(this);
   2832 
   2833             if (mPositionScroller != null) {
   2834                 removeCallbacks(mPositionScroller);
   2835             }
   2836         }
   2837 
   2838         public void run() {
   2839             switch (mTouchMode) {
   2840             default:
   2841                 return;
   2842 
   2843             case TOUCH_MODE_FLING: {
   2844                 if (mItemCount == 0 || getChildCount() == 0) {
   2845                     endFling();
   2846                     return;
   2847                 }
   2848 
   2849                 final OverScroller scroller = mScroller;
   2850                 boolean more = scroller.computeScrollOffset();
   2851                 final int y = scroller.getCurrY();
   2852 
   2853                 // Flip sign to convert finger direction to list items direction
   2854                 // (e.g. finger moving down means list is moving towards the top)
   2855                 int delta = mLastFlingY - y;
   2856 
   2857                 // Pretend that each frame of a fling scroll is a touch scroll
   2858                 if (delta > 0) {
   2859                     // List is moving towards the top. Use first view as mMotionPosition
   2860                     mMotionPosition = mFirstPosition;
   2861                     final View firstView = getChildAt(0);
   2862                     mMotionViewOriginalTop = firstView.getTop();
   2863 
   2864                     // Don't fling more than 1 screen
   2865                     delta = Math.min(getHeight() - mPaddingBottom - mPaddingTop - 1, delta);
   2866                 } else {
   2867                     // List is moving towards the bottom. Use last view as mMotionPosition
   2868                     int offsetToLast = getChildCount() - 1;
   2869                     mMotionPosition = mFirstPosition + offsetToLast;
   2870 
   2871                     final View lastView = getChildAt(offsetToLast);
   2872                     mMotionViewOriginalTop = lastView.getTop();
   2873 
   2874                     // Don't fling more than 1 screen
   2875                     delta = Math.max(-(getHeight() - mPaddingBottom - mPaddingTop - 1), delta);
   2876                 }
   2877 
   2878                 // Check to see if we have bumped into the scroll limit
   2879                 View motionView = getChildAt(mMotionPosition - mFirstPosition);
   2880                 int oldTop = 0;
   2881                 if (motionView != null) {
   2882                     oldTop = motionView.getTop();
   2883                 }
   2884 
   2885                 final boolean atEnd = trackMotionScroll(delta, delta);
   2886                 if (atEnd) {
   2887                     if (motionView != null) {
   2888                         // Tweak the scroll for how far we overshot
   2889                         int overshoot = -(delta - (motionView.getTop() - oldTop));
   2890                         overScrollBy(0, overshoot, 0, mScrollY, 0, 0,
   2891                                 0, mOverflingDistance, false);
   2892                     }
   2893                     if (more) {
   2894                         edgeReached(delta);
   2895                     }
   2896                     break;
   2897                 }
   2898 
   2899                 if (more && !atEnd) {
   2900                     invalidate();
   2901                     mLastFlingY = y;
   2902                     post(this);
   2903                 } else {
   2904                     endFling();
   2905 
   2906                     if (PROFILE_FLINGING) {
   2907                         if (mFlingProfilingStarted) {
   2908                             Debug.stopMethodTracing();
   2909                             mFlingProfilingStarted = false;
   2910                         }
   2911                     }
   2912                 }
   2913                 break;
   2914             }
   2915 
   2916             case TOUCH_MODE_OVERFLING: {
   2917                 final OverScroller scroller = mScroller;
   2918                 if (scroller.computeScrollOffset()) {
   2919                     final int scrollY = mScrollY;
   2920                     final int deltaY = scroller.getCurrY() - scrollY;
   2921                     if (overScrollBy(0, deltaY, 0, scrollY, 0, 0,
   2922                             0, mOverflingDistance, false)) {
   2923                         startSpringback();
   2924                     } else {
   2925                         invalidate();
   2926                         post(this);
   2927                     }
   2928                 } else {
   2929                     endFling();
   2930                 }
   2931                 break;
   2932             }
   2933             }
   2934 
   2935         }
   2936     }
   2937 
   2938 
   2939     class PositionScroller implements Runnable {
   2940         private static final int SCROLL_DURATION = 400;
   2941 
   2942         private static final int MOVE_DOWN_POS = 1;
   2943         private static final int MOVE_UP_POS = 2;
   2944         private static final int MOVE_DOWN_BOUND = 3;
   2945         private static final int MOVE_UP_BOUND = 4;
   2946 
   2947         private int mMode;
   2948         private int mTargetPos;
   2949         private int mBoundPos;
   2950         private int mLastSeenPos;
   2951         private int mScrollDuration;
   2952         private final int mExtraScroll;
   2953 
   2954         PositionScroller() {
   2955             mExtraScroll = ViewConfiguration.get(mContext).getScaledFadingEdgeLength();
   2956         }
   2957 
   2958         void start(int position) {
   2959             final int firstPos = mFirstPosition;
   2960             final int lastPos = firstPos + getChildCount() - 1;
   2961 
   2962             int viewTravelCount = 0;
   2963             if (position <= firstPos) {
   2964                 viewTravelCount = firstPos - position + 1;
   2965                 mMode = MOVE_UP_POS;
   2966             } else if (position >= lastPos) {
   2967                 viewTravelCount = position - lastPos + 1;
   2968                 mMode = MOVE_DOWN_POS;
   2969             } else {
   2970                 // Already on screen, nothing to do
   2971                 return;
   2972             }
   2973 
   2974             if (viewTravelCount > 0) {
   2975                 mScrollDuration = SCROLL_DURATION / viewTravelCount;
   2976             } else {
   2977                 mScrollDuration = SCROLL_DURATION;
   2978             }
   2979             mTargetPos = position;
   2980             mBoundPos = INVALID_POSITION;
   2981             mLastSeenPos = INVALID_POSITION;
   2982 
   2983             post(this);
   2984         }
   2985 
   2986         void start(int position, int boundPosition) {
   2987             if (boundPosition == INVALID_POSITION) {
   2988                 start(position);
   2989                 return;
   2990             }
   2991 
   2992             final int firstPos = mFirstPosition;
   2993             final int lastPos = firstPos + getChildCount() - 1;
   2994 
   2995             int viewTravelCount = 0;
   2996             if (position <= firstPos) {
   2997                 final int boundPosFromLast = lastPos - boundPosition;
   2998                 if (boundPosFromLast < 1) {
   2999                     // Moving would shift our bound position off the screen. Abort.
   3000                     return;
   3001                 }
   3002 
   3003                 final int posTravel = firstPos - position + 1;
   3004                 final int boundTravel = boundPosFromLast - 1;
   3005                 if (boundTravel < posTravel) {
   3006                     viewTravelCount = boundTravel;
   3007                     mMode = MOVE_UP_BOUND;
   3008                 } else {
   3009                     viewTravelCount = posTravel;
   3010                     mMode = MOVE_UP_POS;
   3011                 }
   3012             } else if (position >= lastPos) {
   3013                 final int boundPosFromFirst = boundPosition - firstPos;
   3014                 if (boundPosFromFirst < 1) {
   3015                     // Moving would shift our bound position off the screen. Abort.
   3016                     return;
   3017                 }
   3018 
   3019                 final int posTravel = position - lastPos + 1;
   3020                 final int boundTravel = boundPosFromFirst - 1;
   3021                 if (boundTravel < posTravel) {
   3022                     viewTravelCount = boundTravel;
   3023                     mMode = MOVE_DOWN_BOUND;
   3024                 } else {
   3025                     viewTravelCount = posTravel;
   3026                     mMode = MOVE_DOWN_POS;
   3027                 }
   3028             } else {
   3029                 // Already on screen, nothing to do
   3030                 return;
   3031             }
   3032 
   3033             if (viewTravelCount > 0) {
   3034                 mScrollDuration = SCROLL_DURATION / viewTravelCount;
   3035             } else {
   3036                 mScrollDuration = SCROLL_DURATION;
   3037             }
   3038             mTargetPos = position;
   3039             mBoundPos = boundPosition;
   3040             mLastSeenPos = INVALID_POSITION;
   3041 
   3042             post(this);
   3043         }
   3044 
   3045         void stop() {
   3046             removeCallbacks(this);
   3047         }
   3048 
   3049         public void run() {
   3050             final int listHeight = getHeight();
   3051             final int firstPos = mFirstPosition;
   3052 
   3053             switch (mMode) {
   3054             case MOVE_DOWN_POS: {
   3055                 final int lastViewIndex = getChildCount() - 1;
   3056                 final int lastPos = firstPos + lastViewIndex;
   3057 
   3058                 if (lastViewIndex < 0) {
   3059                     return;
   3060                 }
   3061 
   3062                 if (lastPos == mLastSeenPos) {
   3063                     // No new views, let things keep going.
   3064                     post(this);
   3065                     return;
   3066                 }
   3067 
   3068                 final View lastView = getChildAt(lastViewIndex);
   3069                 final int lastViewHeight = lastView.getHeight();
   3070                 final int lastViewTop = lastView.getTop();
   3071                 final int lastViewPixelsShowing = listHeight - lastViewTop;
   3072                 final int extraScroll = lastPos < mItemCount - 1 ? mExtraScroll : mListPadding.bottom;
   3073 
   3074                 smoothScrollBy(lastViewHeight - lastViewPixelsShowing + extraScroll,
   3075                         mScrollDuration);
   3076 
   3077                 mLastSeenPos = lastPos;
   3078                 if (lastPos < mTargetPos) {
   3079                     post(this);
   3080                 }
   3081                 break;
   3082             }
   3083 
   3084             case MOVE_DOWN_BOUND: {
   3085                 final int nextViewIndex = 1;
   3086                 final int childCount = getChildCount();
   3087 
   3088                 if (firstPos == mBoundPos || childCount <= nextViewIndex
   3089                         || firstPos + childCount >= mItemCount) {
   3090                     return;
   3091                 }
   3092                 final int nextPos = firstPos + nextViewIndex;
   3093 
   3094                 if (nextPos == mLastSeenPos) {
   3095                     // No new views, let things keep going.
   3096                     post(this);
   3097                     return;
   3098                 }
   3099 
   3100                 final View nextView = getChildAt(nextViewIndex);
   3101                 final int nextViewHeight = nextView.getHeight();
   3102                 final int nextViewTop = nextView.getTop();
   3103                 final int extraScroll = mExtraScroll;
   3104                 if (nextPos < mBoundPos) {
   3105                     smoothScrollBy(Math.max(0, nextViewHeight + nextViewTop - extraScroll),
   3106                             mScrollDuration);
   3107 
   3108                     mLastSeenPos = nextPos;
   3109 
   3110                     post(this);
   3111                 } else  {
   3112                     if (nextViewTop > extraScroll) {
   3113                         smoothScrollBy(nextViewTop - extraScroll, mScrollDuration);
   3114                     }
   3115                 }
   3116                 break;
   3117             }
   3118 
   3119             case MOVE_UP_POS: {
   3120                 if (firstPos == mLastSeenPos) {
   3121                     // No new views, let things keep going.
   3122                     post(this);
   3123                     return;
   3124                 }
   3125 
   3126                 final View firstView = getChildAt(0);
   3127                 if (firstView == null) {
   3128                     return;
   3129                 }
   3130                 final int firstViewTop = firstView.getTop();
   3131                 final int extraScroll = firstPos > 0 ? mExtraScroll : mListPadding.top;
   3132 
   3133                 smoothScrollBy(firstViewTop - extraScroll, mScrollDuration);
   3134 
   3135                 mLastSeenPos = firstPos;
   3136 
   3137                 if (firstPos > mTargetPos) {
   3138                     post(this);
   3139                 }
   3140                 break;
   3141             }
   3142 
   3143             case MOVE_UP_BOUND: {
   3144                 final int lastViewIndex = getChildCount() - 2;
   3145                 if (lastViewIndex < 0) {
   3146                     return;
   3147                 }
   3148                 final int lastPos = firstPos + lastViewIndex;
   3149 
   3150                 if (lastPos == mLastSeenPos) {
   3151                     // No new views, let things keep going.
   3152                     post(this);
   3153                     return;
   3154                 }
   3155 
   3156                 final View lastView = getChildAt(lastViewIndex);
   3157                 final int lastViewHeight = lastView.getHeight();
   3158                 final int lastViewTop = lastView.getTop();
   3159                 final int lastViewPixelsShowing = listHeight - lastViewTop;
   3160                 mLastSeenPos = lastPos;
   3161                 if (lastPos > mBoundPos) {
   3162                     smoothScrollBy(-(lastViewPixelsShowing - mExtraScroll), mScrollDuration);
   3163                     post(this);
   3164                 } else {
   3165                     final int bottom = listHeight - mExtraScroll;
   3166                     final int lastViewBottom = lastViewTop + lastViewHeight;
   3167                     if (bottom > lastViewBottom) {
   3168                         smoothScrollBy(-(bottom - lastViewBottom), mScrollDuration);
   3169                     }
   3170                 }
   3171                 break;
   3172             }
   3173 
   3174             default:
   3175                 break;
   3176             }
   3177         }
   3178     }
   3179 
   3180     /**
   3181      * Smoothly scroll to the specified adapter position. The view will
   3182      * scroll such that the indicated position is displayed.
   3183      * @param position Scroll to this adapter position.
   3184      */
   3185     public void smoothScrollToPosition(int position) {
   3186         if (mPositionScroller == null) {
   3187             mPositionScroller = new PositionScroller();
   3188         }
   3189         mPositionScroller.start(position);
   3190     }
   3191 
   3192     /**
   3193      * Smoothly scroll to the specified adapter position. The view will
   3194      * scroll such that the indicated position is displayed, but it will
   3195      * stop early if scrolling further would scroll boundPosition out of
   3196      * view.
   3197      * @param position Scroll to this adapter position.
   3198      * @param boundPosition Do not scroll if it would move this adapter
   3199      *          position out of view.
   3200      */
   3201     public void smoothScrollToPosition(int position, int boundPosition) {
   3202         if (mPositionScroller == null) {
   3203             mPositionScroller = new PositionScroller();
   3204         }
   3205         mPositionScroller.start(position, boundPosition);
   3206     }
   3207 
   3208     /**
   3209      * Smoothly scroll by distance pixels over duration milliseconds.
   3210      * @param distance Distance to scroll in pixels.
   3211      * @param duration Duration of the scroll animation in milliseconds.
   3212      */
   3213     public void smoothScrollBy(int distance, int duration) {
   3214         if (mFlingRunnable == null) {
   3215             mFlingRunnable = new FlingRunnable();
   3216         } else {
   3217             mFlingRunnable.endFling();
   3218         }
   3219         mFlingRunnable.startScroll(distance, duration);
   3220     }
   3221 
   3222     private void createScrollingCache() {
   3223         if (mScrollingCacheEnabled && !mCachingStarted) {
   3224             setChildrenDrawnWithCacheEnabled(true);
   3225             setChildrenDrawingCacheEnabled(true);
   3226             mCachingStarted = true;
   3227         }
   3228     }
   3229 
   3230     private void clearScrollingCache() {
   3231         if (mClearScrollingCache == null) {
   3232             mClearScrollingCache = new Runnable() {
   3233                 public void run() {
   3234                     if (mCachingStarted) {
   3235                         mCachingStarted = false;
   3236                         setChildrenDrawnWithCacheEnabled(false);
   3237                         if ((mPersistentDrawingCache & PERSISTENT_SCROLLING_CACHE) == 0) {
   3238                             setChildrenDrawingCacheEnabled(false);
   3239                         }
   3240                         if (!isAlwaysDrawnWithCacheEnabled()) {
   3241                             invalidate();
   3242                         }
   3243                     }
   3244                 }
   3245             };
   3246         }
   3247         post(mClearScrollingCache);
   3248     }
   3249 
   3250     /**
   3251      * Track a motion scroll
   3252      *
   3253      * @param deltaY Amount to offset mMotionView. This is the accumulated delta since the motion
   3254      *        began. Positive numbers mean the user's finger is moving down the screen.
   3255      * @param incrementalDeltaY Change in deltaY from the previous event.
   3256      * @return true if we're already at the beginning/end of the list and have nothing to do.
   3257      */
   3258     boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
   3259         final int childCount = getChildCount();
   3260         if (childCount == 0) {
   3261             return true;
   3262         }
   3263 
   3264         final int firstTop = getChildAt(0).getTop();
   3265         final int lastBottom = getChildAt(childCount - 1).getBottom();
   3266 
   3267         final Rect listPadding = mListPadding;
   3268 
   3269          // FIXME account for grid vertical spacing too?
   3270         final int spaceAbove = listPadding.top - firstTop;
   3271         final int end = getHeight() - listPadding.bottom;
   3272         final int spaceBelow = lastBottom - end;
   3273 
   3274         final int height = getHeight() - mPaddingBottom - mPaddingTop;
   3275         if (deltaY < 0) {
   3276             deltaY = Math.max(-(height - 1), deltaY);
   3277         } else {
   3278             deltaY = Math.min(height - 1, deltaY);
   3279         }
   3280 
   3281         if (incrementalDeltaY < 0) {
   3282             incrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY);
   3283         } else {
   3284             incrementalDeltaY = Math.min(height - 1, incrementalDeltaY);
   3285         }
   3286 
   3287         final int firstPosition = mFirstPosition;
   3288 
   3289         // Update our guesses for where the first and last views are
   3290         if (firstPosition == 0) {
   3291             mFirstPositionDistanceGuess = firstTop - mListPadding.top;
   3292         } else {
   3293             mFirstPositionDistanceGuess += incrementalDeltaY;
   3294         }
   3295         if (firstPosition + childCount == mItemCount) {
   3296             mLastPositionDistanceGuess = lastBottom + mListPadding.bottom;
   3297         } else {
   3298             mLastPositionDistanceGuess += incrementalDeltaY;
   3299         }
   3300 
   3301         if (firstPosition == 0 && firstTop >= listPadding.top && incrementalDeltaY >= 0) {
   3302             // Don't need to move views down if the top of the first position
   3303             // is already visible
   3304             return incrementalDeltaY != 0;
   3305         }
   3306 
   3307         if (firstPosition + childCount == mItemCount && lastBottom <= end &&
   3308                 incrementalDeltaY <= 0) {
   3309             // Don't need to move views up if the bottom of the last position
   3310             // is already visible
   3311             return incrementalDeltaY != 0;
   3312         }
   3313 
   3314         final boolean down = incrementalDeltaY < 0;
   3315 
   3316         final boolean inTouchMode = isInTouchMode();
   3317         if (inTouchMode) {
   3318             hideSelector();
   3319         }
   3320 
   3321         final int headerViewsCount = getHeaderViewsCount();
   3322         final int footerViewsStart = mItemCount - getFooterViewsCount();
   3323 
   3324         int start = 0;
   3325         int count = 0;
   3326 
   3327         if (down) {
   3328             final int top = listPadding.top - incrementalDeltaY;
   3329             for (int i = 0; i < childCount; i++) {
   3330                 final View child = getChildAt(i);
   3331                 if (child.getBottom() >= top) {
   3332                     break;
   3333                 } else {
   3334                     count++;
   3335                     int position = firstPosition + i;
   3336                     if (position >= headerViewsCount && position < footerViewsStart) {
   3337                         mRecycler.addScrapView(child);
   3338 
   3339                         if (ViewDebug.TRACE_RECYCLER) {
   3340                             ViewDebug.trace(child,
   3341                                     ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP,
   3342                                     firstPosition + i, -1);
   3343                         }
   3344                     }
   3345                 }
   3346             }
   3347         } else {
   3348             final int bottom = getHeight() - listPadding.bottom - incrementalDeltaY;
   3349             for (int i = childCount - 1; i >= 0; i--) {
   3350                 final View child = getChildAt(i);
   3351                 if (child.getTop() <= bottom) {
   3352                     break;
   3353                 } else {
   3354                     start = i;
   3355                     count++;
   3356                     int position = firstPosition + i;
   3357                     if (position >= headerViewsCount && position < footerViewsStart) {
   3358                         mRecycler.addScrapView(child);
   3359 
   3360                         if (ViewDebug.TRACE_RECYCLER) {
   3361                             ViewDebug.trace(child,
   3362                                     ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP,
   3363                                     firstPosition + i, -1);
   3364                         }
   3365                     }
   3366                 }
   3367             }
   3368         }
   3369 
   3370         mMotionViewNewTop = mMotionViewOriginalTop + deltaY;
   3371 
   3372         mBlockLayoutRequests = true;
   3373 
   3374         if (count > 0) {
   3375             detachViewsFromParent(start, count);
   3376         }
   3377         offsetChildrenTopAndBottom(incrementalDeltaY);
   3378 
   3379         if (down) {
   3380             mFirstPosition += count;
   3381         }
   3382 
   3383         invalidate();
   3384 
   3385         final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
   3386         if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
   3387             fillGap(down);
   3388         }
   3389 
   3390         if (!inTouchMode && mSelectedPosition != INVALID_POSITION) {
   3391             final int childIndex = mSelectedPosition - mFirstPosition;
   3392             if (childIndex >= 0 && childIndex < getChildCount()) {
   3393                 positionSelector(getChildAt(childIndex));
   3394             }
   3395         }
   3396 
   3397         mBlockLayoutRequests = false;
   3398 
   3399         invokeOnItemScrollListener();
   3400         awakenScrollBars();
   3401 
   3402         return false;
   3403     }
   3404 
   3405     /**
   3406      * Returns the number of header views in the list. Header views are special views
   3407      * at the top of the list that should not be recycled during a layout.
   3408      *
   3409      * @return The number of header views, 0 in the default implementation.
   3410      */
   3411     int getHeaderViewsCount() {
   3412         return 0;
   3413     }
   3414 
   3415     /**
   3416      * Returns the number of footer views in the list. Footer views are special views
   3417      * at the bottom of the list that should not be recycled during a layout.
   3418      *
   3419      * @return The number of footer views, 0 in the default implementation.
   3420      */
   3421     int getFooterViewsCount() {
   3422         return 0;
   3423     }
   3424 
   3425     /**
   3426      * Fills the gap left open by a touch-scroll. During a touch scroll, children that
   3427      * remain on screen are shifted and the other ones are discarded. The role of this
   3428      * method is to fill the gap thus created by performing a partial layout in the
   3429      * empty space.
   3430      *
   3431      * @param down true if the scroll is going down, false if it is going up
   3432      */
   3433     abstract void fillGap(boolean down);
   3434 
   3435     void hideSelector() {
   3436         if (mSelectedPosition != INVALID_POSITION) {
   3437             if (mLayoutMode != LAYOUT_SPECIFIC) {
   3438                 mResurrectToPosition = mSelectedPosition;
   3439             }
   3440             if (mNextSelectedPosition >= 0 && mNextSelectedPosition != mSelectedPosition) {
   3441                 mResurrectToPosition = mNextSelectedPosition;
   3442             }
   3443             setSelectedPositionInt(INVALID_POSITION);
   3444             setNextSelectedPositionInt(INVALID_POSITION);
   3445             mSelectedTop = 0;
   3446             mSelectorRect.setEmpty();
   3447         }
   3448     }
   3449 
   3450     /**
   3451      * @return A position to select. First we try mSelectedPosition. If that has been clobbered by
   3452      * entering touch mode, we then try mResurrectToPosition. Values are pinned to the range
   3453      * of items available in the adapter
   3454      */
   3455     int reconcileSelectedPosition() {
   3456         int position = mSelectedPosition;
   3457         if (position < 0) {
   3458             position = mResurrectToPosition;
   3459         }
   3460         position = Math.max(0, position);
   3461         position = Math.min(position, mItemCount - 1);
   3462         return position;
   3463     }
   3464 
   3465     /**
   3466      * Find the row closest to y. This row will be used as the motion row when scrolling
   3467      *
   3468      * @param y Where the user touched
   3469      * @return The position of the first (or only) item in the row containing y
   3470      */
   3471     abstract int findMotionRow(int y);
   3472 
   3473     /**
   3474      * Find the row closest to y. This row will be used as the motion row when scrolling.
   3475      *
   3476      * @param y Where the user touched
   3477      * @return The position of the first (or only) item in the row closest to y
   3478      */
   3479     int findClosestMotionRow(int y) {
   3480         final int childCount = getChildCount();
   3481         if (childCount == 0) {
   3482             return INVALID_POSITION;
   3483         }
   3484 
   3485         final int motionRow = findMotionRow(y);
   3486         return motionRow != INVALID_POSITION ? motionRow : mFirstPosition + childCount - 1;
   3487     }
   3488 
   3489     /**
   3490      * Causes all the views to be rebuilt and redrawn.
   3491      */
   3492     public void invalidateViews() {
   3493         mDataChanged = true;
   3494         rememberSyncState();
   3495         requestLayout();
   3496         invalidate();
   3497     }
   3498 
   3499     /**
   3500      * Makes the item at the supplied position selected.
   3501      *
   3502      * @param position the position of the new selection
   3503      */
   3504     abstract void setSelectionInt(int position);
   3505 
   3506     /**
   3507      * Attempt to bring the selection back if the user is switching from touch
   3508      * to trackball mode
   3509      * @return Whether selection was set to something.
   3510      */
   3511     boolean resurrectSelection() {
   3512         final int childCount = getChildCount();
   3513 
   3514         if (childCount <= 0) {
   3515             return false;
   3516         }
   3517 
   3518         int selectedTop = 0;
   3519         int selectedPos;
   3520         int childrenTop = mListPadding.top;
   3521         int childrenBottom = mBottom - mTop - mListPadding.bottom;
   3522         final int firstPosition = mFirstPosition;
   3523         final int toPosition = mResurrectToPosition;
   3524         boolean down = true;
   3525 
   3526         if (toPosition >= firstPosition && toPosition < firstPosition + childCount) {
   3527             selectedPos = toPosition;
   3528 
   3529             final View selected = getChildAt(selectedPos - mFirstPosition);
   3530             selectedTop = selected.getTop();
   3531             int selectedBottom = selected.getBottom();
   3532 
   3533             // We are scrolled, don't get in the fade
   3534             if (selectedTop < childrenTop) {
   3535                 selectedTop = childrenTop + getVerticalFadingEdgeLength();
   3536             } else if (selectedBottom > childrenBottom) {
   3537                 selectedTop = childrenBottom - selected.getMeasuredHeight()
   3538                         - getVerticalFadingEdgeLength();
   3539             }
   3540         } else {
   3541             if (toPosition < firstPosition) {
   3542                 // Default to selecting whatever is first
   3543                 selectedPos = firstPosition;
   3544                 for (int i = 0; i < childCount; i++) {
   3545                     final View v = getChildAt(i);
   3546                     final int top = v.getTop();
   3547 
   3548                     if (i == 0) {
   3549                         // Remember the position of the first item
   3550                         selectedTop = top;
   3551                         // See if we are scrolled at all
   3552                         if (firstPosition > 0 || top < childrenTop) {
   3553                             // If we are scrolled, don't select anything that is
   3554                             // in the fade region
   3555                             childrenTop += getVerticalFadingEdgeLength();
   3556                         }
   3557                     }
   3558                     if (top >= childrenTop) {
   3559                         // Found a view whose top is fully visisble
   3560                         selectedPos = firstPosition + i;
   3561                         selectedTop = top;
   3562                         break;
   3563                     }
   3564                 }
   3565             } else {
   3566                 final int itemCount = mItemCount;
   3567                 down = false;
   3568                 selectedPos = firstPosition + childCount - 1;
   3569 
   3570                 for (int i = childCount - 1; i >= 0; i--) {
   3571                     final View v = getChildAt(i);
   3572                     final int top = v.getTop();
   3573                     final int bottom = v.getBottom();
   3574 
   3575                     if (i == childCount - 1) {
   3576                         selectedTop = top;
   3577                         if (firstPosition + childCount < itemCount || bottom > childrenBottom) {
   3578                             childrenBottom -= getVerticalFadingEdgeLength();
   3579                         }
   3580                     }
   3581 
   3582                     if (bottom <= childrenBottom) {
   3583                         selectedPos = firstPosition + i;
   3584                         selectedTop = top;
   3585                         break;
   3586                     }
   3587                 }
   3588             }
   3589         }
   3590 
   3591         mResurrectToPosition = INVALID_POSITION;
   3592         removeCallbacks(mFlingRunnable);
   3593         mTouchMode = TOUCH_MODE_REST;
   3594         clearScrollingCache();
   3595         mSpecificTop = selectedTop;
   3596         selectedPos = lookForSelectablePosition(selectedPos, down);
   3597         if (selectedPos >= firstPosition && selectedPos <= getLastVisiblePosition()) {
   3598             mLayoutMode = LAYOUT_SPECIFIC;
   3599             setSelectionInt(selectedPos);
   3600             invokeOnItemScrollListener();
   3601         } else {
   3602             selectedPos = INVALID_POSITION;
   3603         }
   3604         reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
   3605 
   3606         return selectedPos >= 0;
   3607     }
   3608 
   3609     @Override
   3610     protected void handleDataChanged() {
   3611         int count = mItemCount;
   3612         if (count > 0) {
   3613 
   3614             int newPos;
   3615 
   3616             int selectablePos;
   3617 
   3618             // Find the row we are supposed to sync to
   3619             if (mNeedSync) {
   3620                 // Update this first, since setNextSelectedPositionInt inspects it
   3621                 mNeedSync = false;
   3622 
   3623                 if (mTranscriptMode == TRANSCRIPT_MODE_ALWAYS_SCROLL ||
   3624                         (mTranscriptMode == TRANSCRIPT_MODE_NORMAL &&
   3625                                 mFirstPosition + getChildCount() >= mOldItemCount)) {
   3626                     mLayoutMode = LAYOUT_FORCE_BOTTOM;
   3627                     return;
   3628                 }
   3629 
   3630                 switch (mSyncMode) {
   3631                 case SYNC_SELECTED_POSITION:
   3632                     if (isInTouchMode()) {
   3633                         // We saved our state when not in touch mode. (We know this because
   3634                         // mSyncMode is SYNC_SELECTED_POSITION.) Now we are trying to
   3635                         // restore in touch mode. Just leave mSyncPosition as it is (possibly
   3636                         // adjusting if the available range changed) and return.
   3637                         mLayoutMode = LAYOUT_SYNC;
   3638                         mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
   3639 
   3640                         return;
   3641                     } else {
   3642                         // See if we can find a position in the new data with the same
   3643                         // id as the old selection. This will change mSyncPosition.
   3644                         newPos = findSyncPosition();
   3645                         if (newPos >= 0) {
   3646                             // Found it. Now verify that new selection is still selectable
   3647                             selectablePos = lookForSelectablePosition(newPos, true);
   3648                             if (selectablePos == newPos) {
   3649                                 // Same row id is selected
   3650                                 mSyncPosition = newPos;
   3651 
   3652                                 if (mSyncHeight == getHeight()) {
   3653                                     // If we are at the same height as when we saved state, try
   3654                                     // to restore the scroll position too.
   3655                                     mLayoutMode = LAYOUT_SYNC;
   3656                                 } else {
   3657                                     // We are not the same height as when the selection was saved, so
   3658                                     // don't try to restore the exact position
   3659                                     mLayoutMode = LAYOUT_SET_SELECTION;
   3660                                 }
   3661 
   3662                                 // Restore selection
   3663                                 setNextSelectedPositionInt(newPos);
   3664                                 return;
   3665                             }
   3666                         }
   3667                     }
   3668                     break;
   3669                 case SYNC_FIRST_POSITION:
   3670                     // Leave mSyncPosition as it is -- just pin to available range
   3671                     mLayoutMode = LAYOUT_SYNC;
   3672                     mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
   3673 
   3674                     return;
   3675                 }
   3676             }
   3677 
   3678             if (!isInTouchMode()) {
   3679                 // We couldn't find matching data -- try to use the same position
   3680                 newPos = getSelectedItemPosition();
   3681 
   3682                 // Pin position to the available range
   3683                 if (newPos >= count) {
   3684                     newPos = count - 1;
   3685                 }
   3686                 if (newPos < 0) {
   3687                     newPos = 0;
   3688                 }
   3689 
   3690                 // Make sure we select something selectable -- first look down
   3691                 selectablePos = lookForSelectablePosition(newPos, true);
   3692 
   3693                 if (selectablePos >= 0) {
   3694                     setNextSelectedPositionInt(selectablePos);
   3695                     return;
   3696                 } else {
   3697                     // Looking down didn't work -- try looking up
   3698                     selectablePos = lookForSelectablePosition(newPos, false);
   3699                     if (selectablePos >= 0) {
   3700                         setNextSelectedPositionInt(selectablePos);
   3701                         return;
   3702                     }
   3703                 }
   3704             } else {
   3705 
   3706                 // We already know where we want to resurrect the selection
   3707                 if (mResurrectToPosition >= 0) {
   3708                     return;
   3709                 }
   3710             }
   3711 
   3712         }
   3713 
   3714         // Nothing is selected. Give up and reset everything.
   3715         mLayoutMode = mStackFromBottom ? LAYOUT_FORCE_BOTTOM : LAYOUT_FORCE_TOP;
   3716         mSelectedPosition = INVALID_POSITION;
   3717         mSelectedRowId = INVALID_ROW_ID;
   3718         mNextSelectedPosition = INVALID_POSITION;
   3719         mNextSelectedRowId = INVALID_ROW_ID;
   3720         mNeedSync = false;
   3721         checkSelectionChanged();
   3722     }
   3723 
   3724     @Override
   3725     protected void onDisplayHint(int hint) {
   3726         super.onDisplayHint(hint);
   3727         switch (hint) {
   3728             case INVISIBLE:
   3729                 if (mPopup != null && mPopup.isShowing()) {
   3730                     dismissPopup();
   3731                 }
   3732                 break;
   3733             case VISIBLE:
   3734                 if (mFiltered && mPopup != null && !mPopup.isShowing()) {
   3735                     showPopup();
   3736                 }
   3737                 break;
   3738         }
   3739         mPopupHidden = hint == INVISIBLE;
   3740     }
   3741 
   3742     /**
   3743      * Removes the filter window
   3744      */
   3745     private void dismissPopup() {
   3746         if (mPopup != null) {
   3747             mPopup.dismiss();
   3748         }
   3749     }
   3750 
   3751     /**
   3752      * Shows the filter window
   3753      */
   3754     private void showPopup() {
   3755         // Make sure we have a window before showing the popup
   3756         if (getWindowVisibility() == View.VISIBLE) {
   3757             createTextFilter(true);
   3758             positionPopup();
   3759             // Make sure we get focus if we are showing the popup
   3760             checkFocus();
   3761         }
   3762     }
   3763 
   3764     private void positionPopup() {
   3765         int screenHeight = getResources().getDisplayMetrics().heightPixels;
   3766         final int[] xy = new int[2];
   3767         getLocationOnScreen(xy);
   3768         // TODO: The 20 below should come from the theme
   3769         // TODO: And the gravity should be defined in the theme as well
   3770         final int bottomGap = screenHeight - xy[1] - getHeight() + (int) (mDensityScale * 20);
   3771         if (!mPopup.isShowing()) {
   3772             mPopup.showAtLocation(this, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL,
   3773                     xy[0], bottomGap);
   3774         } else {
   3775             mPopup.update(xy[0], bottomGap, -1, -1);
   3776         }
   3777     }
   3778 
   3779     /**
   3780      * What is the distance between the source and destination rectangles given the direction of
   3781      * focus navigation between them? The direction basically helps figure out more quickly what is
   3782      * self evident by the relationship between the rects...
   3783      *
   3784      * @param source the source rectangle
   3785      * @param dest the destination rectangle
   3786      * @param direction the direction
   3787      * @return the distance between the rectangles
   3788      */
   3789     static int getDistance(Rect source, Rect dest, int direction) {
   3790         int sX, sY; // source x, y
   3791         int dX, dY; // dest x, y
   3792         switch (direction) {
   3793         case View.FOCUS_RIGHT:
   3794             sX = source.right;
   3795             sY = source.top + source.height() / 2;
   3796             dX = dest.left;
   3797             dY = dest.top + dest.height() / 2;
   3798             break;
   3799         case View.FOCUS_DOWN:
   3800             sX = source.left + source.width() / 2;
   3801             sY = source.bottom;
   3802             dX = dest.left + dest.width() / 2;
   3803             dY = dest.top;
   3804             break;
   3805         case View.FOCUS_LEFT:
   3806             sX = source.left;
   3807             sY = source.top + source.height() / 2;
   3808             dX = dest.right;
   3809             dY = dest.top + dest.height() / 2;
   3810             break;
   3811         case View.FOCUS_UP:
   3812             sX = source.left + source.width() / 2;
   3813             sY = source.top;
   3814             dX = dest.left + dest.width() / 2;
   3815             dY = dest.bottom;
   3816             break;
   3817         default:
   3818             throw new IllegalArgumentException("direction must be one of "
   3819                     + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
   3820         }
   3821         int deltaX = dX - sX;
   3822         int deltaY = dY - sY;
   3823         return deltaY * deltaY + deltaX * deltaX;
   3824     }
   3825 
   3826     @Override
   3827     protected boolean isInFilterMode() {
   3828         return mFiltered;
   3829     }
   3830 
   3831     /**
   3832      * Sends a key to the text filter window
   3833      *
   3834      * @param keyCode The keycode for the event
   3835      * @param event The actual key event
   3836      *
   3837      * @return True if the text filter handled the event, false otherwise.
   3838      */
   3839     boolean sendToTextFilter(int keyCode, int count, KeyEvent event) {
   3840         if (!acceptFilter()) {
   3841             return false;
   3842         }
   3843 
   3844         boolean handled = false;
   3845         boolean okToSend = true;
   3846         switch (keyCode) {
   3847         case KeyEvent.KEYCODE_DPAD_UP:
   3848         case KeyEvent.KEYCODE_DPAD_DOWN:
   3849         case KeyEvent.KEYCODE_DPAD_LEFT:
   3850         case KeyEvent.KEYCODE_DPAD_RIGHT:
   3851         case KeyEvent.KEYCODE_DPAD_CENTER:
   3852         case KeyEvent.KEYCODE_ENTER:
   3853             okToSend = false;
   3854             break;
   3855         case KeyEvent.KEYCODE_BACK:
   3856             if (mFiltered && mPopup != null && mPopup.isShowing()) {
   3857                 if (event.getAction() == KeyEvent.ACTION_DOWN
   3858                         && event.getRepeatCount() == 0) {
   3859                     getKeyDispatcherState().startTracking(event, this);
   3860                     handled = true;
   3861                 } else if (event.getAction() == KeyEvent.ACTION_UP
   3862                         && event.isTracking() && !event.isCanceled()) {
   3863                     handled = true;
   3864                     mTextFilter.setText("");
   3865                 }
   3866             }
   3867             okToSend = false;
   3868             break;
   3869         case KeyEvent.KEYCODE_SPACE:
   3870             // Only send spaces once we are filtered
   3871             okToSend = mFiltered;
   3872             break;
   3873         }
   3874 
   3875         if (okToSend) {
   3876             createTextFilter(true);
   3877 
   3878             KeyEvent forwardEvent = event;
   3879             if (forwardEvent.getRepeatCount() > 0) {
   3880                 forwardEvent = KeyEvent.changeTimeRepeat(event, event.getEventTime(), 0);
   3881             }
   3882 
   3883             int action = event.getAction();
   3884             switch (action) {
   3885                 case KeyEvent.ACTION_DOWN:
   3886                     handled = mTextFilter.onKeyDown(keyCode, forwardEvent);
   3887                     break;
   3888 
   3889                 case KeyEvent.ACTION_UP:
   3890                     handled = mTextFilter.onKeyUp(keyCode, forwardEvent);
   3891                     break;
   3892 
   3893                 case KeyEvent.ACTION_MULTIPLE:
   3894                     handled = mTextFilter.onKeyMultiple(keyCode, count, event);
   3895                     break;
   3896             }
   3897         }
   3898         return handled;
   3899     }
   3900 
   3901     /**
   3902      * Return an InputConnection for editing of the filter text.
   3903      */
   3904     @Override
   3905     public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
   3906         if (isTextFilterEnabled()) {
   3907             // XXX we need to have the text filter created, so we can get an
   3908             // InputConnection to proxy to.  Unfortunately this means we pretty
   3909             // much need to make it as soon as a list view gets focus.
   3910             createTextFilter(false);
   3911             if (mPublicInputConnection == null) {
   3912                 mDefInputConnection = new BaseInputConnection(this, false);
   3913                 mPublicInputConnection = new InputConnectionWrapper(
   3914                         mTextFilter.onCreateInputConnection(outAttrs), true) {
   3915                     @Override
   3916                     public boolean reportFullscreenMode(boolean enabled) {
   3917                         // Use our own input connection, since it is
   3918                         // the "real" one the IME is talking with.
   3919                         return mDefInputConnection.reportFullscreenMode(enabled);
   3920                     }
   3921 
   3922                     @Override
   3923                     public boolean performEditorAction(int editorAction) {
   3924                         // The editor is off in its own window; we need to be
   3925                         // the one that does this.
   3926                         if (editorAction == EditorInfo.IME_ACTION_DONE) {
   3927                             InputMethodManager imm = (InputMethodManager)
   3928                                     getContext().getSystemService(
   3929                                             Context.INPUT_METHOD_SERVICE);
   3930                             if (imm != null) {
   3931                                 imm.hideSoftInputFromWindow(getWindowToken(), 0);
   3932                             }
   3933                             return true;
   3934                         }
   3935                         return false;
   3936                     }
   3937 
   3938                     @Override
   3939                     public boolean sendKeyEvent(KeyEvent event) {
   3940                         // Use our own input connection, since the filter
   3941                         // text view may not be shown in a window so has
   3942                         // no ViewRoot to dispatch events with.
   3943                         return mDefInputConnection.sendKeyEvent(event);
   3944                     }
   3945                 };
   3946             }
   3947             outAttrs.inputType = EditorInfo.TYPE_CLASS_TEXT
   3948                     | EditorInfo.TYPE_TEXT_VARIATION_FILTER;
   3949             outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE;
   3950             return mPublicInputConnection;
   3951         }
   3952         return null;
   3953     }
   3954 
   3955     /**
   3956      * For filtering we proxy an input connection to an internal text editor,
   3957      * and this allows the proxying to happen.
   3958      */
   3959     @Override
   3960     public boolean checkInputConnectionProxy(View view) {
   3961         return view == mTextFilter;
   3962     }
   3963 
   3964     /**
   3965      * Creates the window for the text filter and populates it with an EditText field;
   3966      *
   3967      * @param animateEntrance true if the window should appear with an animation
   3968      */
   3969     private void createTextFilter(boolean animateEntrance) {
   3970         if (mPopup == null) {
   3971             Context c = getContext();
   3972             PopupWindow p = new PopupWindow(c);
   3973             LayoutInflater layoutInflater = (LayoutInflater)
   3974                     c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
   3975             mTextFilter = (EditText) layoutInflater.inflate(
   3976                     com.android.internal.R.layout.typing_filter, null);
   3977             // For some reason setting this as the "real" input type changes
   3978             // the text view in some way that it doesn't work, and I don't
   3979             // want to figure out why this is.
   3980             mTextFilter.setRawInputType(EditorInfo.TYPE_CLASS_TEXT
   3981                     | EditorInfo.TYPE_TEXT_VARIATION_FILTER);
   3982             mTextFilter.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI);
   3983             mTextFilter.addTextChangedListener(this);
   3984             p.setFocusable(false);
   3985             p.setTouchable(false);
   3986             p.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
   3987             p.setContentView(mTextFilter);
   3988             p.setWidth(LayoutParams.WRAP_CONTENT);
   3989             p.setHeight(LayoutParams.WRAP_CONTENT);
   3990             p.setBackgroundDrawable(null);
   3991             mPopup = p;
   3992             getViewTreeObserver().addOnGlobalLayoutListener(this);
   3993             mGlobalLayoutListenerAddedFilter = true;
   3994         }
   3995         if (animateEntrance) {
   3996             mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilter);
   3997         } else {
   3998             mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilterRestore);
   3999         }
   4000     }
   4001 
   4002     /**
   4003      * Clear the text filter.
   4004      */
   4005     public void clearTextFilter() {
   4006         if (mFiltered) {
   4007             mTextFilter.setText("");
   4008             mFiltered = false;
   4009             if (mPopup != null && mPopup.isShowing()) {
   4010                 dismissPopup();
   4011             }
   4012         }
   4013     }
   4014 
   4015     /**
   4016      * Returns if the ListView currently has a text filter.
   4017      */
   4018     public boolean hasTextFilter() {
   4019         return mFiltered;
   4020     }
   4021 
   4022     public void onGlobalLayout() {
   4023         if (isShown()) {
   4024             // Show the popup if we are filtered
   4025             if (mFiltered && mPopup != null && !mPopup.isShowing() && !mPopupHidden) {
   4026                 showPopup();
   4027             }
   4028         } else {
   4029             // Hide the popup when we are no longer visible
   4030             if (mPopup != null && mPopup.isShowing()) {
   4031                 dismissPopup();
   4032             }
   4033         }
   4034 
   4035     }
   4036 
   4037     /**
   4038      * For our text watcher that is associated with the text filter.  Does
   4039      * nothing.
   4040      */
   4041     public void beforeTextChanged(CharSequence s, int start, int count, int after) {
   4042     }
   4043 
   4044     /**
   4045      * For our text watcher that is associated with the text filter. Performs
   4046      * the actual filtering as the text changes, and takes care of hiding and
   4047      * showing the popup displaying the currently entered filter text.
   4048      */
   4049     public void onTextChanged(CharSequence s, int start, int before, int count) {
   4050         if (mPopup != null && isTextFilterEnabled()) {
   4051             int length = s.length();
   4052             boolean showing = mPopup.isShowing();
   4053             if (!showing && length > 0) {
   4054                 // Show the filter popup if necessary
   4055                 showPopup();
   4056                 mFiltered = true;
   4057             } else if (showing && length == 0) {
   4058                 // Remove the filter popup if the user has cleared all text
   4059                 dismissPopup();
   4060                 mFiltered = false;
   4061             }
   4062             if (mAdapter instanceof Filterable) {
   4063                 Filter f = ((Filterable) mAdapter).getFilter();
   4064                 // Filter should not be null when we reach this part
   4065                 if (f != null) {
   4066                     f.filter(s, this);
   4067                 } else {
   4068                     throw new IllegalStateException("You cannot call onTextChanged with a non "
   4069                             + "filterable adapter");
   4070                 }
   4071             }
   4072         }
   4073     }
   4074 
   4075     /**
   4076      * For our text watcher that is associated with the text filter.  Does
   4077      * nothing.
   4078      */
   4079     public void afterTextChanged(Editable s) {
   4080     }
   4081 
   4082     public void onFilterComplete(int count) {
   4083         if (mSelectedPosition < 0 && count > 0) {
   4084             mResurrectToPosition = INVALID_POSITION;
   4085             resurrectSelection();
   4086         }
   4087     }
   4088 
   4089     @Override
   4090     protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
   4091         return new LayoutParams(p);
   4092     }
   4093 
   4094     @Override
   4095     public LayoutParams generateLayoutParams(AttributeSet attrs) {
   4096         return new AbsListView.LayoutParams(getContext(), attrs);
   4097     }
   4098 
   4099     @Override
   4100     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
   4101         return p instanceof AbsListView.LayoutParams;
   4102     }
   4103 
   4104     /**
   4105      * Puts the list or grid into transcript mode. In this mode the list or grid will always scroll
   4106      * to the bottom to show new items.
   4107      *
   4108      * @param mode the transcript mode to set
   4109      *
   4110      * @see #TRANSCRIPT_MODE_DISABLED
   4111      * @see #TRANSCRIPT_MODE_NORMAL
   4112      * @see #TRANSCRIPT_MODE_ALWAYS_SCROLL
   4113      */
   4114     public void setTranscriptMode(int mode) {
   4115         mTranscriptMode = mode;
   4116     }
   4117 
   4118     /**
   4119      * Returns the current transcript mode.
   4120      *
   4121      * @return {@link #TRANSCRIPT_MODE_DISABLED}, {@link #TRANSCRIPT_MODE_NORMAL} or
   4122      *         {@link #TRANSCRIPT_MODE_ALWAYS_SCROLL}
   4123      */
   4124     public int getTranscriptMode() {
   4125         return mTranscriptMode;
   4126     }
   4127 
   4128     @Override
   4129     public int getSolidColor() {
   4130         return mCacheColorHint;
   4131     }
   4132 
   4133     /**
   4134      * When set to a non-zero value, the cache color hint indicates that this list is always drawn
   4135      * on top of a solid, single-color, opaque background
   4136      *
   4137      * @param color The background color
   4138      */
   4139     public void setCacheColorHint(int color) {
   4140         if (color != mCacheColorHint) {
   4141             mCacheColorHint = color;
   4142             int count = getChildCount();
   4143             for (int i = 0; i < count; i++) {
   4144                 getChildAt(i).setDrawingCacheBackgroundColor(color);
   4145             }
   4146             mRecycler.setCacheColorHint(color);
   4147         }
   4148     }
   4149 
   4150     /**
   4151      * When set to a non-zero value, the cache color hint indicates that this list is always drawn
   4152      * on top of a solid, single-color, opaque background
   4153      *
   4154      * @return The cache color hint
   4155      */
   4156     public int getCacheColorHint() {
   4157         return mCacheColorHint;
   4158     }
   4159 
   4160     /**
   4161      * Move all views (excluding headers and footers) held by this AbsListView into the supplied
   4162      * List. This includes views displayed on the screen as well as views stored in AbsListView's
   4163      * internal view recycler.
   4164      *
   4165      * @param views A list into which to put the reclaimed views
   4166      */
   4167     public void reclaimViews(List<View> views) {
   4168         int childCount = getChildCount();
   4169         RecyclerListener listener = mRecycler.mRecyclerListener;
   4170 
   4171         // Reclaim views on screen
   4172         for (int i = 0; i < childCount; i++) {
   4173             View child = getChildAt(i);
   4174             AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
   4175             // Don't reclaim header or footer views, or views that should be ignored
   4176             if (lp != null && mRecycler.shouldRecycleViewType(lp.viewType)) {
   4177                 views.add(child);
   4178                 if (listener != null) {
   4179                     // Pretend they went through the scrap heap
   4180                     listener.onMovedToScrapHeap(child);
   4181                 }
   4182             }
   4183         }
   4184         mRecycler.reclaimScrapViews(views);
   4185         removeAllViewsInLayout();
   4186     }
   4187 
   4188     /**
   4189      * @hide
   4190      */
   4191     @Override
   4192     protected boolean onConsistencyCheck(int consistency) {
   4193         boolean result = super.onConsistencyCheck(consistency);
   4194 
   4195         final boolean checkLayout = (consistency & ViewDebug.CONSISTENCY_LAYOUT) != 0;
   4196 
   4197         if (checkLayout) {
   4198             // The active recycler must be empty
   4199             final View[] activeViews = mRecycler.mActiveViews;
   4200             int count = activeViews.length;
   4201             for (int i = 0; i < count; i++) {
   4202                 if (activeViews[i] != null) {
   4203                     result = false;
   4204                     Log.d(ViewDebug.CONSISTENCY_LOG_TAG,
   4205                             "AbsListView " + this + " has a view in its active recycler: " +
   4206                                     activeViews[i]);
   4207                 }
   4208             }
   4209 
   4210             // All views in the recycler must NOT be on screen and must NOT have a parent
   4211             final ArrayList<View> scrap = mRecycler.mCurrentScrap;
   4212             if (!checkScrap(scrap)) result = false;
   4213             final ArrayList<View>[] scraps = mRecycler.mScrapViews;
   4214             count = scraps.length;
   4215             for (int i = 0; i < count; i++) {
   4216                 if (!checkScrap(scraps[i])) result = false;
   4217             }
   4218         }
   4219 
   4220         return result;
   4221     }
   4222 
   4223     private boolean checkScrap(ArrayList<View> scrap) {
   4224         if (scrap == null) return true;
   4225         boolean result = true;
   4226 
   4227         final int count = scrap.size();
   4228         for (int i = 0; i < count; i++) {
   4229             final View view = scrap.get(i);
   4230             if (view.getParent() != null) {
   4231                 result = false;
   4232                 Log.d(ViewDebug.CONSISTENCY_LOG_TAG, "AbsListView " + this +
   4233                         " has a view in its scrap heap still attached to a parent: " + view);
   4234             }
   4235             if (indexOfChild(view) >= 0) {
   4236                 result = false;
   4237                 Log.d(ViewDebug.CONSISTENCY_LOG_TAG, "AbsListView " + this +
   4238                         " has a view in its scrap heap that is also a direct child: " + view);
   4239             }
   4240         }
   4241 
   4242         return result;
   4243     }
   4244 
   4245     private void finishGlows() {
   4246         if (mEdgeGlowTop != null) {
   4247             mEdgeGlowTop.finish();
   4248             mEdgeGlowBottom.finish();
   4249         }
   4250     }
   4251 
   4252     /**
   4253      * Sets the recycler listener to be notified whenever a View is set aside in
   4254      * the recycler for later reuse. This listener can be used to free resources
   4255      * associated to the View.
   4256      *
   4257      * @param listener The recycler listener to be notified of views set aside
   4258      *        in the recycler.
   4259      *
   4260      * @see android.widget.AbsListView.RecycleBin
   4261      * @see android.widget.AbsListView.RecyclerListener
   4262      */
   4263     public void setRecyclerListener(RecyclerListener listener) {
   4264         mRecycler.mRecyclerListener = listener;
   4265     }
   4266 
   4267     /**
   4268      * AbsListView extends LayoutParams to provide a place to hold the view type.
   4269      */
   4270     public static class LayoutParams extends ViewGroup.LayoutParams {
   4271         /**
   4272          * View type for this view, as returned by
   4273          * {@link android.widget.Adapter#getItemViewType(int) }
   4274          */
   4275         @ViewDebug.ExportedProperty(category = "list", mapping = {
   4276             @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_IGNORE, to = "ITEM_VIEW_TYPE_IGNORE"),
   4277             @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_HEADER_OR_FOOTER, to = "ITEM_VIEW_TYPE_HEADER_OR_FOOTER")
   4278         })
   4279         int viewType;
   4280 
   4281         /**
   4282          * When this boolean is set, the view has been added to the AbsListView
   4283          * at least once. It is used to know whether headers/footers have already
   4284          * been added to the list view and whether they should be treated as
   4285          * recycled views or not.
   4286          */
   4287         @ViewDebug.ExportedProperty(category = "list")
   4288         boolean recycledHeaderFooter;
   4289 
   4290         /**
   4291          * When an AbsListView is measured with an AT_MOST measure spec, it needs
   4292          * to obtain children views to measure itself. When doing so, the children
   4293          * are not attached to the window, but put in the recycler which assumes
   4294          * they've been attached before. Setting this flag will force the reused
   4295          * view to be attached to the window rather than just attached to the
   4296          * parent.
   4297          */
   4298         @ViewDebug.ExportedProperty(category = "list")
   4299         boolean forceAdd;
   4300 
   4301         public LayoutParams(Context c, AttributeSet attrs) {
   4302             super(c, attrs);
   4303         }
   4304 
   4305         public LayoutParams(int w, int h) {
   4306             super(w, h);
   4307         }
   4308 
   4309         public LayoutParams(int w, int h, int viewType) {
   4310             super(w, h);
   4311             this.viewType = viewType;
   4312         }
   4313 
   4314         public LayoutParams(ViewGroup.LayoutParams source) {
   4315             super(source);
   4316         }
   4317     }
   4318 
   4319     /**
   4320      * A RecyclerListener is used to receive a notification whenever a View is placed
   4321      * inside the RecycleBin's scrap heap. This listener is used to free resources
   4322      * associated to Views placed in the RecycleBin.
   4323      *
   4324      * @see android.widget.AbsListView.RecycleBin
   4325      * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
   4326      */
   4327     public static interface RecyclerListener {
   4328         /**
   4329          * Indicates that the specified View was moved into the recycler's scrap heap.
   4330          * The view is not displayed on screen any more and any expensive resource
   4331          * associated with the view should be discarded.
   4332          *
   4333          * @param view
   4334          */
   4335         void onMovedToScrapHeap(View view);
   4336     }
   4337 
   4338     /**
   4339      * The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of
   4340      * storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the
   4341      * start of a layout. By construction, they are displaying current information. At the end of
   4342      * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that
   4343      * could potentially be used by the adapter to avoid allocating views unnecessarily.
   4344      *
   4345      * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
   4346      * @see android.widget.AbsListView.RecyclerListener
   4347      */
   4348     class RecycleBin {
   4349         private RecyclerListener mRecyclerListener;
   4350 
   4351         /**
   4352          * The position of the first view stored in mActiveViews.
   4353          */
   4354         private int mFirstActivePosition;
   4355 
   4356         /**
   4357          * Views that were on screen at the start of layout. This array is populated at the start of
   4358          * layout, and at the end of layout all view in mActiveViews are moved to mScrapViews.
   4359          * Views in mActiveViews represent a contiguous range of Views, with position of the first
   4360          * view store in mFirstActivePosition.
   4361          */
   4362         private View[] mActiveViews = new View[0];
   4363 
   4364         /**
   4365          * Unsorted views that can be used by the adapter as a convert view.
   4366          */
   4367         private ArrayList<View>[] mScrapViews;
   4368 
   4369         private int mViewTypeCount;
   4370 
   4371         private ArrayList<View> mCurrentScrap;
   4372 
   4373         public void setViewTypeCount(int viewTypeCount) {
   4374             if (viewTypeCount < 1) {
   4375                 throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
   4376             }
   4377             //noinspection unchecked
   4378             ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
   4379             for (int i = 0; i < viewTypeCount; i++) {
   4380                 scrapViews[i] = new ArrayList<View>();
   4381             }
   4382             mViewTypeCount = viewTypeCount;
   4383             mCurrentScrap = scrapViews[0];
   4384             mScrapViews = scrapViews;
   4385         }
   4386 
   4387         public void markChildrenDirty() {
   4388             if (mViewTypeCount == 1) {
   4389                 final ArrayList<View> scrap = mCurrentScrap;
   4390                 final int scrapCount = scrap.size();
   4391                 for (int i = 0; i < scrapCount; i++) {
   4392                     scrap.get(i).forceLayout();
   4393                 }
   4394             } else {
   4395                 final int typeCount = mViewTypeCount;
   4396                 for (int i = 0; i < typeCount; i++) {
   4397                     final ArrayList<View> scrap = mScrapViews[i];
   4398                     final int scrapCount = scrap.size();
   4399                     for (int j = 0; j < scrapCount; j++) {
   4400                         scrap.get(j).forceLayout();
   4401                     }
   4402                 }
   4403             }
   4404         }
   4405 
   4406         public boolean shouldRecycleViewType(int viewType) {
   4407             return viewType >= 0;
   4408         }
   4409 
   4410         /**
   4411          * Clears the scrap heap.
   4412          */
   4413         void clear() {
   4414             if (mViewTypeCount == 1) {
   4415                 final ArrayList<View> scrap = mCurrentScrap;
   4416                 final int scrapCount = scrap.size();
   4417                 for (int i = 0; i < scrapCount; i++) {
   4418                     removeDetachedView(scrap.remove(scrapCount - 1 - i), false);
   4419                 }
   4420             } else {
   4421                 final int typeCount = mViewTypeCount;
   4422                 for (int i = 0; i < typeCount; i++) {
   4423                     final ArrayList<View> scrap = mScrapViews[i];
   4424                     final int scrapCount = scrap.size();
   4425                     for (int j = 0; j < scrapCount; j++) {
   4426                         removeDetachedView(scrap.remove(scrapCount - 1 - j), false);
   4427                     }
   4428                 }
   4429             }
   4430         }
   4431 
   4432         /**
   4433          * Fill ActiveViews with all of the children of the AbsListView.
   4434          *
   4435          * @param childCount The minimum number of views mActiveViews should hold
   4436          * @param firstActivePosition The position of the first view that will be stored in
   4437          *        mActiveViews
   4438          */
   4439         void fillActiveViews(int childCount, int firstActivePosition) {
   4440             if (mActiveViews.length < childCount) {
   4441                 mActiveViews = new View[childCount];
   4442             }
   4443             mFirstActivePosition = firstActivePosition;
   4444 
   4445             final View[] activeViews = mActiveViews;
   4446             for (int i = 0; i < childCount; i++) {
   4447                 View child = getChildAt(i);
   4448                 AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
   4449                 // Don't put header or footer views into the scrap heap
   4450                 if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
   4451                     // Note:  We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views.
   4452                     //        However, we will NOT place them into scrap views.
   4453                     activeViews[i] = child;
   4454                 }
   4455             }
   4456         }
   4457 
   4458         /**
   4459          * Get the view corresponding to the specified position. The view will be removed from
   4460          * mActiveViews if it is found.
   4461          *
   4462          * @param position The position to look up in mActiveViews
   4463          * @return The view if it is found, null otherwise
   4464          */
   4465         View getActiveView(int position) {
   4466             int index = position - mFirstActivePosition;
   4467             final View[] activeViews = mActiveViews;
   4468             if (index >=0 && index < activeViews.length) {
   4469                 final View match = activeViews[index];
   4470                 activeViews[index] = null;
   4471                 return match;
   4472             }
   4473             return null;
   4474         }
   4475 
   4476         /**
   4477          * @return A view from the ScrapViews collection. These are unordered.
   4478          */
   4479         View getScrapView(int position) {
   4480             ArrayList<View> scrapViews;
   4481             if (mViewTypeCount == 1) {
   4482                 scrapViews = mCurrentScrap;
   4483                 int size = scrapViews.size();
   4484                 if (size > 0) {
   4485                     return scrapViews.remove(size - 1);
   4486                 } else {
   4487                     return null;
   4488                 }
   4489             } else {
   4490                 int whichScrap = mAdapter.getItemViewType(position);
   4491                 if (whichScrap >= 0 && whichScrap < mScrapViews.length) {
   4492                     scrapViews = mScrapViews[whichScrap];
   4493                     int size = scrapViews.size();
   4494                     if (size > 0) {
   4495                         return scrapViews.remove(size - 1);
   4496                     }
   4497                 }
   4498             }
   4499             return null;
   4500         }
   4501 
   4502         /**
   4503          * Put a view into the ScapViews list. These views are unordered.
   4504          *
   4505          * @param scrap The view to add
   4506          */
   4507         void addScrapView(View scrap) {
   4508             AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
   4509             if (lp == null) {
   4510                 return;
   4511             }
   4512 
   4513             // Don't put header or footer views or views that should be ignored
   4514             // into the scrap heap
   4515             int viewType = lp.viewType;
   4516             if (!shouldRecycleViewType(viewType)) {
   4517                 if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
   4518                     removeDetachedView(scrap, false);
   4519                 }
   4520                 return;
   4521             }
   4522 
   4523             if (mViewTypeCount == 1) {
   4524                 scrap.dispatchStartTemporaryDetach();
   4525                 mCurrentScrap.add(scrap);
   4526             } else {
   4527                 scrap.dispatchStartTemporaryDetach();
   4528                 mScrapViews[viewType].add(scrap);
   4529             }
   4530 
   4531             if (mRecyclerListener != null) {
   4532                 mRecyclerListener.onMovedToScrapHeap(scrap);
   4533             }
   4534         }
   4535 
   4536         /**
   4537          * Move all views remaining in mActiveViews to mScrapViews.
   4538          */
   4539         void scrapActiveViews() {
   4540             final View[] activeViews = mActiveViews;
   4541             final boolean hasListener = mRecyclerListener != null;
   4542             final boolean multipleScraps = mViewTypeCount > 1;
   4543 
   4544             ArrayList<View> scrapViews = mCurrentScrap;
   4545             final int count = activeViews.length;
   4546             for (int i = count - 1; i >= 0; i--) {
   4547                 final View victim = activeViews[i];
   4548                 if (victim != null) {
   4549                     int whichScrap = ((AbsListView.LayoutParams) victim.getLayoutParams()).viewType;
   4550 
   4551                     activeViews[i] = null;
   4552 
   4553                     if (!shouldRecycleViewType(whichScrap)) {
   4554                         // Do not move views that should be ignored
   4555                         if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
   4556                             removeDetachedView(victim, false);
   4557                         }
   4558                         continue;
   4559                     }
   4560 
   4561                     if (multipleScraps) {
   4562                         scrapViews = mScrapViews[whichScrap];
   4563                     }
   4564                     victim.dispatchStartTemporaryDetach();
   4565                     scrapViews.add(victim);
   4566 
   4567                     if (hasListener) {
   4568                         mRecyclerListener.onMovedToScrapHeap(victim);
   4569                     }
   4570 
   4571                     if (ViewDebug.TRACE_RECYCLER) {
   4572                         ViewDebug.trace(victim,
   4573                                 ViewDebug.RecyclerTraceType.MOVE_FROM_ACTIVE_TO_SCRAP_HEAP,
   4574                                 mFirstActivePosition + i, -1);
   4575                     }
   4576                 }
   4577             }
   4578 
   4579             pruneScrapViews();
   4580         }
   4581 
   4582         /**
   4583          * Makes sure that the size of mScrapViews does not exceed the size of mActiveViews.
   4584          * (This can happen if an adapter does not recycle its views).
   4585          */
   4586         private void pruneScrapViews() {
   4587             final int maxViews = mActiveViews.length;
   4588             final int viewTypeCount = mViewTypeCount;
   4589             final ArrayList<View>[] scrapViews = mScrapViews;
   4590             for (int i = 0; i < viewTypeCount; ++i) {
   4591                 final ArrayList<View> scrapPile = scrapViews[i];
   4592                 int size = scrapPile.size();
   4593                 final int extras = size - maxViews;
   4594                 size--;
   4595                 for (int j = 0; j < extras; j++) {
   4596                     removeDetachedView(scrapPile.remove(size--), false);
   4597                 }
   4598             }
   4599         }
   4600 
   4601         /**
   4602          * Puts all views in the scrap heap into the supplied list.
   4603          */
   4604         void reclaimScrapViews(List<View> views) {
   4605             if (mViewTypeCount == 1) {
   4606                 views.addAll(mCurrentScrap);
   4607             } else {
   4608                 final int viewTypeCount = mViewTypeCount;
   4609                 final ArrayList<View>[] scrapViews = mScrapViews;
   4610                 for (int i = 0; i < viewTypeCount; ++i) {
   4611                     final ArrayList<View> scrapPile = scrapViews[i];
   4612                     views.addAll(scrapPile);
   4613                 }
   4614             }
   4615         }
   4616 
   4617         /**
   4618          * Updates the cache color hint of all known views.
   4619          *
   4620          * @param color The new cache color hint.
   4621          */
   4622         void setCacheColorHint(int color) {
   4623             if (mViewTypeCount == 1) {
   4624                 final ArrayList<View> scrap = mCurrentScrap;
   4625                 final int scrapCount = scrap.size();
   4626                 for (int i = 0; i < scrapCount; i++) {
   4627                     scrap.get(i).setDrawingCacheBackgroundColor(color);
   4628                 }
   4629             } else {
   4630                 final int typeCount = mViewTypeCount;
   4631                 for (int i = 0; i < typeCount; i++) {
   4632                     final ArrayList<View> scrap = mScrapViews[i];
   4633                     final int scrapCount = scrap.size();
   4634                     for (int j = 0; j < scrapCount; j++) {
   4635                         scrap.get(i).setDrawingCacheBackgroundColor(color);
   4636                     }
   4637                 }
   4638             }
   4639             // Just in case this is called during a layout pass
   4640             final View[] activeViews = mActiveViews;
   4641             final int count = activeViews.length;
   4642             for (int i = 0; i < count; ++i) {
   4643                 final View victim = activeViews[i];
   4644                 if (victim != null) {
   4645                     victim.setDrawingCacheBackgroundColor(color);
   4646                 }
   4647             }
   4648         }
   4649     }
   4650 }
   4651