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