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