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