mobile/android/base/widget/TwoWayView.java

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/mobile/android/base/widget/TwoWayView.java	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,6543 @@
     1.4 +/*
     1.5 + * Copyright (C) 2013 Lucas Rocha
     1.6 + *
     1.7 + * This code is based on bits and pieces of Android's AbsListView,
     1.8 + * Listview, and StaggeredGridView.
     1.9 + *
    1.10 + * Copyright (C) 2012 The Android Open Source Project
    1.11 + *
    1.12 + * Licensed under the Apache License, Version 2.0 (the "License");
    1.13 + * you may not use this file except in compliance with the License.
    1.14 + * You may obtain a copy of the License at
    1.15 + *
    1.16 + *      http://www.apache.org/licenses/LICENSE-2.0
    1.17 + *
    1.18 + * Unless required by applicable law or agreed to in writing, software
    1.19 + * distributed under the License is distributed on an "AS IS" BASIS,
    1.20 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    1.21 + * See the License for the specific language governing permissions and
    1.22 + * limitations under the License.
    1.23 + */
    1.24 +
    1.25 +package org.mozilla.gecko.widget;
    1.26 +
    1.27 +import org.mozilla.gecko.R;
    1.28 +
    1.29 +import android.annotation.TargetApi;
    1.30 +import android.content.Context;
    1.31 +import android.content.res.TypedArray;
    1.32 +import android.database.DataSetObserver;
    1.33 +import android.graphics.Canvas;
    1.34 +import android.graphics.Rect;
    1.35 +import android.graphics.drawable.Drawable;
    1.36 +import android.graphics.drawable.TransitionDrawable;
    1.37 +import android.os.Build;
    1.38 +import android.os.Bundle;
    1.39 +import android.os.Parcel;
    1.40 +import android.os.Parcelable;
    1.41 +import android.os.SystemClock;
    1.42 +import android.support.v4.util.LongSparseArray;
    1.43 +import android.support.v4.util.SparseArrayCompat;
    1.44 +import android.support.v4.view.AccessibilityDelegateCompat;
    1.45 +import android.support.v4.view.KeyEventCompat;
    1.46 +import android.support.v4.view.MotionEventCompat;
    1.47 +import android.support.v4.view.VelocityTrackerCompat;
    1.48 +import android.support.v4.view.ViewCompat;
    1.49 +import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
    1.50 +import android.support.v4.widget.EdgeEffectCompat;
    1.51 +import android.util.AttributeSet;
    1.52 +import android.util.Log;
    1.53 +import android.util.SparseBooleanArray;
    1.54 +import android.view.ContextMenu.ContextMenuInfo;
    1.55 +import android.view.FocusFinder;
    1.56 +import android.view.HapticFeedbackConstants;
    1.57 +import android.view.KeyEvent;
    1.58 +import android.view.MotionEvent;
    1.59 +import android.view.SoundEffectConstants;
    1.60 +import android.view.VelocityTracker;
    1.61 +import android.view.View;
    1.62 +import android.view.ViewConfiguration;
    1.63 +import android.view.ViewGroup;
    1.64 +import android.view.ViewParent;
    1.65 +import android.view.ViewTreeObserver;
    1.66 +import android.view.accessibility.AccessibilityEvent;
    1.67 +import android.view.accessibility.AccessibilityNodeInfo;
    1.68 +import android.widget.AdapterView;
    1.69 +import android.widget.Checkable;
    1.70 +import android.widget.ListAdapter;
    1.71 +import android.widget.Scroller;
    1.72 +
    1.73 +import java.util.ArrayList;
    1.74 +import java.util.List;
    1.75 +
    1.76 +/*
    1.77 + * Implementation Notes:
    1.78 + *
    1.79 + * Some terminology:
    1.80 + *
    1.81 + *     index    - index of the items that are currently visible
    1.82 + *     position - index of the items in the cursor
    1.83 + *
    1.84 + * Given the bi-directional nature of this view, the source code
    1.85 + * usually names variables with 'start' to mean 'top' or 'left'; and
    1.86 + * 'end' to mean 'bottom' or 'right', depending on the current
    1.87 + * orientation of the widget.
    1.88 + */
    1.89 +
    1.90 +/**
    1.91 + * A view that shows items in a vertical or horizontal scrolling list.
    1.92 + * The items come from the {@link ListAdapter} associated with this view.
    1.93 + */
    1.94 +public class TwoWayView extends AdapterView<ListAdapter> implements
    1.95 +        ViewTreeObserver.OnTouchModeChangeListener {
    1.96 +    private static final String LOGTAG = "TwoWayView";
    1.97 +
    1.98 +    private static final int NO_POSITION = -1;
    1.99 +    private static final int INVALID_POINTER = -1;
   1.100 +
   1.101 +    public static final int[] STATE_NOTHING = new int[] { 0 };
   1.102 +
   1.103 +    private static final int TOUCH_MODE_REST = -1;
   1.104 +    private static final int TOUCH_MODE_DOWN = 0;
   1.105 +    private static final int TOUCH_MODE_TAP = 1;
   1.106 +    private static final int TOUCH_MODE_DONE_WAITING = 2;
   1.107 +    private static final int TOUCH_MODE_DRAGGING = 3;
   1.108 +    private static final int TOUCH_MODE_FLINGING = 4;
   1.109 +    private static final int TOUCH_MODE_OVERSCROLL = 5;
   1.110 +
   1.111 +    private static final int TOUCH_MODE_UNKNOWN = -1;
   1.112 +    private static final int TOUCH_MODE_ON = 0;
   1.113 +    private static final int TOUCH_MODE_OFF = 1;
   1.114 +
   1.115 +    private static final int LAYOUT_NORMAL = 0;
   1.116 +    private static final int LAYOUT_FORCE_TOP = 1;
   1.117 +    private static final int LAYOUT_SET_SELECTION = 2;
   1.118 +    private static final int LAYOUT_FORCE_BOTTOM = 3;
   1.119 +    private static final int LAYOUT_SPECIFIC = 4;
   1.120 +    private static final int LAYOUT_SYNC = 5;
   1.121 +    private static final int LAYOUT_MOVE_SELECTION = 6;
   1.122 +
   1.123 +    private static final int SYNC_SELECTED_POSITION = 0;
   1.124 +    private static final int SYNC_FIRST_POSITION = 1;
   1.125 +
   1.126 +    private static final int SYNC_MAX_DURATION_MILLIS = 100;
   1.127 +
   1.128 +    private static final int CHECK_POSITION_SEARCH_DISTANCE = 20;
   1.129 +
   1.130 +    private static final float MAX_SCROLL_FACTOR = 0.33f;
   1.131 +
   1.132 +    private static final int MIN_SCROLL_PREVIEW_PIXELS = 10;
   1.133 +
   1.134 +    public static enum ChoiceMode {
   1.135 +        NONE,
   1.136 +        SINGLE,
   1.137 +        MULTIPLE
   1.138 +    }
   1.139 +
   1.140 +    public static enum Orientation {
   1.141 +        HORIZONTAL,
   1.142 +        VERTICAL;
   1.143 +    };
   1.144 +
   1.145 +    private ListAdapter mAdapter;
   1.146 +
   1.147 +    private boolean mIsVertical;
   1.148 +
   1.149 +    private int mItemMargin;
   1.150 +
   1.151 +    private boolean mInLayout;
   1.152 +    private boolean mBlockLayoutRequests;
   1.153 +
   1.154 +    private boolean mIsAttached;
   1.155 +
   1.156 +    private final RecycleBin mRecycler;
   1.157 +    private AdapterDataSetObserver mDataSetObserver;
   1.158 +
   1.159 +    private boolean mItemsCanFocus;
   1.160 +
   1.161 +    final boolean[] mIsScrap = new boolean[1];
   1.162 +
   1.163 +    private boolean mDataChanged;
   1.164 +    private int mItemCount;
   1.165 +    private int mOldItemCount;
   1.166 +    private boolean mHasStableIds;
   1.167 +    private boolean mAreAllItemsSelectable;
   1.168 +
   1.169 +    private int mFirstPosition;
   1.170 +    private int mSpecificStart;
   1.171 +
   1.172 +    private SavedState mPendingSync;
   1.173 +
   1.174 +    private final int mTouchSlop;
   1.175 +    private final int mMaximumVelocity;
   1.176 +    private final int mFlingVelocity;
   1.177 +    private float mLastTouchPos;
   1.178 +    private float mTouchRemainderPos;
   1.179 +    private int mActivePointerId;
   1.180 +
   1.181 +    private final Rect mTempRect;
   1.182 +
   1.183 +    private final ArrowScrollFocusResult mArrowScrollFocusResult;
   1.184 +
   1.185 +    private Rect mTouchFrame;
   1.186 +    private int mMotionPosition;
   1.187 +    private CheckForTap mPendingCheckForTap;
   1.188 +    private CheckForLongPress mPendingCheckForLongPress;
   1.189 +    private CheckForKeyLongPress mPendingCheckForKeyLongPress;
   1.190 +    private PerformClick mPerformClick;
   1.191 +    private Runnable mTouchModeReset;
   1.192 +    private int mResurrectToPosition;
   1.193 +
   1.194 +    private boolean mIsChildViewEnabled;
   1.195 +
   1.196 +    private boolean mDrawSelectorOnTop;
   1.197 +    private Drawable mSelector;
   1.198 +    private int mSelectorPosition;
   1.199 +    private final Rect mSelectorRect;
   1.200 +
   1.201 +    private int mOverScroll;
   1.202 +    private final int mOverscrollDistance;
   1.203 +
   1.204 +    private boolean mDesiredFocusableState;
   1.205 +    private boolean mDesiredFocusableInTouchModeState;
   1.206 +
   1.207 +    private SelectionNotifier mSelectionNotifier;
   1.208 +
   1.209 +    private boolean mNeedSync;
   1.210 +    private int mSyncMode;
   1.211 +    private int mSyncPosition;
   1.212 +    private long mSyncRowId;
   1.213 +    private long mSyncHeight;
   1.214 +    private int mSelectedStart;
   1.215 +
   1.216 +    private int mNextSelectedPosition;
   1.217 +    private long mNextSelectedRowId;
   1.218 +    private int mSelectedPosition;
   1.219 +    private long mSelectedRowId;
   1.220 +    private int mOldSelectedPosition;
   1.221 +    private long mOldSelectedRowId;
   1.222 +
   1.223 +    private ChoiceMode mChoiceMode;
   1.224 +    private int mCheckedItemCount;
   1.225 +    private SparseBooleanArray mCheckStates;
   1.226 +    LongSparseArray<Integer> mCheckedIdStates;
   1.227 +
   1.228 +    private ContextMenuInfo mContextMenuInfo;
   1.229 +
   1.230 +    private int mLayoutMode;
   1.231 +    private int mTouchMode;
   1.232 +    private int mLastTouchMode;
   1.233 +    private VelocityTracker mVelocityTracker;
   1.234 +    private final Scroller mScroller;
   1.235 +
   1.236 +    private EdgeEffectCompat mStartEdge;
   1.237 +    private EdgeEffectCompat mEndEdge;
   1.238 +
   1.239 +    private OnScrollListener mOnScrollListener;
   1.240 +    private int mLastScrollState;
   1.241 +
   1.242 +    private View mEmptyView;
   1.243 +
   1.244 +    private ListItemAccessibilityDelegate mAccessibilityDelegate;
   1.245 +
   1.246 +    private int mLastAccessibilityScrollEventFromIndex;
   1.247 +    private int mLastAccessibilityScrollEventToIndex;
   1.248 +
   1.249 +    public interface OnScrollListener {
   1.250 +
   1.251 +        /**
   1.252 +         * The view is not scrolling. Note navigating the list using the trackball counts as
   1.253 +         * being in the idle state since these transitions are not animated.
   1.254 +         */
   1.255 +        public static int SCROLL_STATE_IDLE = 0;
   1.256 +
   1.257 +        /**
   1.258 +         * The user is scrolling using touch, and their finger is still on the screen
   1.259 +         */
   1.260 +        public static int SCROLL_STATE_TOUCH_SCROLL = 1;
   1.261 +
   1.262 +        /**
   1.263 +         * The user had previously been scrolling using touch and had performed a fling. The
   1.264 +         * animation is now coasting to a stop
   1.265 +         */
   1.266 +        public static int SCROLL_STATE_FLING = 2;
   1.267 +
   1.268 +        /**
   1.269 +         * Callback method to be invoked while the list view or grid view is being scrolled. If the
   1.270 +         * view is being scrolled, this method will be called before the next frame of the scroll is
   1.271 +         * rendered. In particular, it will be called before any calls to
   1.272 +         * {@link Adapter#getView(int, View, ViewGroup)}.
   1.273 +         *
   1.274 +         * @param view The view whose scroll state is being reported
   1.275 +         *
   1.276 +         * @param scrollState The current scroll state. One of {@link #SCROLL_STATE_IDLE},
   1.277 +         * {@link #SCROLL_STATE_TOUCH_SCROLL} or {@link #SCROLL_STATE_IDLE}.
   1.278 +         */
   1.279 +        public void onScrollStateChanged(TwoWayView view, int scrollState);
   1.280 +
   1.281 +        /**
   1.282 +         * Callback method to be invoked when the list or grid has been scrolled. This will be
   1.283 +         * called after the scroll has completed
   1.284 +         * @param view The view whose scroll state is being reported
   1.285 +         * @param firstVisibleItem the index of the first visible cell (ignore if
   1.286 +         *        visibleItemCount == 0)
   1.287 +         * @param visibleItemCount the number of visible cells
   1.288 +         * @param totalItemCount the number of items in the list adaptor
   1.289 +         */
   1.290 +        public void onScroll(TwoWayView view, int firstVisibleItem, int visibleItemCount,
   1.291 +                int totalItemCount);
   1.292 +    }
   1.293 +
   1.294 +    /**
   1.295 +     * A RecyclerListener is used to receive a notification whenever a View is placed
   1.296 +     * inside the RecycleBin's scrap heap. This listener is used to free resources
   1.297 +     * associated to Views placed in the RecycleBin.
   1.298 +     *
   1.299 +     * @see TwoWayView.RecycleBin
   1.300 +     * @see TwoWayView#setRecyclerListener(TwoWayView.RecyclerListener)
   1.301 +     */
   1.302 +    public static interface RecyclerListener {
   1.303 +        /**
   1.304 +         * Indicates that the specified View was moved into the recycler's scrap heap.
   1.305 +         * The view is not displayed on screen any more and any expensive resource
   1.306 +         * associated with the view should be discarded.
   1.307 +         *
   1.308 +         * @param view
   1.309 +         */
   1.310 +        void onMovedToScrapHeap(View view);
   1.311 +    }
   1.312 +
   1.313 +    public TwoWayView(Context context) {
   1.314 +        this(context, null);
   1.315 +    }
   1.316 +
   1.317 +    public TwoWayView(Context context, AttributeSet attrs) {
   1.318 +        this(context, attrs, 0);
   1.319 +    }
   1.320 +
   1.321 +    public TwoWayView(Context context, AttributeSet attrs, int defStyle) {
   1.322 +        super(context, attrs, defStyle);
   1.323 +
   1.324 +        mNeedSync = false;
   1.325 +        mVelocityTracker = null;
   1.326 +
   1.327 +        mLayoutMode = LAYOUT_NORMAL;
   1.328 +        mTouchMode = TOUCH_MODE_REST;
   1.329 +        mLastTouchMode = TOUCH_MODE_UNKNOWN;
   1.330 +
   1.331 +        mIsAttached = false;
   1.332 +
   1.333 +        mContextMenuInfo = null;
   1.334 +
   1.335 +        mOnScrollListener = null;
   1.336 +        mLastScrollState = OnScrollListener.SCROLL_STATE_IDLE;
   1.337 +
   1.338 +        final ViewConfiguration vc = ViewConfiguration.get(context);
   1.339 +        mTouchSlop = vc.getScaledTouchSlop();
   1.340 +        mMaximumVelocity = vc.getScaledMaximumFlingVelocity();
   1.341 +        mFlingVelocity = vc.getScaledMinimumFlingVelocity();
   1.342 +        mOverscrollDistance = getScaledOverscrollDistance(vc);
   1.343 +
   1.344 +        mOverScroll = 0;
   1.345 +
   1.346 +        mScroller = new Scroller(context);
   1.347 +
   1.348 +        mIsVertical = true;
   1.349 +
   1.350 +        mItemsCanFocus = false;
   1.351 +
   1.352 +        mTempRect = new Rect();
   1.353 +
   1.354 +        mArrowScrollFocusResult = new ArrowScrollFocusResult();
   1.355 +
   1.356 +        mSelectorPosition = INVALID_POSITION;
   1.357 +
   1.358 +        mSelectorRect = new Rect();
   1.359 +        mSelectedStart = 0;
   1.360 +
   1.361 +        mResurrectToPosition = INVALID_POSITION;
   1.362 +
   1.363 +        mSelectedStart = 0;
   1.364 +        mNextSelectedPosition = INVALID_POSITION;
   1.365 +        mNextSelectedRowId = INVALID_ROW_ID;
   1.366 +        mSelectedPosition = INVALID_POSITION;
   1.367 +        mSelectedRowId = INVALID_ROW_ID;
   1.368 +        mOldSelectedPosition = INVALID_POSITION;
   1.369 +        mOldSelectedRowId = INVALID_ROW_ID;
   1.370 +
   1.371 +        mChoiceMode = ChoiceMode.NONE;
   1.372 +        mCheckedItemCount = 0;
   1.373 +        mCheckedIdStates = null;
   1.374 +        mCheckStates = null;
   1.375 +
   1.376 +        mRecycler = new RecycleBin();
   1.377 +        mDataSetObserver = null;
   1.378 +
   1.379 +        mAreAllItemsSelectable = true;
   1.380 +
   1.381 +        mStartEdge = null;
   1.382 +        mEndEdge = null;
   1.383 +
   1.384 +        setClickable(true);
   1.385 +        setFocusableInTouchMode(true);
   1.386 +        setWillNotDraw(false);
   1.387 +        setAlwaysDrawnWithCacheEnabled(false);
   1.388 +        setWillNotDraw(false);
   1.389 +        setClipToPadding(false);
   1.390 +
   1.391 +        ViewCompat.setOverScrollMode(this, ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS);
   1.392 +
   1.393 +        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TwoWayView, defStyle, 0);
   1.394 +        initializeScrollbars(a);
   1.395 +
   1.396 +        mDrawSelectorOnTop = a.getBoolean(
   1.397 +                R.styleable.TwoWayView_android_drawSelectorOnTop, false);
   1.398 +
   1.399 +        Drawable d = a.getDrawable(R.styleable.TwoWayView_android_listSelector);
   1.400 +        if (d != null) {
   1.401 +            setSelector(d);
   1.402 +        }
   1.403 +
   1.404 +        int orientation = a.getInt(R.styleable.TwoWayView_android_orientation, -1);
   1.405 +        if (orientation >= 0) {
   1.406 +            setOrientation(Orientation.values()[orientation]);
   1.407 +        }
   1.408 +
   1.409 +        int choiceMode = a.getInt(R.styleable.TwoWayView_android_choiceMode, -1);
   1.410 +        if (choiceMode >= 0) {
   1.411 +            setChoiceMode(ChoiceMode.values()[choiceMode]);
   1.412 +        }
   1.413 +
   1.414 +        a.recycle();
   1.415 +
   1.416 +        updateScrollbarsDirection();
   1.417 +    }
   1.418 +
   1.419 +    public void setOrientation(Orientation orientation) {
   1.420 +        final boolean isVertical = (orientation.compareTo(Orientation.VERTICAL) == 0);
   1.421 +        if (mIsVertical == isVertical) {
   1.422 +            return;
   1.423 +        }
   1.424 +
   1.425 +        mIsVertical = isVertical;
   1.426 +
   1.427 +        updateScrollbarsDirection();
   1.428 +        resetState();
   1.429 +        mRecycler.clear();
   1.430 +
   1.431 +        requestLayout();
   1.432 +    }
   1.433 +
   1.434 +    public Orientation getOrientation() {
   1.435 +        return (mIsVertical ? Orientation.VERTICAL : Orientation.HORIZONTAL);
   1.436 +    }
   1.437 +
   1.438 +    public void setItemMargin(int itemMargin) {
   1.439 +        if (mItemMargin == itemMargin) {
   1.440 +            return;
   1.441 +        }
   1.442 +
   1.443 +        mItemMargin = itemMargin;
   1.444 +        requestLayout();
   1.445 +    }
   1.446 +
   1.447 +    public int getItemMargin() {
   1.448 +        return mItemMargin;
   1.449 +    }
   1.450 +
   1.451 +    /**
   1.452 +     * Indicates that the views created by the ListAdapter can contain focusable
   1.453 +     * items.
   1.454 +     *
   1.455 +     * @param itemsCanFocus true if items can get focus, false otherwise
   1.456 +     */
   1.457 +    public void setItemsCanFocus(boolean itemsCanFocus) {
   1.458 +        mItemsCanFocus = itemsCanFocus;
   1.459 +        if (!itemsCanFocus) {
   1.460 +            setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
   1.461 +        }
   1.462 +    }
   1.463 +
   1.464 +    /**
   1.465 +     * @return Whether the views created by the ListAdapter can contain focusable
   1.466 +     * items.
   1.467 +     */
   1.468 +    public boolean getItemsCanFocus() {
   1.469 +        return mItemsCanFocus;
   1.470 +    }
   1.471 +
   1.472 +    /**
   1.473 +     * Set the listener that will receive notifications every time the list scrolls.
   1.474 +     *
   1.475 +     * @param l the scroll listener
   1.476 +     */
   1.477 +    public void setOnScrollListener(OnScrollListener l) {
   1.478 +        mOnScrollListener = l;
   1.479 +        invokeOnItemScrollListener();
   1.480 +    }
   1.481 +
   1.482 +    /**
   1.483 +     * Sets the recycler listener to be notified whenever a View is set aside in
   1.484 +     * the recycler for later reuse. This listener can be used to free resources
   1.485 +     * associated to the View.
   1.486 +     *
   1.487 +     * @param listener The recycler listener to be notified of views set aside
   1.488 +     *        in the recycler.
   1.489 +     *
   1.490 +     * @see TwoWayView.RecycleBin
   1.491 +     * @see TwoWayView.RecyclerListener
   1.492 +     */
   1.493 +    public void setRecyclerListener(RecyclerListener l) {
   1.494 +        mRecycler.mRecyclerListener = l;
   1.495 +    }
   1.496 +
   1.497 +    /**
   1.498 +     * Controls whether the selection highlight drawable should be drawn on top of the item or
   1.499 +     * behind it.
   1.500 +     *
   1.501 +     * @param onTop If true, the selector will be drawn on the item it is highlighting. The default
   1.502 +     *        is false.
   1.503 +     *
   1.504 +     * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop
   1.505 +     */
   1.506 +    public void setDrawSelectorOnTop(boolean drawSelectorOnTop) {
   1.507 +        mDrawSelectorOnTop = drawSelectorOnTop;
   1.508 +    }
   1.509 +
   1.510 +    /**
   1.511 +     * Set a Drawable that should be used to highlight the currently selected item.
   1.512 +     *
   1.513 +     * @param resID A Drawable resource to use as the selection highlight.
   1.514 +     *
   1.515 +     * @attr ref android.R.styleable#AbsListView_listSelector
   1.516 +     */
   1.517 +    public void setSelector(int resID) {
   1.518 +        setSelector(getResources().getDrawable(resID));
   1.519 +    }
   1.520 +
   1.521 +    /**
   1.522 +     * Set a Drawable that should be used to highlight the currently selected item.
   1.523 +     *
   1.524 +     * @param selector A Drawable to use as the selection highlight.
   1.525 +     *
   1.526 +     * @attr ref android.R.styleable#AbsListView_listSelector
   1.527 +     */
   1.528 +    public void setSelector(Drawable selector) {
   1.529 +        if (mSelector != null) {
   1.530 +            mSelector.setCallback(null);
   1.531 +            unscheduleDrawable(mSelector);
   1.532 +        }
   1.533 +
   1.534 +        mSelector = selector;
   1.535 +        Rect padding = new Rect();
   1.536 +        selector.getPadding(padding);
   1.537 +
   1.538 +        selector.setCallback(this);
   1.539 +        updateSelectorState();
   1.540 +    }
   1.541 +
   1.542 +    /**
   1.543 +     * Returns the selector {@link android.graphics.drawable.Drawable} that is used to draw the
   1.544 +     * selection in the list.
   1.545 +     *
   1.546 +     * @return the drawable used to display the selector
   1.547 +     */
   1.548 +    public Drawable getSelector() {
   1.549 +        return mSelector;
   1.550 +    }
   1.551 +
   1.552 +    /**
   1.553 +     * {@inheritDoc}
   1.554 +     */
   1.555 +    @Override
   1.556 +    public int getSelectedItemPosition() {
   1.557 +        return mNextSelectedPosition;
   1.558 +    }
   1.559 +
   1.560 +    /**
   1.561 +     * {@inheritDoc}
   1.562 +     */
   1.563 +    @Override
   1.564 +    public long getSelectedItemId() {
   1.565 +        return mNextSelectedRowId;
   1.566 +    }
   1.567 +
   1.568 +    /**
   1.569 +     * Returns the number of items currently selected. This will only be valid
   1.570 +     * if the choice mode is not {@link #CHOICE_MODE_NONE} (default).
   1.571 +     *
   1.572 +     * <p>To determine the specific items that are currently selected, use one of
   1.573 +     * the <code>getChecked*</code> methods.
   1.574 +     *
   1.575 +     * @return The number of items currently selected
   1.576 +     *
   1.577 +     * @see #getCheckedItemPosition()
   1.578 +     * @see #getCheckedItemPositions()
   1.579 +     * @see #getCheckedItemIds()
   1.580 +     */
   1.581 +    public int getCheckedItemCount() {
   1.582 +        return mCheckedItemCount;
   1.583 +    }
   1.584 +
   1.585 +    /**
   1.586 +     * Returns the checked state of the specified position. The result is only
   1.587 +     * valid if the choice mode has been set to {@link #CHOICE_MODE_SINGLE}
   1.588 +     * or {@link #CHOICE_MODE_MULTIPLE}.
   1.589 +     *
   1.590 +     * @param position The item whose checked state to return
   1.591 +     * @return The item's checked state or <code>false</code> if choice mode
   1.592 +     *         is invalid
   1.593 +     *
   1.594 +     * @see #setChoiceMode(int)
   1.595 +     */
   1.596 +    public boolean isItemChecked(int position) {
   1.597 +        if (mChoiceMode.compareTo(ChoiceMode.NONE) == 0 && mCheckStates != null) {
   1.598 +            return mCheckStates.get(position);
   1.599 +        }
   1.600 +
   1.601 +        return false;
   1.602 +    }
   1.603 +
   1.604 +    /**
   1.605 +     * Returns the currently checked item. The result is only valid if the choice
   1.606 +     * mode has been set to {@link #CHOICE_MODE_SINGLE}.
   1.607 +     *
   1.608 +     * @return The position of the currently checked item or
   1.609 +     *         {@link #INVALID_POSITION} if nothing is selected
   1.610 +     *
   1.611 +     * @see #setChoiceMode(int)
   1.612 +     */
   1.613 +    public int getCheckedItemPosition() {
   1.614 +        if (mChoiceMode.compareTo(ChoiceMode.SINGLE) == 0 &&
   1.615 +                mCheckStates != null && mCheckStates.size() == 1) {
   1.616 +            return mCheckStates.keyAt(0);
   1.617 +        }
   1.618 +
   1.619 +        return INVALID_POSITION;
   1.620 +    }
   1.621 +
   1.622 +    /**
   1.623 +     * Returns the set of checked items in the list. The result is only valid if
   1.624 +     * the choice mode has not been set to {@link #CHOICE_MODE_NONE}.
   1.625 +     *
   1.626 +     * @return  A SparseBooleanArray which will return true for each call to
   1.627 +     *          get(int position) where position is a position in the list,
   1.628 +     *          or <code>null</code> if the choice mode is set to
   1.629 +     *          {@link #CHOICE_MODE_NONE}.
   1.630 +     */
   1.631 +    public SparseBooleanArray getCheckedItemPositions() {
   1.632 +        if (mChoiceMode.compareTo(ChoiceMode.NONE) != 0) {
   1.633 +            return mCheckStates;
   1.634 +        }
   1.635 +
   1.636 +        return null;
   1.637 +    }
   1.638 +
   1.639 +    /**
   1.640 +     * Returns the set of checked items ids. The result is only valid if the
   1.641 +     * choice mode has not been set to {@link #CHOICE_MODE_NONE} and the adapter
   1.642 +     * has stable IDs. ({@link ListAdapter#hasStableIds()} == {@code true})
   1.643 +     *
   1.644 +     * @return A new array which contains the id of each checked item in the
   1.645 +     *         list.
   1.646 +     */
   1.647 +    public long[] getCheckedItemIds() {
   1.648 +        if (mChoiceMode.compareTo(ChoiceMode.NONE) == 0 ||
   1.649 +                mCheckedIdStates == null || mAdapter == null) {
   1.650 +            return new long[0];
   1.651 +        }
   1.652 +
   1.653 +        final LongSparseArray<Integer> idStates = mCheckedIdStates;
   1.654 +        final int count = idStates.size();
   1.655 +        final long[] ids = new long[count];
   1.656 +
   1.657 +        for (int i = 0; i < count; i++) {
   1.658 +            ids[i] = idStates.keyAt(i);
   1.659 +        }
   1.660 +
   1.661 +        return ids;
   1.662 +    }
   1.663 +
   1.664 +    /**
   1.665 +     * Sets the checked state of the specified position. The is only valid if
   1.666 +     * the choice mode has been set to {@link #CHOICE_MODE_SINGLE} or
   1.667 +     * {@link #CHOICE_MODE_MULTIPLE}.
   1.668 +     *
   1.669 +     * @param position The item whose checked state is to be checked
   1.670 +     * @param value The new checked state for the item
   1.671 +     */
   1.672 +    public void setItemChecked(int position, boolean value) {
   1.673 +        if (mChoiceMode.compareTo(ChoiceMode.NONE) == 0) {
   1.674 +            return;
   1.675 +        }
   1.676 +
   1.677 +        if (mChoiceMode.compareTo(ChoiceMode.MULTIPLE) == 0) {
   1.678 +            boolean oldValue = mCheckStates.get(position);
   1.679 +            mCheckStates.put(position, value);
   1.680 +
   1.681 +            if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
   1.682 +                if (value) {
   1.683 +                    mCheckedIdStates.put(mAdapter.getItemId(position), position);
   1.684 +                } else {
   1.685 +                    mCheckedIdStates.delete(mAdapter.getItemId(position));
   1.686 +                }
   1.687 +            }
   1.688 +
   1.689 +            if (oldValue != value) {
   1.690 +                if (value) {
   1.691 +                    mCheckedItemCount++;
   1.692 +                } else {
   1.693 +                    mCheckedItemCount--;
   1.694 +                }
   1.695 +            }
   1.696 +        } else {
   1.697 +            boolean updateIds = mCheckedIdStates != null && mAdapter.hasStableIds();
   1.698 +
   1.699 +            // Clear all values if we're checking something, or unchecking the currently
   1.700 +            // selected item
   1.701 +            if (value || isItemChecked(position)) {
   1.702 +                mCheckStates.clear();
   1.703 +
   1.704 +                if (updateIds) {
   1.705 +                    mCheckedIdStates.clear();
   1.706 +                }
   1.707 +            }
   1.708 +
   1.709 +            // This may end up selecting the value we just cleared but this way
   1.710 +            // we ensure length of mCheckStates is 1, a fact getCheckedItemPosition relies on
   1.711 +            if (value) {
   1.712 +                mCheckStates.put(position, true);
   1.713 +
   1.714 +                if (updateIds) {
   1.715 +                    mCheckedIdStates.put(mAdapter.getItemId(position), position);
   1.716 +                }
   1.717 +
   1.718 +                mCheckedItemCount = 1;
   1.719 +            } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
   1.720 +                mCheckedItemCount = 0;
   1.721 +            }
   1.722 +        }
   1.723 +
   1.724 +        // Do not generate a data change while we are in the layout phase
   1.725 +        if (!mInLayout && !mBlockLayoutRequests) {
   1.726 +            mDataChanged = true;
   1.727 +            rememberSyncState();
   1.728 +            requestLayout();
   1.729 +        }
   1.730 +    }
   1.731 +
   1.732 +    /**
   1.733 +     * Clear any choices previously set
   1.734 +     */
   1.735 +    public void clearChoices() {
   1.736 +        if (mCheckStates != null) {
   1.737 +            mCheckStates.clear();
   1.738 +        }
   1.739 +
   1.740 +        if (mCheckedIdStates != null) {
   1.741 +            mCheckedIdStates.clear();
   1.742 +        }
   1.743 +
   1.744 +        mCheckedItemCount = 0;
   1.745 +    }
   1.746 +
   1.747 +    /**
   1.748 +     * @see #setChoiceMode(int)
   1.749 +     *
   1.750 +     * @return The current choice mode
   1.751 +     */
   1.752 +    public ChoiceMode getChoiceMode() {
   1.753 +        return mChoiceMode;
   1.754 +    }
   1.755 +
   1.756 +    /**
   1.757 +     * Defines the choice behavior for the List. By default, Lists do not have any choice behavior
   1.758 +     * ({@link #CHOICE_MODE_NONE}). By setting the choiceMode to {@link #CHOICE_MODE_SINGLE}, the
   1.759 +     * List allows up to one item to  be in a chosen state. By setting the choiceMode to
   1.760 +     * {@link #CHOICE_MODE_MULTIPLE}, the list allows any number of items to be chosen.
   1.761 +     *
   1.762 +     * @param choiceMode One of {@link #CHOICE_MODE_NONE}, {@link #CHOICE_MODE_SINGLE}, or
   1.763 +     * {@link #CHOICE_MODE_MULTIPLE}
   1.764 +     */
   1.765 +    public void setChoiceMode(ChoiceMode choiceMode) {
   1.766 +        mChoiceMode = choiceMode;
   1.767 +
   1.768 +        if (mChoiceMode.compareTo(ChoiceMode.NONE) != 0) {
   1.769 +            if (mCheckStates == null) {
   1.770 +                mCheckStates = new SparseBooleanArray();
   1.771 +            }
   1.772 +
   1.773 +            if (mCheckedIdStates == null && mAdapter != null && mAdapter.hasStableIds()) {
   1.774 +                mCheckedIdStates = new LongSparseArray<Integer>();
   1.775 +            }
   1.776 +        }
   1.777 +    }
   1.778 +
   1.779 +    @Override
   1.780 +    public ListAdapter getAdapter() {
   1.781 +        return mAdapter;
   1.782 +    }
   1.783 +
   1.784 +    @Override
   1.785 +    public void setAdapter(ListAdapter adapter) {
   1.786 +        if (mAdapter != null && mDataSetObserver != null) {
   1.787 +            mAdapter.unregisterDataSetObserver(mDataSetObserver);
   1.788 +        }
   1.789 +
   1.790 +        resetState();
   1.791 +        mRecycler.clear();
   1.792 +
   1.793 +        mAdapter = adapter;
   1.794 +        mDataChanged = true;
   1.795 +
   1.796 +        mOldSelectedPosition = INVALID_POSITION;
   1.797 +        mOldSelectedRowId = INVALID_ROW_ID;
   1.798 +
   1.799 +        if (mCheckStates != null) {
   1.800 +            mCheckStates.clear();
   1.801 +        }
   1.802 +
   1.803 +        if (mCheckedIdStates != null) {
   1.804 +            mCheckedIdStates.clear();
   1.805 +        }
   1.806 +
   1.807 +        if (mAdapter != null) {
   1.808 +            mOldItemCount = mItemCount;
   1.809 +            mItemCount = adapter.getCount();
   1.810 +
   1.811 +            mDataSetObserver = new AdapterDataSetObserver();
   1.812 +            mAdapter.registerDataSetObserver(mDataSetObserver);
   1.813 +
   1.814 +            mRecycler.setViewTypeCount(adapter.getViewTypeCount());
   1.815 +
   1.816 +            mHasStableIds = adapter.hasStableIds();
   1.817 +            mAreAllItemsSelectable = adapter.areAllItemsEnabled();
   1.818 +
   1.819 +            if (mChoiceMode.compareTo(ChoiceMode.NONE) != 0 && mHasStableIds &&
   1.820 +                    mCheckedIdStates == null) {
   1.821 +                mCheckedIdStates = new LongSparseArray<Integer>();
   1.822 +            }
   1.823 +
   1.824 +            final int position = lookForSelectablePosition(0);
   1.825 +            setSelectedPositionInt(position);
   1.826 +            setNextSelectedPositionInt(position);
   1.827 +
   1.828 +            if (mItemCount == 0) {
   1.829 +                checkSelectionChanged();
   1.830 +            }
   1.831 +        } else {
   1.832 +            mItemCount = 0;
   1.833 +            mHasStableIds = false;
   1.834 +            mAreAllItemsSelectable = true;
   1.835 +
   1.836 +            checkSelectionChanged();
   1.837 +        }
   1.838 +
   1.839 +        checkFocus();
   1.840 +        requestLayout();
   1.841 +    }
   1.842 +
   1.843 +    @Override
   1.844 +    public int getFirstVisiblePosition() {
   1.845 +        return mFirstPosition;
   1.846 +    }
   1.847 +
   1.848 +    @Override
   1.849 +    public int getLastVisiblePosition() {
   1.850 +        return mFirstPosition + getChildCount() - 1;
   1.851 +    }
   1.852 +
   1.853 +    @Override
   1.854 +    public int getCount() {
   1.855 +        return mItemCount;
   1.856 +    }
   1.857 +
   1.858 +    @Override
   1.859 +    public int getPositionForView(View view) {
   1.860 +        View child = view;
   1.861 +        try {
   1.862 +            View v;
   1.863 +            while (!(v = (View) child.getParent()).equals(this)) {
   1.864 +                child = v;
   1.865 +            }
   1.866 +        } catch (ClassCastException e) {
   1.867 +            // We made it up to the window without find this list view
   1.868 +            return INVALID_POSITION;
   1.869 +        }
   1.870 +
   1.871 +        // Search the children for the list item
   1.872 +        final int childCount = getChildCount();
   1.873 +        for (int i = 0; i < childCount; i++) {
   1.874 +            if (getChildAt(i).equals(child)) {
   1.875 +                return mFirstPosition + i;
   1.876 +            }
   1.877 +        }
   1.878 +
   1.879 +        // Child not found!
   1.880 +        return INVALID_POSITION;
   1.881 +    }
   1.882 +
   1.883 +    @Override
   1.884 +    public void getFocusedRect(Rect r) {
   1.885 +        View view = getSelectedView();
   1.886 +
   1.887 +        if (view != null && view.getParent() == this) {
   1.888 +            // The focused rectangle of the selected view offset into the
   1.889 +            // coordinate space of this view.
   1.890 +            view.getFocusedRect(r);
   1.891 +            offsetDescendantRectToMyCoords(view, r);
   1.892 +        } else {
   1.893 +            super.getFocusedRect(r);
   1.894 +        }
   1.895 +    }
   1.896 +
   1.897 +    @Override
   1.898 +    protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
   1.899 +        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
   1.900 +
   1.901 +        if (gainFocus && mSelectedPosition < 0 && !isInTouchMode()) {
   1.902 +            if (!mIsAttached && mAdapter != null) {
   1.903 +                // Data may have changed while we were detached and it's valid
   1.904 +                // to change focus while detached. Refresh so we don't die.
   1.905 +                mDataChanged = true;
   1.906 +                mOldItemCount = mItemCount;
   1.907 +                mItemCount = mAdapter.getCount();
   1.908 +            }
   1.909 +
   1.910 +            resurrectSelection();
   1.911 +        }
   1.912 +
   1.913 +        final ListAdapter adapter = mAdapter;
   1.914 +        int closetChildIndex = INVALID_POSITION;
   1.915 +        int closestChildStart = 0;
   1.916 +
   1.917 +        if (adapter != null && gainFocus && previouslyFocusedRect != null) {
   1.918 +            previouslyFocusedRect.offset(getScrollX(), getScrollY());
   1.919 +
   1.920 +            // Don't cache the result of getChildCount or mFirstPosition here,
   1.921 +            // it could change in layoutChildren.
   1.922 +            if (adapter.getCount() < getChildCount() + mFirstPosition) {
   1.923 +                mLayoutMode = LAYOUT_NORMAL;
   1.924 +                layoutChildren();
   1.925 +            }
   1.926 +
   1.927 +            // Figure out which item should be selected based on previously
   1.928 +            // focused rect.
   1.929 +            Rect otherRect = mTempRect;
   1.930 +            int minDistance = Integer.MAX_VALUE;
   1.931 +            final int childCount = getChildCount();
   1.932 +            final int firstPosition = mFirstPosition;
   1.933 +
   1.934 +            for (int i = 0; i < childCount; i++) {
   1.935 +                // Only consider selectable views
   1.936 +                if (!adapter.isEnabled(firstPosition + i)) {
   1.937 +                    continue;
   1.938 +                }
   1.939 +
   1.940 +                View other = getChildAt(i);
   1.941 +                other.getDrawingRect(otherRect);
   1.942 +                offsetDescendantRectToMyCoords(other, otherRect);
   1.943 +                int distance = getDistance(previouslyFocusedRect, otherRect, direction);
   1.944 +
   1.945 +                if (distance < minDistance) {
   1.946 +                    minDistance = distance;
   1.947 +                    closetChildIndex = i;
   1.948 +                    closestChildStart = (mIsVertical ? other.getTop() : other.getLeft());
   1.949 +                }
   1.950 +            }
   1.951 +        }
   1.952 +
   1.953 +        if (closetChildIndex >= 0) {
   1.954 +            setSelectionFromOffset(closetChildIndex + mFirstPosition, closestChildStart);
   1.955 +        } else {
   1.956 +            requestLayout();
   1.957 +        }
   1.958 +    }
   1.959 +
   1.960 +    @Override
   1.961 +    protected void onAttachedToWindow() {
   1.962 +        super.onAttachedToWindow();
   1.963 +
   1.964 +        final ViewTreeObserver treeObserver = getViewTreeObserver();
   1.965 +        treeObserver.addOnTouchModeChangeListener(this);
   1.966 +
   1.967 +        if (mAdapter != null && mDataSetObserver == null) {
   1.968 +            mDataSetObserver = new AdapterDataSetObserver();
   1.969 +            mAdapter.registerDataSetObserver(mDataSetObserver);
   1.970 +
   1.971 +            // Data may have changed while we were detached. Refresh.
   1.972 +            mDataChanged = true;
   1.973 +            mOldItemCount = mItemCount;
   1.974 +            mItemCount = mAdapter.getCount();
   1.975 +        }
   1.976 +
   1.977 +        mIsAttached = true;
   1.978 +    }
   1.979 +
   1.980 +    @Override
   1.981 +    protected void onDetachedFromWindow() {
   1.982 +        super.onDetachedFromWindow();
   1.983 +
   1.984 +        // Detach any view left in the scrap heap
   1.985 +        mRecycler.clear();
   1.986 +
   1.987 +        final ViewTreeObserver treeObserver = getViewTreeObserver();
   1.988 +        treeObserver.removeOnTouchModeChangeListener(this);
   1.989 +
   1.990 +        if (mAdapter != null) {
   1.991 +            mAdapter.unregisterDataSetObserver(mDataSetObserver);
   1.992 +            mDataSetObserver = null;
   1.993 +        }
   1.994 +
   1.995 +        if (mPerformClick != null) {
   1.996 +            removeCallbacks(mPerformClick);
   1.997 +        }
   1.998 +
   1.999 +        if (mTouchModeReset != null) {
  1.1000 +            removeCallbacks(mTouchModeReset);
  1.1001 +            mTouchModeReset.run();
  1.1002 +        }
  1.1003 +
  1.1004 +        mIsAttached = false;
  1.1005 +    }
  1.1006 +
  1.1007 +    @Override
  1.1008 +    public void onWindowFocusChanged(boolean hasWindowFocus) {
  1.1009 +        super.onWindowFocusChanged(hasWindowFocus);
  1.1010 +
  1.1011 +        final int touchMode = isInTouchMode() ? TOUCH_MODE_ON : TOUCH_MODE_OFF;
  1.1012 +
  1.1013 +        if (!hasWindowFocus) {
  1.1014 +            if (touchMode == TOUCH_MODE_OFF) {
  1.1015 +                // Remember the last selected element
  1.1016 +                mResurrectToPosition = mSelectedPosition;
  1.1017 +            }
  1.1018 +        } else {
  1.1019 +            // If we changed touch mode since the last time we had focus
  1.1020 +            if (touchMode != mLastTouchMode && mLastTouchMode != TOUCH_MODE_UNKNOWN) {
  1.1021 +                // If we come back in trackball mode, we bring the selection back
  1.1022 +                if (touchMode == TOUCH_MODE_OFF) {
  1.1023 +                    // This will trigger a layout
  1.1024 +                    resurrectSelection();
  1.1025 +
  1.1026 +                // If we come back in touch mode, then we want to hide the selector
  1.1027 +                } else {
  1.1028 +                    hideSelector();
  1.1029 +                    mLayoutMode = LAYOUT_NORMAL;
  1.1030 +                    layoutChildren();
  1.1031 +                }
  1.1032 +            }
  1.1033 +        }
  1.1034 +
  1.1035 +        mLastTouchMode = touchMode;
  1.1036 +    }
  1.1037 +
  1.1038 +    @Override
  1.1039 +    protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
  1.1040 +        boolean needsInvalidate = false;
  1.1041 +
  1.1042 +        if (mIsVertical && mOverScroll != scrollY) {
  1.1043 +            onScrollChanged(getScrollX(), scrollY, getScrollX(), mOverScroll);
  1.1044 +            mOverScroll = scrollY;
  1.1045 +            needsInvalidate = true;
  1.1046 +        } else if (!mIsVertical && mOverScroll != scrollX) {
  1.1047 +            onScrollChanged(scrollX, getScrollY(), mOverScroll, getScrollY());
  1.1048 +            mOverScroll = scrollX;
  1.1049 +            needsInvalidate = true;
  1.1050 +        }
  1.1051 +
  1.1052 +        if (needsInvalidate) {
  1.1053 +            invalidate();
  1.1054 +            awakenScrollbarsInternal();
  1.1055 +        }
  1.1056 +    }
  1.1057 +
  1.1058 +    @TargetApi(9)
  1.1059 +    private boolean overScrollByInternal(int deltaX, int deltaY,
  1.1060 +            int scrollX, int scrollY,
  1.1061 +            int scrollRangeX, int scrollRangeY,
  1.1062 +            int maxOverScrollX, int maxOverScrollY,
  1.1063 +            boolean isTouchEvent) {
  1.1064 +        if (Build.VERSION.SDK_INT < 9) {
  1.1065 +            return false;
  1.1066 +        }
  1.1067 +
  1.1068 +        return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX,
  1.1069 +                scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
  1.1070 +    }
  1.1071 +
  1.1072 +    @Override
  1.1073 +    @TargetApi(9)
  1.1074 +    public void setOverScrollMode(int mode) {
  1.1075 +        if (Build.VERSION.SDK_INT < 9) {
  1.1076 +            return;
  1.1077 +        }
  1.1078 +
  1.1079 +        if (mode != ViewCompat.OVER_SCROLL_NEVER) {
  1.1080 +            if (mStartEdge == null) {
  1.1081 +                Context context = getContext();
  1.1082 +
  1.1083 +                mStartEdge = new EdgeEffectCompat(context);
  1.1084 +                mEndEdge = new EdgeEffectCompat(context);
  1.1085 +            }
  1.1086 +        } else {
  1.1087 +            mStartEdge = null;
  1.1088 +            mEndEdge = null;
  1.1089 +        }
  1.1090 +
  1.1091 +        super.setOverScrollMode(mode);
  1.1092 +    }
  1.1093 +
  1.1094 +    public int pointToPosition(int x, int y) {
  1.1095 +        Rect frame = mTouchFrame;
  1.1096 +        if (frame == null) {
  1.1097 +            mTouchFrame = new Rect();
  1.1098 +            frame = mTouchFrame;
  1.1099 +        }
  1.1100 +
  1.1101 +        final int count = getChildCount();
  1.1102 +        for (int i = count - 1; i >= 0; i--) {
  1.1103 +            final View child = getChildAt(i);
  1.1104 +
  1.1105 +            if (child.getVisibility() == View.VISIBLE) {
  1.1106 +                child.getHitRect(frame);
  1.1107 +
  1.1108 +                if (frame.contains(x, y)) {
  1.1109 +                    return mFirstPosition + i;
  1.1110 +                }
  1.1111 +            }
  1.1112 +        }
  1.1113 +        return INVALID_POSITION;
  1.1114 +    }
  1.1115 +
  1.1116 +    @Override
  1.1117 +    protected int computeVerticalScrollExtent() {
  1.1118 +        final int count = getChildCount();
  1.1119 +        if (count == 0) {
  1.1120 +            return 0;
  1.1121 +        }
  1.1122 +
  1.1123 +        int extent = count * 100;
  1.1124 +
  1.1125 +        View child = getChildAt(0);
  1.1126 +        final int childTop = child.getTop();
  1.1127 +
  1.1128 +        int childHeight = child.getHeight();
  1.1129 +        if (childHeight > 0) {
  1.1130 +            extent += (childTop * 100) / childHeight;
  1.1131 +        }
  1.1132 +
  1.1133 +        child = getChildAt(count - 1);
  1.1134 +        final int childBottom = child.getBottom();
  1.1135 +
  1.1136 +        childHeight = child.getHeight();
  1.1137 +        if (childHeight > 0) {
  1.1138 +            extent -= ((childBottom - getHeight()) * 100) / childHeight;
  1.1139 +        }
  1.1140 +
  1.1141 +        return extent;
  1.1142 +    }
  1.1143 +
  1.1144 +    @Override
  1.1145 +    protected int computeHorizontalScrollExtent() {
  1.1146 +        final int count = getChildCount();
  1.1147 +        if (count == 0) {
  1.1148 +            return 0;
  1.1149 +        }
  1.1150 +
  1.1151 +        int extent = count * 100;
  1.1152 +
  1.1153 +        View child = getChildAt(0);
  1.1154 +        final int childLeft = child.getLeft();
  1.1155 +
  1.1156 +        int childWidth = child.getWidth();
  1.1157 +        if (childWidth > 0) {
  1.1158 +            extent += (childLeft * 100) / childWidth;
  1.1159 +        }
  1.1160 +
  1.1161 +        child = getChildAt(count - 1);
  1.1162 +        final int childRight = child.getRight();
  1.1163 +
  1.1164 +        childWidth = child.getWidth();
  1.1165 +        if (childWidth > 0) {
  1.1166 +            extent -= ((childRight - getWidth()) * 100) / childWidth;
  1.1167 +        }
  1.1168 +
  1.1169 +        return extent;
  1.1170 +    }
  1.1171 +
  1.1172 +    @Override
  1.1173 +    protected int computeVerticalScrollOffset() {
  1.1174 +        final int firstPosition = mFirstPosition;
  1.1175 +        final int childCount = getChildCount();
  1.1176 +
  1.1177 +        if (firstPosition < 0 || childCount == 0) {
  1.1178 +            return 0;
  1.1179 +        }
  1.1180 +
  1.1181 +        final View child = getChildAt(0);
  1.1182 +        final int childTop = child.getTop();
  1.1183 +
  1.1184 +        int childHeight = child.getHeight();
  1.1185 +        if (childHeight > 0) {
  1.1186 +            return Math.max(firstPosition * 100 - (childTop * 100) / childHeight, 0);
  1.1187 +        }
  1.1188 +
  1.1189 +        return 0;
  1.1190 +    }
  1.1191 +
  1.1192 +    @Override
  1.1193 +    protected int computeHorizontalScrollOffset() {
  1.1194 +        final int firstPosition = mFirstPosition;
  1.1195 +        final int childCount = getChildCount();
  1.1196 +
  1.1197 +        if (firstPosition < 0 || childCount == 0) {
  1.1198 +            return 0;
  1.1199 +        }
  1.1200 +
  1.1201 +        final View child = getChildAt(0);
  1.1202 +        final int childLeft = child.getLeft();
  1.1203 +
  1.1204 +        int childWidth = child.getWidth();
  1.1205 +        if (childWidth > 0) {
  1.1206 +            return Math.max(firstPosition * 100 - (childLeft * 100) / childWidth, 0);
  1.1207 +        }
  1.1208 +
  1.1209 +        return 0;
  1.1210 +    }
  1.1211 +
  1.1212 +    @Override
  1.1213 +    protected int computeVerticalScrollRange() {
  1.1214 +        int result = Math.max(mItemCount * 100, 0);
  1.1215 +
  1.1216 +        if (mIsVertical && mOverScroll != 0) {
  1.1217 +            // Compensate for overscroll
  1.1218 +            result += Math.abs((int) ((float) mOverScroll / getHeight() * mItemCount * 100));
  1.1219 +        }
  1.1220 +
  1.1221 +        return result;
  1.1222 +    }
  1.1223 +
  1.1224 +    @Override
  1.1225 +    protected int computeHorizontalScrollRange() {
  1.1226 +        int result = Math.max(mItemCount * 100, 0);
  1.1227 +
  1.1228 +        if (!mIsVertical && mOverScroll != 0) {
  1.1229 +            // Compensate for overscroll
  1.1230 +            result += Math.abs((int) ((float) mOverScroll / getWidth() * mItemCount * 100));
  1.1231 +        }
  1.1232 +
  1.1233 +        return result;
  1.1234 +    }
  1.1235 +
  1.1236 +    @Override
  1.1237 +    public boolean showContextMenuForChild(View originalView) {
  1.1238 +        final int longPressPosition = getPositionForView(originalView);
  1.1239 +        if (longPressPosition >= 0) {
  1.1240 +            final long longPressId = mAdapter.getItemId(longPressPosition);
  1.1241 +            boolean handled = false;
  1.1242 +
  1.1243 +            OnItemLongClickListener listener = getOnItemLongClickListener();
  1.1244 +            if (listener != null) {
  1.1245 +                handled = listener.onItemLongClick(TwoWayView.this, originalView,
  1.1246 +                        longPressPosition, longPressId);
  1.1247 +            }
  1.1248 +
  1.1249 +            if (!handled) {
  1.1250 +                mContextMenuInfo = createContextMenuInfo(
  1.1251 +                        getChildAt(longPressPosition - mFirstPosition),
  1.1252 +                        longPressPosition, longPressId);
  1.1253 +
  1.1254 +                handled = super.showContextMenuForChild(originalView);
  1.1255 +            }
  1.1256 +
  1.1257 +            return handled;
  1.1258 +        }
  1.1259 +
  1.1260 +        return false;
  1.1261 +    }
  1.1262 +
  1.1263 +    @Override
  1.1264 +    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
  1.1265 +        if (disallowIntercept) {
  1.1266 +            recycleVelocityTracker();
  1.1267 +        }
  1.1268 +
  1.1269 +        super.requestDisallowInterceptTouchEvent(disallowIntercept);
  1.1270 +    }
  1.1271 +
  1.1272 +    @Override
  1.1273 +    public boolean onInterceptTouchEvent(MotionEvent ev) {
  1.1274 +        if (!mIsAttached) {
  1.1275 +            return false;
  1.1276 +        }
  1.1277 +
  1.1278 +        final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
  1.1279 +        switch (action) {
  1.1280 +        case MotionEvent.ACTION_DOWN:
  1.1281 +            initOrResetVelocityTracker();
  1.1282 +            mVelocityTracker.addMovement(ev);
  1.1283 +
  1.1284 +            mScroller.abortAnimation();
  1.1285 +
  1.1286 +            final float x = ev.getX();
  1.1287 +            final float y = ev.getY();
  1.1288 +
  1.1289 +            mLastTouchPos = (mIsVertical ? y : x);
  1.1290 +
  1.1291 +            final int motionPosition = findMotionRowOrColumn((int) mLastTouchPos);
  1.1292 +
  1.1293 +            mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
  1.1294 +            mTouchRemainderPos = 0;
  1.1295 +
  1.1296 +            if (mTouchMode == TOUCH_MODE_FLINGING) {
  1.1297 +                return true;
  1.1298 +            } else if (motionPosition >= 0) {
  1.1299 +                mMotionPosition = motionPosition;
  1.1300 +                mTouchMode = TOUCH_MODE_DOWN;
  1.1301 +            }
  1.1302 +
  1.1303 +            break;
  1.1304 +
  1.1305 +        case MotionEvent.ACTION_MOVE: {
  1.1306 +            if (mTouchMode != TOUCH_MODE_DOWN) {
  1.1307 +                break;
  1.1308 +            }
  1.1309 +
  1.1310 +            initVelocityTrackerIfNotExists();
  1.1311 +            mVelocityTracker.addMovement(ev);
  1.1312 +
  1.1313 +            final int index = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
  1.1314 +            if (index < 0) {
  1.1315 +                Log.e(LOGTAG, "onInterceptTouchEvent could not find pointer with id " +
  1.1316 +                        mActivePointerId + " - did TwoWayView receive an inconsistent " +
  1.1317 +                        "event stream?");
  1.1318 +                return false;
  1.1319 +            }
  1.1320 +
  1.1321 +            final float pos;
  1.1322 +            if (mIsVertical) {
  1.1323 +                pos = MotionEventCompat.getY(ev, index);
  1.1324 +            } else {
  1.1325 +                pos = MotionEventCompat.getX(ev, index);
  1.1326 +            }
  1.1327 +
  1.1328 +            final float diff = pos - mLastTouchPos + mTouchRemainderPos;
  1.1329 +            final int delta = (int) diff;
  1.1330 +            mTouchRemainderPos = diff - delta;
  1.1331 +
  1.1332 +            if (maybeStartScrolling(delta)) {
  1.1333 +                return true;
  1.1334 +            }
  1.1335 +            
  1.1336 +            break;
  1.1337 +        }
  1.1338 +
  1.1339 +        case MotionEvent.ACTION_CANCEL:
  1.1340 +        case MotionEvent.ACTION_UP:
  1.1341 +            mActivePointerId = INVALID_POINTER;
  1.1342 +            mTouchMode = TOUCH_MODE_REST;
  1.1343 +            recycleVelocityTracker();
  1.1344 +            reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
  1.1345 +
  1.1346 +            break;
  1.1347 +        }
  1.1348 +
  1.1349 +        return false;
  1.1350 +    }
  1.1351 +
  1.1352 +    @Override
  1.1353 +    public boolean onTouchEvent(MotionEvent ev) {
  1.1354 +        if (!isEnabled()) {
  1.1355 +            // A disabled view that is clickable still consumes the touch
  1.1356 +            // events, it just doesn't respond to them.
  1.1357 +            return isClickable() || isLongClickable();
  1.1358 +        }
  1.1359 +
  1.1360 +        if (!mIsAttached) {
  1.1361 +            return false;
  1.1362 +        }
  1.1363 +
  1.1364 +        boolean needsInvalidate = false;
  1.1365 +
  1.1366 +        initVelocityTrackerIfNotExists();
  1.1367 +        mVelocityTracker.addMovement(ev);
  1.1368 +
  1.1369 +        final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
  1.1370 +        switch (action) {
  1.1371 +        case MotionEvent.ACTION_DOWN: {
  1.1372 +            if (mDataChanged) {
  1.1373 +                break;
  1.1374 +            }
  1.1375 +
  1.1376 +            mVelocityTracker.clear();
  1.1377 +            mScroller.abortAnimation();
  1.1378 +
  1.1379 +            final float x = ev.getX();
  1.1380 +            final float y = ev.getY();
  1.1381 +
  1.1382 +            mLastTouchPos = (mIsVertical ? y : x);
  1.1383 +
  1.1384 +            int motionPosition = pointToPosition((int) x, (int) y);
  1.1385 +
  1.1386 +            mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
  1.1387 +            mTouchRemainderPos = 0;
  1.1388 +
  1.1389 +            if (mDataChanged) {
  1.1390 +                break;
  1.1391 +            }
  1.1392 +
  1.1393 +            if (mTouchMode == TOUCH_MODE_FLINGING) {
  1.1394 +                mTouchMode = TOUCH_MODE_DRAGGING;
  1.1395 +                reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
  1.1396 +                motionPosition = findMotionRowOrColumn((int) mLastTouchPos);
  1.1397 +                return true;
  1.1398 +            } else if (mMotionPosition >= 0 && mAdapter.isEnabled(mMotionPosition)) {
  1.1399 +                mTouchMode = TOUCH_MODE_DOWN;
  1.1400 +                triggerCheckForTap();
  1.1401 +            }
  1.1402 +
  1.1403 +            mMotionPosition = motionPosition;
  1.1404 +
  1.1405 +            break;
  1.1406 +        }
  1.1407 +
  1.1408 +        case MotionEvent.ACTION_MOVE: {
  1.1409 +            final int index = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
  1.1410 +            if (index < 0) {
  1.1411 +                Log.e(LOGTAG, "onInterceptTouchEvent could not find pointer with id " +
  1.1412 +                        mActivePointerId + " - did TwoWayView receive an inconsistent " +
  1.1413 +                        "event stream?");
  1.1414 +                return false;
  1.1415 +            }
  1.1416 +
  1.1417 +            final float pos;
  1.1418 +            if (mIsVertical) {
  1.1419 +                pos = MotionEventCompat.getY(ev, index);
  1.1420 +            } else {
  1.1421 +                pos = MotionEventCompat.getX(ev, index);
  1.1422 +            }
  1.1423 +
  1.1424 +            if (mDataChanged) {
  1.1425 +                // Re-sync everything if data has been changed
  1.1426 +                // since the scroll operation can query the adapter.
  1.1427 +                layoutChildren();
  1.1428 +            }
  1.1429 +
  1.1430 +            final float diff = pos - mLastTouchPos + mTouchRemainderPos;
  1.1431 +            final int delta = (int) diff;
  1.1432 +            mTouchRemainderPos = diff - delta;
  1.1433 +
  1.1434 +            switch (mTouchMode) {
  1.1435 +            case TOUCH_MODE_DOWN:
  1.1436 +            case TOUCH_MODE_TAP:
  1.1437 +            case TOUCH_MODE_DONE_WAITING:
  1.1438 +                // Check if we have moved far enough that it looks more like a
  1.1439 +                // scroll than a tap
  1.1440 +                maybeStartScrolling(delta);
  1.1441 +                break;
  1.1442 +
  1.1443 +            case TOUCH_MODE_DRAGGING:
  1.1444 +            case TOUCH_MODE_OVERSCROLL:
  1.1445 +                mLastTouchPos = pos;
  1.1446 +                maybeScroll(delta);
  1.1447 +                break;
  1.1448 +            }
  1.1449 +
  1.1450 +            break;
  1.1451 +        }
  1.1452 +
  1.1453 +        case MotionEvent.ACTION_CANCEL:
  1.1454 +            cancelCheckForTap();
  1.1455 +            mTouchMode = TOUCH_MODE_REST;
  1.1456 +            reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
  1.1457 +
  1.1458 +            setPressed(false);
  1.1459 +            View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
  1.1460 +            if (motionView != null) {
  1.1461 +                motionView.setPressed(false);
  1.1462 +            }
  1.1463 +
  1.1464 +            if (mStartEdge != null && mEndEdge != null) {
  1.1465 +                needsInvalidate = mStartEdge.onRelease() | mEndEdge.onRelease();
  1.1466 +            }
  1.1467 +
  1.1468 +            recycleVelocityTracker();
  1.1469 +
  1.1470 +            break;
  1.1471 +
  1.1472 +        case MotionEvent.ACTION_UP: {
  1.1473 +            switch (mTouchMode) {
  1.1474 +            case TOUCH_MODE_DOWN:
  1.1475 +            case TOUCH_MODE_TAP:
  1.1476 +            case TOUCH_MODE_DONE_WAITING: {
  1.1477 +                final int motionPosition = mMotionPosition;
  1.1478 +                final View child = getChildAt(motionPosition - mFirstPosition);
  1.1479 +
  1.1480 +                final float x = ev.getX();
  1.1481 +                final float y = ev.getY();
  1.1482 +
  1.1483 +                boolean inList = false;
  1.1484 +                if (mIsVertical) {
  1.1485 +                    inList = x > getPaddingLeft() && x < getWidth() - getPaddingRight();
  1.1486 +                } else {
  1.1487 +                    inList = y > getPaddingTop() && y < getHeight() - getPaddingBottom();
  1.1488 +                }
  1.1489 +
  1.1490 +                if (child != null && !child.hasFocusable() && inList) {
  1.1491 +                    if (mTouchMode != TOUCH_MODE_DOWN) {
  1.1492 +                        child.setPressed(false);
  1.1493 +                    }
  1.1494 +
  1.1495 +                    if (mPerformClick == null) {
  1.1496 +                        mPerformClick = new PerformClick();
  1.1497 +                    }
  1.1498 +
  1.1499 +                    final PerformClick performClick = mPerformClick;
  1.1500 +                    performClick.mClickMotionPosition = motionPosition;
  1.1501 +                    performClick.rememberWindowAttachCount();
  1.1502 +
  1.1503 +                    mResurrectToPosition = motionPosition;
  1.1504 +
  1.1505 +                    if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) {
  1.1506 +                        if (mTouchMode == TOUCH_MODE_DOWN) {
  1.1507 +                            cancelCheckForTap();
  1.1508 +                        } else {
  1.1509 +                            cancelCheckForLongPress();
  1.1510 +                        }
  1.1511 +
  1.1512 +                        mLayoutMode = LAYOUT_NORMAL;
  1.1513 +
  1.1514 +                        if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
  1.1515 +                            mTouchMode = TOUCH_MODE_TAP;
  1.1516 +
  1.1517 +                            setPressed(true);
  1.1518 +                            positionSelector(mMotionPosition, child);
  1.1519 +                            child.setPressed(true);
  1.1520 +
  1.1521 +                            if (mSelector != null) {
  1.1522 +                                Drawable d = mSelector.getCurrent();
  1.1523 +                                if (d != null && d instanceof TransitionDrawable) {
  1.1524 +                                    ((TransitionDrawable) d).resetTransition();
  1.1525 +                                }
  1.1526 +                            }
  1.1527 +
  1.1528 +                            if (mTouchModeReset != null) {
  1.1529 +                                removeCallbacks(mTouchModeReset);
  1.1530 +                            }
  1.1531 +
  1.1532 +                            mTouchModeReset = new Runnable() {
  1.1533 +                                @Override
  1.1534 +                                public void run() {
  1.1535 +                                    mTouchMode = TOUCH_MODE_REST;
  1.1536 +
  1.1537 +                                    setPressed(false);
  1.1538 +                                    child.setPressed(false);
  1.1539 +
  1.1540 +                                    if (!mDataChanged) {
  1.1541 +                                        performClick.run();
  1.1542 +                                    }
  1.1543 +
  1.1544 +                                    mTouchModeReset = null;
  1.1545 +                                }
  1.1546 +                            };
  1.1547 +
  1.1548 +                            postDelayed(mTouchModeReset,
  1.1549 +                                    ViewConfiguration.getPressedStateDuration());
  1.1550 +                        } else {
  1.1551 +                            mTouchMode = TOUCH_MODE_REST;
  1.1552 +                            updateSelectorState();
  1.1553 +                        }
  1.1554 +                    } else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
  1.1555 +                        performClick.run();
  1.1556 +                    }
  1.1557 +                }
  1.1558 +
  1.1559 +                mTouchMode = TOUCH_MODE_REST;
  1.1560 +                updateSelectorState();
  1.1561 +
  1.1562 +                break;
  1.1563 +            }
  1.1564 +
  1.1565 +            case TOUCH_MODE_DRAGGING:
  1.1566 +                if (contentFits()) {
  1.1567 +                    mTouchMode = TOUCH_MODE_REST;
  1.1568 +                    reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
  1.1569 +                    break;
  1.1570 +                }
  1.1571 +
  1.1572 +                mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
  1.1573 +
  1.1574 +                final float velocity;
  1.1575 +                if (mIsVertical) {
  1.1576 +                    velocity = VelocityTrackerCompat.getYVelocity(mVelocityTracker,
  1.1577 +                            mActivePointerId);
  1.1578 +                } else {
  1.1579 +                    velocity = VelocityTrackerCompat.getXVelocity(mVelocityTracker,
  1.1580 +                            mActivePointerId);
  1.1581 +                }
  1.1582 +
  1.1583 +                if (Math.abs(velocity) >= mFlingVelocity) {
  1.1584 +                    mTouchMode = TOUCH_MODE_FLINGING;
  1.1585 +                    reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
  1.1586 +
  1.1587 +                    mScroller.fling(0, 0,
  1.1588 +                                    (int) (mIsVertical ? 0 : velocity),
  1.1589 +                                    (int) (mIsVertical ? velocity : 0),
  1.1590 +                                    (mIsVertical ? 0 : Integer.MIN_VALUE),
  1.1591 +                                    (mIsVertical ? 0 : Integer.MAX_VALUE),
  1.1592 +                                    (mIsVertical ? Integer.MIN_VALUE : 0),
  1.1593 +                                    (mIsVertical ? Integer.MAX_VALUE : 0));
  1.1594 +
  1.1595 +                    mLastTouchPos = 0;
  1.1596 +                    needsInvalidate = true;
  1.1597 +                } else {
  1.1598 +                    mTouchMode = TOUCH_MODE_REST;
  1.1599 +                    reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
  1.1600 +                }
  1.1601 +
  1.1602 +                break;
  1.1603 +
  1.1604 +            case TOUCH_MODE_OVERSCROLL:
  1.1605 +                mTouchMode = TOUCH_MODE_REST;
  1.1606 +                reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
  1.1607 +                break;
  1.1608 +            }
  1.1609 +
  1.1610 +            cancelCheckForTap();
  1.1611 +            cancelCheckForLongPress();
  1.1612 +            setPressed(false);
  1.1613 +
  1.1614 +            if (mStartEdge != null && mEndEdge != null) {
  1.1615 +                needsInvalidate |= mStartEdge.onRelease() | mEndEdge.onRelease();
  1.1616 +            }
  1.1617 +
  1.1618 +            recycleVelocityTracker();
  1.1619 +
  1.1620 +            break;
  1.1621 +        }
  1.1622 +        }
  1.1623 +
  1.1624 +        if (needsInvalidate) {
  1.1625 +            ViewCompat.postInvalidateOnAnimation(this);
  1.1626 +        }
  1.1627 +
  1.1628 +        return true;
  1.1629 +    }
  1.1630 +
  1.1631 +    @Override
  1.1632 +    public void onTouchModeChanged(boolean isInTouchMode) {
  1.1633 +        if (isInTouchMode) {
  1.1634 +            // Get rid of the selection when we enter touch mode
  1.1635 +            hideSelector();
  1.1636 +
  1.1637 +            // Layout, but only if we already have done so previously.
  1.1638 +            // (Otherwise may clobber a LAYOUT_SYNC layout that was requested to restore
  1.1639 +            // state.)
  1.1640 +            if (getWidth() > 0 && getHeight() > 0 && getChildCount() > 0) {
  1.1641 +                layoutChildren();
  1.1642 +            }
  1.1643 +
  1.1644 +            updateSelectorState();
  1.1645 +        } else {
  1.1646 +            final int touchMode = mTouchMode;
  1.1647 +            if (touchMode == TOUCH_MODE_OVERSCROLL) {
  1.1648 +                if (mOverScroll != 0) {
  1.1649 +                    mOverScroll = 0;
  1.1650 +                    finishEdgeGlows();
  1.1651 +                    invalidate();
  1.1652 +                }
  1.1653 +            }
  1.1654 +        }
  1.1655 +    }
  1.1656 +
  1.1657 +    @Override
  1.1658 +    public boolean onKeyDown(int keyCode, KeyEvent event) {
  1.1659 +        return handleKeyEvent(keyCode, 1, event);
  1.1660 +    }
  1.1661 +
  1.1662 +    @Override
  1.1663 +    public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
  1.1664 +        return handleKeyEvent(keyCode, repeatCount, event);
  1.1665 +    }
  1.1666 +
  1.1667 +    @Override
  1.1668 +    public boolean onKeyUp(int keyCode, KeyEvent event) {
  1.1669 +        return handleKeyEvent(keyCode, 1, event);
  1.1670 +    }
  1.1671 +
  1.1672 +    @Override
  1.1673 +    public void sendAccessibilityEvent(int eventType) {
  1.1674 +        // Since this class calls onScrollChanged even if the mFirstPosition and the
  1.1675 +        // child count have not changed we will avoid sending duplicate accessibility
  1.1676 +        // events.
  1.1677 +        if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
  1.1678 +            final int firstVisiblePosition = getFirstVisiblePosition();
  1.1679 +            final int lastVisiblePosition = getLastVisiblePosition();
  1.1680 +
  1.1681 +            if (mLastAccessibilityScrollEventFromIndex == firstVisiblePosition
  1.1682 +                    && mLastAccessibilityScrollEventToIndex == lastVisiblePosition) {
  1.1683 +                return;
  1.1684 +            } else {
  1.1685 +                mLastAccessibilityScrollEventFromIndex = firstVisiblePosition;
  1.1686 +                mLastAccessibilityScrollEventToIndex = lastVisiblePosition;
  1.1687 +            }
  1.1688 +        }
  1.1689 +
  1.1690 +        super.sendAccessibilityEvent(eventType);
  1.1691 +    }
  1.1692 +
  1.1693 +    @Override
  1.1694 +    @TargetApi(14)
  1.1695 +    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
  1.1696 +        super.onInitializeAccessibilityEvent(event);
  1.1697 +        event.setClassName(TwoWayView.class.getName());
  1.1698 +    }
  1.1699 +
  1.1700 +    @Override
  1.1701 +    @TargetApi(14)
  1.1702 +    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
  1.1703 +        super.onInitializeAccessibilityNodeInfo(info);
  1.1704 +        info.setClassName(TwoWayView.class.getName());
  1.1705 +
  1.1706 +        AccessibilityNodeInfoCompat infoCompat = new AccessibilityNodeInfoCompat(info);
  1.1707 +
  1.1708 +        if (isEnabled()) {
  1.1709 +            if (getFirstVisiblePosition() > 0) {
  1.1710 +                infoCompat.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD);
  1.1711 +            }
  1.1712 +
  1.1713 +            if (getLastVisiblePosition() < getCount() - 1) {
  1.1714 +                infoCompat.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD);
  1.1715 +            }
  1.1716 +        }
  1.1717 +    }
  1.1718 +
  1.1719 +    @Override
  1.1720 +    @TargetApi(16)
  1.1721 +    public boolean performAccessibilityAction(int action, Bundle arguments) {
  1.1722 +        if (super.performAccessibilityAction(action, arguments)) {
  1.1723 +            return true;
  1.1724 +        }
  1.1725 +
  1.1726 +        switch (action) {
  1.1727 +        case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
  1.1728 +            if (isEnabled() && getLastVisiblePosition() < getCount() - 1) {
  1.1729 +                final int viewportSize;
  1.1730 +                if (mIsVertical) {
  1.1731 +                    viewportSize = getHeight() - getPaddingTop() - getPaddingBottom();
  1.1732 +                } else {
  1.1733 +                    viewportSize = getWidth() - getPaddingLeft() - getPaddingRight();
  1.1734 +                }
  1.1735 +
  1.1736 +                // TODO: Use some form of smooth scroll instead
  1.1737 +                trackMotionScroll(viewportSize);
  1.1738 +                return true;
  1.1739 +            }
  1.1740 +            return false;
  1.1741 +
  1.1742 +        case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
  1.1743 +            if (isEnabled() && mFirstPosition > 0) {
  1.1744 +                final int viewportSize;
  1.1745 +                if (mIsVertical) {
  1.1746 +                    viewportSize = getHeight() - getPaddingTop() - getPaddingBottom();
  1.1747 +                } else {
  1.1748 +                    viewportSize = getWidth() - getPaddingLeft() - getPaddingRight();
  1.1749 +                }
  1.1750 +
  1.1751 +                // TODO: Use some form of smooth scroll instead
  1.1752 +                trackMotionScroll(-viewportSize);
  1.1753 +                return true;
  1.1754 +            }
  1.1755 +            return false;
  1.1756 +        }
  1.1757 +
  1.1758 +        return false;
  1.1759 +    }
  1.1760 +
  1.1761 +    /**
  1.1762 +     * Return true if child is an ancestor of parent, (or equal to the parent).
  1.1763 +     */
  1.1764 +    private boolean isViewAncestorOf(View child, View parent) {
  1.1765 +        if (child == parent) {
  1.1766 +            return true;
  1.1767 +        }
  1.1768 +
  1.1769 +        final ViewParent theParent = child.getParent();
  1.1770 +
  1.1771 +        return (theParent instanceof ViewGroup) &&
  1.1772 +                 isViewAncestorOf((View) theParent, parent);
  1.1773 +    }
  1.1774 +
  1.1775 +    private void forceValidFocusDirection(int direction) {
  1.1776 +        if (mIsVertical && direction != View.FOCUS_UP && direction != View.FOCUS_DOWN)  {
  1.1777 +            throw new IllegalArgumentException("Focus direction must be one of"
  1.1778 +                    + " {View.FOCUS_UP, View.FOCUS_DOWN} for vertical orientation");
  1.1779 +        } else if (!mIsVertical && direction != View.FOCUS_LEFT && direction != View.FOCUS_RIGHT)  {
  1.1780 +            throw new IllegalArgumentException("Focus direction must be one of"
  1.1781 +                    + " {View.FOCUS_LEFT, View.FOCUS_RIGHT} for vertical orientation");
  1.1782 +        }
  1.1783 +    }
  1.1784 +
  1.1785 +    private void forceValidInnerFocusDirection(int direction) {
  1.1786 +        if (mIsVertical && direction != View.FOCUS_LEFT && direction != View.FOCUS_RIGHT)  {
  1.1787 +            throw new IllegalArgumentException("Direction must be one of"
  1.1788 +                    + " {View.FOCUS_LEFT, View.FOCUS_RIGHT} for vertical orientation");
  1.1789 +        } else if (!mIsVertical && direction != View.FOCUS_UP && direction != View.FOCUS_DOWN)  {
  1.1790 +            throw new IllegalArgumentException("direction must be one of"
  1.1791 +                    + " {View.FOCUS_UP, View.FOCUS_DOWN} for horizontal orientation");
  1.1792 +        }
  1.1793 +    }
  1.1794 +
  1.1795 +    /**
  1.1796 +     * Scrolls up or down by the number of items currently present on screen.
  1.1797 +     *
  1.1798 +     * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} or
  1.1799 +     *        {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT} depending on the
  1.1800 +     *        current view orientation.
  1.1801 +     *
  1.1802 +     * @return whether selection was moved
  1.1803 +     */
  1.1804 +    boolean pageScroll(int direction) {
  1.1805 +        forceValidFocusDirection(direction);
  1.1806 +
  1.1807 +        boolean forward = false;
  1.1808 +        int nextPage = -1;
  1.1809 +
  1.1810 +        if (direction == View.FOCUS_UP || direction == View.FOCUS_LEFT) {
  1.1811 +            nextPage = Math.max(0, mSelectedPosition - getChildCount() - 1);
  1.1812 +        } else if (direction == View.FOCUS_DOWN || direction == View.FOCUS_RIGHT) {
  1.1813 +            nextPage = Math.min(mItemCount - 1, mSelectedPosition + getChildCount() - 1);
  1.1814 +            forward = true;
  1.1815 +        }
  1.1816 +
  1.1817 +        if (nextPage < 0) {
  1.1818 +            return false;
  1.1819 +        }
  1.1820 +
  1.1821 +        final int position = lookForSelectablePosition(nextPage, forward);
  1.1822 +        if (position >= 0) {
  1.1823 +            mLayoutMode = LAYOUT_SPECIFIC;
  1.1824 +            mSpecificStart = (mIsVertical ? getPaddingTop() : getPaddingLeft());
  1.1825 +
  1.1826 +            if (forward && position > mItemCount - getChildCount()) {
  1.1827 +                mLayoutMode = LAYOUT_FORCE_BOTTOM;
  1.1828 +            }
  1.1829 +
  1.1830 +            if (!forward && position < getChildCount()) {
  1.1831 +                mLayoutMode = LAYOUT_FORCE_TOP;
  1.1832 +            }
  1.1833 +
  1.1834 +            setSelectionInt(position);
  1.1835 +            invokeOnItemScrollListener();
  1.1836 +
  1.1837 +            if (!awakenScrollbarsInternal()) {
  1.1838 +                invalidate();
  1.1839 +            }
  1.1840 +
  1.1841 +            return true;
  1.1842 +        }
  1.1843 +
  1.1844 +        return false;
  1.1845 +    }
  1.1846 +
  1.1847 +    /**
  1.1848 +     * Go to the last or first item if possible (not worrying about panning across or navigating
  1.1849 +     * within the internal focus of the currently selected item.)
  1.1850 +     *
  1.1851 +     * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} or
  1.1852 +     *        {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT} depending on the
  1.1853 +     *        current view orientation.
  1.1854 +     *
  1.1855 +     * @return whether selection was moved
  1.1856 +     */
  1.1857 +    boolean fullScroll(int direction) {
  1.1858 +        forceValidFocusDirection(direction);
  1.1859 +
  1.1860 +        boolean moved = false;
  1.1861 +        if (direction == View.FOCUS_UP || direction == View.FOCUS_LEFT) {
  1.1862 +            if (mSelectedPosition != 0) {
  1.1863 +                int position = lookForSelectablePosition(0, true);
  1.1864 +                if (position >= 0) {
  1.1865 +                    mLayoutMode = LAYOUT_FORCE_TOP;
  1.1866 +                    setSelectionInt(position);
  1.1867 +                    invokeOnItemScrollListener();
  1.1868 +                }
  1.1869 +
  1.1870 +                moved = true;
  1.1871 +            }
  1.1872 +        } else if (direction == View.FOCUS_DOWN || direction == View.FOCUS_RIGHT) {
  1.1873 +            if (mSelectedPosition < mItemCount - 1) {
  1.1874 +                int position = lookForSelectablePosition(mItemCount - 1, true);
  1.1875 +                if (position >= 0) {
  1.1876 +                    mLayoutMode = LAYOUT_FORCE_BOTTOM;
  1.1877 +                    setSelectionInt(position);
  1.1878 +                    invokeOnItemScrollListener();
  1.1879 +                }
  1.1880 +
  1.1881 +                moved = true;
  1.1882 +            }
  1.1883 +        }
  1.1884 +
  1.1885 +        if (moved && !awakenScrollbarsInternal()) {
  1.1886 +            awakenScrollbarsInternal();
  1.1887 +            invalidate();
  1.1888 +        }
  1.1889 +
  1.1890 +        return moved;
  1.1891 +    }
  1.1892 +
  1.1893 +    /**
  1.1894 +     * To avoid horizontal/vertical focus searches changing the selected item,
  1.1895 +     * we manually focus search within the selected item (as applicable), and
  1.1896 +     * prevent focus from jumping to something within another item.
  1.1897 +     *
  1.1898 +     * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} or
  1.1899 +     *        {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT} depending on the
  1.1900 +     *        current view orientation.
  1.1901 +     *
  1.1902 +     * @return Whether this consumes the key event.
  1.1903 +     */
  1.1904 +    private boolean handleFocusWithinItem(int direction) {
  1.1905 +        forceValidInnerFocusDirection(direction);
  1.1906 +
  1.1907 +        final int numChildren = getChildCount();
  1.1908 +
  1.1909 +        if (mItemsCanFocus && numChildren > 0 && mSelectedPosition != INVALID_POSITION) {
  1.1910 +            final View selectedView = getSelectedView();
  1.1911 +
  1.1912 +            if (selectedView != null && selectedView.hasFocus() &&
  1.1913 +                    selectedView instanceof ViewGroup) {
  1.1914 +
  1.1915 +                final View currentFocus = selectedView.findFocus();
  1.1916 +                final View nextFocus = FocusFinder.getInstance().findNextFocus(
  1.1917 +                        (ViewGroup) selectedView, currentFocus, direction);
  1.1918 +
  1.1919 +                if (nextFocus != null) {
  1.1920 +                    // Do the math to get interesting rect in next focus' coordinates
  1.1921 +                    currentFocus.getFocusedRect(mTempRect);
  1.1922 +                    offsetDescendantRectToMyCoords(currentFocus, mTempRect);
  1.1923 +                    offsetRectIntoDescendantCoords(nextFocus, mTempRect);
  1.1924 +
  1.1925 +                    if (nextFocus.requestFocus(direction, mTempRect)) {
  1.1926 +                        return true;
  1.1927 +                    }
  1.1928 +                }
  1.1929 +
  1.1930 +                // We are blocking the key from being handled (by returning true)
  1.1931 +                // if the global result is going to be some other view within this
  1.1932 +                // list. This is to achieve the overall goal of having horizontal/vertical
  1.1933 +                // d-pad navigation remain in the current item depending on the current
  1.1934 +                // orientation in this view.
  1.1935 +                final View globalNextFocus = FocusFinder.getInstance().findNextFocus(
  1.1936 +                        (ViewGroup) getRootView(), currentFocus, direction);
  1.1937 +
  1.1938 +                if (globalNextFocus != null) {
  1.1939 +                    return isViewAncestorOf(globalNextFocus, this);
  1.1940 +                }
  1.1941 +            }
  1.1942 +        }
  1.1943 +
  1.1944 +        return false;
  1.1945 +    }
  1.1946 +
  1.1947 +    /**
  1.1948 +     * Scrolls to the next or previous item if possible.
  1.1949 +     *
  1.1950 +     * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} or
  1.1951 +     *        {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT} depending on the
  1.1952 +     *        current view orientation.
  1.1953 +     *
  1.1954 +     * @return whether selection was moved
  1.1955 +     */
  1.1956 +    private boolean arrowScroll(int direction) {
  1.1957 +        forceValidFocusDirection(direction);
  1.1958 +
  1.1959 +        try {
  1.1960 +            mInLayout = true;
  1.1961 +
  1.1962 +            final boolean handled = arrowScrollImpl(direction);
  1.1963 +            if (handled) {
  1.1964 +                playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
  1.1965 +            }
  1.1966 +
  1.1967 +            return handled;
  1.1968 +        } finally {
  1.1969 +            mInLayout = false;
  1.1970 +        }
  1.1971 +    }
  1.1972 +
  1.1973 +    /**
  1.1974 +     * When selection changes, it is possible that the previously selected or the
  1.1975 +     * next selected item will change its size.  If so, we need to offset some folks,
  1.1976 +     * and re-layout the items as appropriate.
  1.1977 +     *
  1.1978 +     * @param selectedView The currently selected view (before changing selection).
  1.1979 +     *   should be <code>null</code> if there was no previous selection.
  1.1980 +     * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} or
  1.1981 +     *        {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT} depending on the
  1.1982 +     *        current view orientation.
  1.1983 +     * @param newSelectedPosition The position of the next selection.
  1.1984 +     * @param newFocusAssigned whether new focus was assigned.  This matters because
  1.1985 +     *        when something has focus, we don't want to show selection (ugh).
  1.1986 +     */
  1.1987 +    private void handleNewSelectionChange(View selectedView, int direction, int newSelectedPosition,
  1.1988 +            boolean newFocusAssigned) {
  1.1989 +        forceValidFocusDirection(direction);
  1.1990 +
  1.1991 +        if (newSelectedPosition == INVALID_POSITION) {
  1.1992 +            throw new IllegalArgumentException("newSelectedPosition needs to be valid");
  1.1993 +        }
  1.1994 +
  1.1995 +        // Whether or not we are moving down/right or up/left, we want to preserve the
  1.1996 +        // top/left of whatever view is at the start:
  1.1997 +        // - moving down/right: the view that had selection
  1.1998 +        // - moving up/left: the view that is getting selection
  1.1999 +        final int selectedIndex = mSelectedPosition - mFirstPosition;
  1.2000 +        final int nextSelectedIndex = newSelectedPosition - mFirstPosition;
  1.2001 +        int startViewIndex, endViewIndex;
  1.2002 +        boolean topSelected = false;
  1.2003 +        View startView;
  1.2004 +        View endView;
  1.2005 +
  1.2006 +        if (direction == View.FOCUS_UP || direction == View.FOCUS_LEFT) {
  1.2007 +            startViewIndex = nextSelectedIndex;
  1.2008 +            endViewIndex = selectedIndex;
  1.2009 +            startView = getChildAt(startViewIndex);
  1.2010 +            endView = selectedView;
  1.2011 +            topSelected = true;
  1.2012 +        } else {
  1.2013 +            startViewIndex = selectedIndex;
  1.2014 +            endViewIndex = nextSelectedIndex;
  1.2015 +            startView = selectedView;
  1.2016 +            endView = getChildAt(endViewIndex);
  1.2017 +        }
  1.2018 +
  1.2019 +        final int numChildren = getChildCount();
  1.2020 +
  1.2021 +        // start with top view: is it changing size?
  1.2022 +        if (startView != null) {
  1.2023 +            startView.setSelected(!newFocusAssigned && topSelected);
  1.2024 +            measureAndAdjustDown(startView, startViewIndex, numChildren);
  1.2025 +        }
  1.2026 +
  1.2027 +        // is the bottom view changing size?
  1.2028 +        if (endView != null) {
  1.2029 +            endView.setSelected(!newFocusAssigned && !topSelected);
  1.2030 +            measureAndAdjustDown(endView, endViewIndex, numChildren);
  1.2031 +        }
  1.2032 +    }
  1.2033 +
  1.2034 +    /**
  1.2035 +     * Re-measure a child, and if its height changes, lay it out preserving its
  1.2036 +     * top, and adjust the children below it appropriately.
  1.2037 +     *
  1.2038 +     * @param child The child
  1.2039 +     * @param childIndex The view group index of the child.
  1.2040 +     * @param numChildren The number of children in the view group.
  1.2041 +     */
  1.2042 +    private void measureAndAdjustDown(View child, int childIndex, int numChildren) {
  1.2043 +        int oldHeight = child.getHeight();
  1.2044 +        measureChild(child);
  1.2045 +
  1.2046 +        if (child.getMeasuredHeight() == oldHeight) {
  1.2047 +            return;
  1.2048 +        }
  1.2049 +
  1.2050 +        // lay out the view, preserving its top
  1.2051 +        relayoutMeasuredChild(child);
  1.2052 +
  1.2053 +        // adjust views below appropriately
  1.2054 +        final int heightDelta = child.getMeasuredHeight() - oldHeight;
  1.2055 +        for (int i = childIndex + 1; i < numChildren; i++) {
  1.2056 +            getChildAt(i).offsetTopAndBottom(heightDelta);
  1.2057 +        }
  1.2058 +    }
  1.2059 +
  1.2060 +    /**
  1.2061 +     * Do an arrow scroll based on focus searching.  If a new view is
  1.2062 +     * given focus, return the selection delta and amount to scroll via
  1.2063 +     * an {@link ArrowScrollFocusResult}, otherwise, return null.
  1.2064 +     *
  1.2065 +     * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} or
  1.2066 +     *        {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT} depending on the
  1.2067 +     *        current view orientation.
  1.2068 +     *
  1.2069 +     * @return The result if focus has changed, or <code>null</code>.
  1.2070 +     */
  1.2071 +    private ArrowScrollFocusResult arrowScrollFocused(final int direction) {
  1.2072 +        forceValidFocusDirection(direction);
  1.2073 +
  1.2074 +        final View selectedView = getSelectedView();
  1.2075 +        final View newFocus;
  1.2076 +        final int searchPoint;
  1.2077 +
  1.2078 +        if (selectedView != null && selectedView.hasFocus()) {
  1.2079 +            View oldFocus = selectedView.findFocus();
  1.2080 +            newFocus = FocusFinder.getInstance().findNextFocus(this, oldFocus, direction);
  1.2081 +        } else {
  1.2082 +            if (direction == View.FOCUS_DOWN || direction == View.FOCUS_RIGHT) {
  1.2083 +                final int start = (mIsVertical ? getPaddingTop() : getPaddingLeft());
  1.2084 +
  1.2085 +                final int selectedStart;
  1.2086 +                if (selectedView != null) {
  1.2087 +                    selectedStart = (mIsVertical ? selectedView.getTop() : selectedView.getLeft());
  1.2088 +                } else {
  1.2089 +                    selectedStart = start;
  1.2090 +                }
  1.2091 +
  1.2092 +                searchPoint = Math.max(selectedStart, start);
  1.2093 +            } else {
  1.2094 +                final int end = (mIsVertical ? getHeight() - getPaddingBottom() :
  1.2095 +                    getWidth() - getPaddingRight());
  1.2096 +
  1.2097 +                final int selectedEnd;
  1.2098 +                if (selectedView != null) {
  1.2099 +                    selectedEnd = (mIsVertical ? selectedView.getBottom() : selectedView.getRight());
  1.2100 +                } else {
  1.2101 +                    selectedEnd = end;
  1.2102 +                }
  1.2103 +
  1.2104 +                searchPoint = Math.min(selectedEnd, end);
  1.2105 +            }
  1.2106 +
  1.2107 +            final int x = (mIsVertical ? 0 : searchPoint);
  1.2108 +            final int y = (mIsVertical ? searchPoint : 0);
  1.2109 +            mTempRect.set(x, y, x, y);
  1.2110 +
  1.2111 +            newFocus = FocusFinder.getInstance().findNextFocusFromRect(this, mTempRect, direction);
  1.2112 +        }
  1.2113 +
  1.2114 +        if (newFocus != null) {
  1.2115 +            final int positionOfNewFocus = positionOfNewFocus(newFocus);
  1.2116 +
  1.2117 +            // If the focus change is in a different new position, make sure
  1.2118 +            // we aren't jumping over another selectable position.
  1.2119 +            if (mSelectedPosition != INVALID_POSITION && positionOfNewFocus != mSelectedPosition) {
  1.2120 +                final int selectablePosition = lookForSelectablePositionOnScreen(direction);
  1.2121 +
  1.2122 +                final boolean movingForward =
  1.2123 +                        (direction == View.FOCUS_DOWN || direction == View.FOCUS_RIGHT);
  1.2124 +                final boolean movingBackward =
  1.2125 +                        (direction == View.FOCUS_UP || direction == View.FOCUS_LEFT);
  1.2126 +
  1.2127 +                if (selectablePosition != INVALID_POSITION &&
  1.2128 +                        ((movingForward && selectablePosition < positionOfNewFocus) ||
  1.2129 +                         (movingBackward && selectablePosition > positionOfNewFocus))) {
  1.2130 +                    return null;
  1.2131 +                }
  1.2132 +            }
  1.2133 +
  1.2134 +            int focusScroll = amountToScrollToNewFocus(direction, newFocus, positionOfNewFocus);
  1.2135 +
  1.2136 +            final int maxScrollAmount = getMaxScrollAmount();
  1.2137 +            if (focusScroll < maxScrollAmount) {
  1.2138 +                // Not moving too far, safe to give next view focus
  1.2139 +                newFocus.requestFocus(direction);
  1.2140 +                mArrowScrollFocusResult.populate(positionOfNewFocus, focusScroll);
  1.2141 +                return mArrowScrollFocusResult;
  1.2142 +            } else if (distanceToView(newFocus) < maxScrollAmount){
  1.2143 +                // Case to consider:
  1.2144 +                // Too far to get entire next focusable on screen, but by going
  1.2145 +                // max scroll amount, we are getting it at least partially in view,
  1.2146 +                // so give it focus and scroll the max amount.
  1.2147 +                newFocus.requestFocus(direction);
  1.2148 +                mArrowScrollFocusResult.populate(positionOfNewFocus, maxScrollAmount);
  1.2149 +                return mArrowScrollFocusResult;
  1.2150 +            }
  1.2151 +        }
  1.2152 +
  1.2153 +        return null;
  1.2154 +    }
  1.2155 +
  1.2156 +    /**
  1.2157 +     * @return The maximum amount a list view will scroll in response to
  1.2158 +     *   an arrow event.
  1.2159 +     */
  1.2160 +    public int getMaxScrollAmount() {
  1.2161 +        return (int) (MAX_SCROLL_FACTOR * getHeight());
  1.2162 +    }
  1.2163 +
  1.2164 +    /**
  1.2165 +     * @return The amount to preview next items when arrow scrolling.
  1.2166 +     */
  1.2167 +    private int getArrowScrollPreviewLength() {
  1.2168 +        // FIXME: TwoWayView has no fading edge support just yet but using it
  1.2169 +        // makes it convenient for defining the next item's previous length.
  1.2170 +        int fadingEdgeLength =
  1.2171 +                (mIsVertical ? getVerticalFadingEdgeLength() : getHorizontalFadingEdgeLength());
  1.2172 +
  1.2173 +        return mItemMargin + Math.max(MIN_SCROLL_PREVIEW_PIXELS, fadingEdgeLength);
  1.2174 +    }
  1.2175 +
  1.2176 +    /**
  1.2177 +     * @param newFocus The view that would have focus.
  1.2178 +     * @return the position that contains newFocus
  1.2179 +     */
  1.2180 +    private int positionOfNewFocus(View newFocus) {
  1.2181 +        final int numChildren = getChildCount();
  1.2182 +
  1.2183 +        for (int i = 0; i < numChildren; i++) {
  1.2184 +            final View child = getChildAt(i);
  1.2185 +            if (isViewAncestorOf(newFocus, child)) {
  1.2186 +                return mFirstPosition + i;
  1.2187 +            }
  1.2188 +        }
  1.2189 +
  1.2190 +        throw new IllegalArgumentException("newFocus is not a child of any of the"
  1.2191 +                + " children of the list!");
  1.2192 +    }
  1.2193 +
  1.2194 +    /**
  1.2195 +     * Handle an arrow scroll going up or down.  Take into account whether items are selectable,
  1.2196 +     * whether there are focusable items, etc.
  1.2197 +     *
  1.2198 +     * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} or
  1.2199 +     *        {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT} depending on the
  1.2200 +     *        current view orientation.
  1.2201 +     *
  1.2202 +     * @return Whether any scrolling, selection or focus change occurred.
  1.2203 +     */
  1.2204 +    private boolean arrowScrollImpl(int direction) {
  1.2205 +        forceValidFocusDirection(direction);
  1.2206 +
  1.2207 +        if (getChildCount() <= 0) {
  1.2208 +            return false;
  1.2209 +        }
  1.2210 +
  1.2211 +        View selectedView = getSelectedView();
  1.2212 +        int selectedPos = mSelectedPosition;
  1.2213 +
  1.2214 +        int nextSelectedPosition = lookForSelectablePositionOnScreen(direction);
  1.2215 +        int amountToScroll = amountToScroll(direction, nextSelectedPosition);
  1.2216 +
  1.2217 +        // If we are moving focus, we may OVERRIDE the default behaviour
  1.2218 +        final ArrowScrollFocusResult focusResult = (mItemsCanFocus ? arrowScrollFocused(direction) : null);
  1.2219 +        if (focusResult != null) {
  1.2220 +            nextSelectedPosition = focusResult.getSelectedPosition();
  1.2221 +            amountToScroll = focusResult.getAmountToScroll();
  1.2222 +        }
  1.2223 +
  1.2224 +        boolean needToRedraw = (focusResult != null);
  1.2225 +        if (nextSelectedPosition != INVALID_POSITION) {
  1.2226 +            handleNewSelectionChange(selectedView, direction, nextSelectedPosition, focusResult != null);
  1.2227 +
  1.2228 +            setSelectedPositionInt(nextSelectedPosition);
  1.2229 +            setNextSelectedPositionInt(nextSelectedPosition);
  1.2230 +
  1.2231 +            selectedView = getSelectedView();
  1.2232 +            selectedPos = nextSelectedPosition;
  1.2233 +
  1.2234 +            if (mItemsCanFocus && focusResult == null) {
  1.2235 +                // There was no new view found to take focus, make sure we
  1.2236 +                // don't leave focus with the old selection.
  1.2237 +                final View focused = getFocusedChild();
  1.2238 +                if (focused != null) {
  1.2239 +                    focused.clearFocus();
  1.2240 +                }
  1.2241 +            }
  1.2242 +
  1.2243 +            needToRedraw = true;
  1.2244 +            checkSelectionChanged();
  1.2245 +        }
  1.2246 +
  1.2247 +        if (amountToScroll > 0) {
  1.2248 +            trackMotionScroll(direction == View.FOCUS_UP || direction == View.FOCUS_LEFT ?
  1.2249 +                    amountToScroll : -amountToScroll);
  1.2250 +            needToRedraw = true;
  1.2251 +        }
  1.2252 +
  1.2253 +        // If we didn't find a new focusable, make sure any existing focused
  1.2254 +        // item that was panned off screen gives up focus.
  1.2255 +        if (mItemsCanFocus && focusResult == null &&
  1.2256 +                selectedView != null && selectedView.hasFocus()) {
  1.2257 +            final View focused = selectedView.findFocus();
  1.2258 +            if (!isViewAncestorOf(focused, this) || distanceToView(focused) > 0) {
  1.2259 +                focused.clearFocus();
  1.2260 +            }
  1.2261 +        }
  1.2262 +
  1.2263 +        // If the current selection is panned off, we need to remove the selection
  1.2264 +        if (nextSelectedPosition == INVALID_POSITION && selectedView != null
  1.2265 +                && !isViewAncestorOf(selectedView, this)) {
  1.2266 +            selectedView = null;
  1.2267 +            hideSelector();
  1.2268 +
  1.2269 +            // But we don't want to set the ressurect position (that would make subsequent
  1.2270 +            // unhandled key events bring back the item we just scrolled off)
  1.2271 +            mResurrectToPosition = INVALID_POSITION;
  1.2272 +        }
  1.2273 +
  1.2274 +        if (needToRedraw) {
  1.2275 +            if (selectedView != null) {
  1.2276 +                positionSelector(selectedPos, selectedView);
  1.2277 +                mSelectedStart = selectedView.getTop();
  1.2278 +            }
  1.2279 +
  1.2280 +            if (!awakenScrollbarsInternal()) {
  1.2281 +                invalidate();
  1.2282 +            }
  1.2283 +
  1.2284 +            invokeOnItemScrollListener();
  1.2285 +            return true;
  1.2286 +        }
  1.2287 +
  1.2288 +        return false;
  1.2289 +    }
  1.2290 +
  1.2291 +    /**
  1.2292 +     * Determine how much we need to scroll in order to get the next selected view
  1.2293 +     * visible. The amount is capped at {@link #getMaxScrollAmount()}.
  1.2294 +     *
  1.2295 +     * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} or
  1.2296 +     *        {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT} depending on the
  1.2297 +     *        current view orientation.
  1.2298 +     * @param nextSelectedPosition The position of the next selection, or
  1.2299 +     *        {@link #INVALID_POSITION} if there is no next selectable position
  1.2300 +     *
  1.2301 +     * @return The amount to scroll. Note: this is always positive!  Direction
  1.2302 +     *         needs to be taken into account when actually scrolling.
  1.2303 +     */
  1.2304 +    private int amountToScroll(int direction, int nextSelectedPosition) {
  1.2305 +        forceValidFocusDirection(direction);
  1.2306 +
  1.2307 +        final int numChildren = getChildCount();
  1.2308 +
  1.2309 +        if (direction == View.FOCUS_DOWN || direction == View.FOCUS_RIGHT) {
  1.2310 +            final int end = (mIsVertical ? getHeight() - getPaddingBottom() :
  1.2311 +                getWidth() - getPaddingRight());
  1.2312 +
  1.2313 +            int indexToMakeVisible = numChildren - 1;
  1.2314 +            if (nextSelectedPosition != INVALID_POSITION) {
  1.2315 +                indexToMakeVisible = nextSelectedPosition - mFirstPosition;
  1.2316 +            }
  1.2317 +
  1.2318 +            final int positionToMakeVisible = mFirstPosition + indexToMakeVisible;
  1.2319 +            final View viewToMakeVisible = getChildAt(indexToMakeVisible);
  1.2320 +
  1.2321 +            int goalEnd = end;
  1.2322 +            if (positionToMakeVisible < mItemCount - 1) {
  1.2323 +                goalEnd -= getArrowScrollPreviewLength();
  1.2324 +            }
  1.2325 +
  1.2326 +            final int viewToMakeVisibleStart =
  1.2327 +                    (mIsVertical ? viewToMakeVisible.getTop() : viewToMakeVisible.getLeft());
  1.2328 +            final int viewToMakeVisibleEnd =
  1.2329 +                    (mIsVertical ? viewToMakeVisible.getBottom() : viewToMakeVisible.getRight());
  1.2330 +
  1.2331 +            if (viewToMakeVisibleEnd <= goalEnd) {
  1.2332 +                // Target item is fully visible
  1.2333 +                return 0;
  1.2334 +            }
  1.2335 +
  1.2336 +            if (nextSelectedPosition != INVALID_POSITION &&
  1.2337 +                    (goalEnd - viewToMakeVisibleStart) >= getMaxScrollAmount()) {
  1.2338 +                // Item already has enough of it visible, changing selection is good enough
  1.2339 +                return 0;
  1.2340 +            }
  1.2341 +
  1.2342 +            int amountToScroll = (viewToMakeVisibleEnd - goalEnd);
  1.2343 +
  1.2344 +            if (mFirstPosition + numChildren == mItemCount) {
  1.2345 +                final View lastChild = getChildAt(numChildren - 1);
  1.2346 +                final int lastChildEnd = (mIsVertical ? lastChild.getBottom() : lastChild.getRight());
  1.2347 +
  1.2348 +                // Last is last in list -> Make sure we don't scroll past it
  1.2349 +                final int max = lastChildEnd - end;
  1.2350 +                amountToScroll = Math.min(amountToScroll, max);
  1.2351 +            }
  1.2352 +
  1.2353 +            return Math.min(amountToScroll, getMaxScrollAmount());
  1.2354 +        } else {
  1.2355 +            final int start = (mIsVertical ? getPaddingTop() : getPaddingLeft());
  1.2356 +
  1.2357 +            int indexToMakeVisible = 0;
  1.2358 +            if (nextSelectedPosition != INVALID_POSITION) {
  1.2359 +                indexToMakeVisible = nextSelectedPosition - mFirstPosition;
  1.2360 +            }
  1.2361 +
  1.2362 +            final int positionToMakeVisible = mFirstPosition + indexToMakeVisible;
  1.2363 +            final View viewToMakeVisible = getChildAt(indexToMakeVisible);
  1.2364 +
  1.2365 +            int goalStart = start;
  1.2366 +            if (positionToMakeVisible > 0) {
  1.2367 +                goalStart += getArrowScrollPreviewLength();
  1.2368 +            }
  1.2369 +
  1.2370 +            final int viewToMakeVisibleStart =
  1.2371 +                    (mIsVertical ? viewToMakeVisible.getTop() : viewToMakeVisible.getLeft());
  1.2372 +            final int viewToMakeVisibleEnd =
  1.2373 +                    (mIsVertical ? viewToMakeVisible.getBottom() : viewToMakeVisible.getRight());
  1.2374 +
  1.2375 +            if (viewToMakeVisibleStart >= goalStart) {
  1.2376 +                // Item is fully visible
  1.2377 +                return 0;
  1.2378 +            }
  1.2379 +
  1.2380 +            if (nextSelectedPosition != INVALID_POSITION &&
  1.2381 +                    (viewToMakeVisibleEnd - goalStart) >= getMaxScrollAmount()) {
  1.2382 +                // Item already has enough of it visible, changing selection is good enough
  1.2383 +                return 0;
  1.2384 +            }
  1.2385 +
  1.2386 +            int amountToScroll = (goalStart - viewToMakeVisibleStart);
  1.2387 +
  1.2388 +            if (mFirstPosition == 0) {
  1.2389 +                final View firstChild = getChildAt(0);
  1.2390 +                final int firstChildStart = (mIsVertical ? firstChild.getTop() : firstChild.getLeft()); 
  1.2391 +
  1.2392 +                // First is first in list -> make sure we don't scroll past it
  1.2393 +                final int max = start - firstChildStart;
  1.2394 +                amountToScroll = Math.min(amountToScroll,  max);
  1.2395 +            }
  1.2396 +
  1.2397 +            return Math.min(amountToScroll, getMaxScrollAmount());
  1.2398 +        }
  1.2399 +    }
  1.2400 +
  1.2401 +    /**
  1.2402 +     * Determine how much we need to scroll in order to get newFocus in view.
  1.2403 +     *
  1.2404 +     * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} or
  1.2405 +     *        {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT} depending on the
  1.2406 +     *        current view orientation.
  1.2407 +     * @param newFocus The view that would take focus.
  1.2408 +     * @param positionOfNewFocus The position of the list item containing newFocus
  1.2409 +     *
  1.2410 +     * @return The amount to scroll. Note: this is always positive! Direction
  1.2411 +     *   needs to be taken into account when actually scrolling.
  1.2412 +     */
  1.2413 +    private int amountToScrollToNewFocus(int direction, View newFocus, int positionOfNewFocus) {
  1.2414 +        forceValidFocusDirection(direction);
  1.2415 +
  1.2416 +        int amountToScroll = 0;
  1.2417 +
  1.2418 +        newFocus.getDrawingRect(mTempRect);
  1.2419 +        offsetDescendantRectToMyCoords(newFocus, mTempRect);
  1.2420 +
  1.2421 +        if (direction == View.FOCUS_UP || direction == View.FOCUS_LEFT) {
  1.2422 +            final int start = (mIsVertical ? getPaddingTop() : getPaddingLeft());
  1.2423 +            final int newFocusStart = (mIsVertical ? mTempRect.top : mTempRect.left);
  1.2424 +
  1.2425 +            if (newFocusStart < start) {
  1.2426 +                amountToScroll = start - newFocusStart;
  1.2427 +                if (positionOfNewFocus > 0) {
  1.2428 +                    amountToScroll += getArrowScrollPreviewLength();
  1.2429 +                }
  1.2430 +            }
  1.2431 +        } else {
  1.2432 +            final int end = (mIsVertical ? getHeight() - getPaddingBottom() :
  1.2433 +                getWidth() - getPaddingRight());
  1.2434 +            final int newFocusEnd = (mIsVertical ? mTempRect.bottom : mTempRect.right);
  1.2435 +
  1.2436 +            if (newFocusEnd > end) {
  1.2437 +                amountToScroll = newFocusEnd - end;
  1.2438 +                if (positionOfNewFocus < mItemCount - 1) {
  1.2439 +                    amountToScroll += getArrowScrollPreviewLength();
  1.2440 +                }
  1.2441 +            }
  1.2442 +        }
  1.2443 +
  1.2444 +        return amountToScroll;
  1.2445 +    }
  1.2446 +
  1.2447 +    /**
  1.2448 +     * Determine the distance to the nearest edge of a view in a particular
  1.2449 +     * direction.
  1.2450 +     *
  1.2451 +     * @param descendant A descendant of this list.
  1.2452 +     * @return The distance, or 0 if the nearest edge is already on screen.
  1.2453 +     */
  1.2454 +    private int distanceToView(View descendant) {
  1.2455 +        descendant.getDrawingRect(mTempRect);
  1.2456 +        offsetDescendantRectToMyCoords(descendant, mTempRect);
  1.2457 +
  1.2458 +        final int start = (mIsVertical ? getPaddingTop() : getPaddingLeft());
  1.2459 +        final int end = (mIsVertical ? getHeight() - getPaddingBottom() :
  1.2460 +            getWidth() - getPaddingRight());
  1.2461 +
  1.2462 +        final int viewStart = (mIsVertical ? mTempRect.top : mTempRect.left);
  1.2463 +        final int viewEnd = (mIsVertical ? mTempRect.bottom : mTempRect.right);
  1.2464 +
  1.2465 +        int distance = 0;
  1.2466 +        if (viewEnd < start) {
  1.2467 +            distance = start - viewEnd;
  1.2468 +        } else if (viewStart > end) {
  1.2469 +            distance = viewStart - end;
  1.2470 +        }
  1.2471 +
  1.2472 +        return distance;
  1.2473 +    }
  1.2474 +
  1.2475 +    private boolean handleKeyScroll(KeyEvent event, int count, int direction) {
  1.2476 +        boolean handled = false;
  1.2477 +
  1.2478 +        if (KeyEventCompat.hasNoModifiers(event)) {
  1.2479 +            handled = resurrectSelectionIfNeeded();
  1.2480 +            if (!handled) {
  1.2481 +                while (count-- > 0) {
  1.2482 +                    if (arrowScroll(direction)) {
  1.2483 +                        handled = true;
  1.2484 +                    } else {
  1.2485 +                        break;
  1.2486 +                    }
  1.2487 +                }
  1.2488 +            }
  1.2489 +        } else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_ALT_ON)) {
  1.2490 +            handled = resurrectSelectionIfNeeded() || fullScroll(direction);
  1.2491 +        }
  1.2492 +
  1.2493 +        return handled;
  1.2494 +    }
  1.2495 +
  1.2496 +    private boolean handleKeyEvent(int keyCode, int count, KeyEvent event) {
  1.2497 +        if (mAdapter == null || !mIsAttached) {
  1.2498 +            return false;
  1.2499 +        }
  1.2500 +
  1.2501 +        if (mDataChanged) {
  1.2502 +            layoutChildren();
  1.2503 +        }
  1.2504 +
  1.2505 +        boolean handled = false;
  1.2506 +        final int action = event.getAction();
  1.2507 +
  1.2508 +        if (action != KeyEvent.ACTION_UP) {
  1.2509 +            switch (keyCode) {
  1.2510 +            case KeyEvent.KEYCODE_DPAD_UP:
  1.2511 +                if (mIsVertical) {
  1.2512 +                    handled = handleKeyScroll(event, count, View.FOCUS_UP);
  1.2513 +                } else if (KeyEventCompat.hasNoModifiers(event)) {
  1.2514 +                    handled = handleFocusWithinItem(View.FOCUS_UP);
  1.2515 +                }
  1.2516 +                break;
  1.2517 +
  1.2518 +            case KeyEvent.KEYCODE_DPAD_DOWN: {
  1.2519 +                if (mIsVertical) {
  1.2520 +                    handled = handleKeyScroll(event, count, View.FOCUS_DOWN);
  1.2521 +                } else if (KeyEventCompat.hasNoModifiers(event)) {
  1.2522 +                    handled = handleFocusWithinItem(View.FOCUS_DOWN);
  1.2523 +                }
  1.2524 +                break;
  1.2525 +            }
  1.2526 +
  1.2527 +            case KeyEvent.KEYCODE_DPAD_LEFT:
  1.2528 +                if (!mIsVertical) {
  1.2529 +                    handled = handleKeyScroll(event, count, View.FOCUS_LEFT);
  1.2530 +                } else if (KeyEventCompat.hasNoModifiers(event)) {
  1.2531 +                    handled = handleFocusWithinItem(View.FOCUS_LEFT);
  1.2532 +                }
  1.2533 +                break;
  1.2534 +
  1.2535 +            case KeyEvent.KEYCODE_DPAD_RIGHT:
  1.2536 +                if (!mIsVertical) {
  1.2537 +                    handled = handleKeyScroll(event, count, View.FOCUS_RIGHT);
  1.2538 +                } else if (KeyEventCompat.hasNoModifiers(event)) {
  1.2539 +                    handled = handleFocusWithinItem(View.FOCUS_RIGHT);
  1.2540 +                }
  1.2541 +                break;
  1.2542 +
  1.2543 +            case KeyEvent.KEYCODE_DPAD_CENTER:
  1.2544 +            case KeyEvent.KEYCODE_ENTER:
  1.2545 +                if (KeyEventCompat.hasNoModifiers(event)) {
  1.2546 +                    handled = resurrectSelectionIfNeeded();
  1.2547 +                    if (!handled
  1.2548 +                            && event.getRepeatCount() == 0 && getChildCount() > 0) {
  1.2549 +                        keyPressed();
  1.2550 +                        handled = true;
  1.2551 +                    }
  1.2552 +                }
  1.2553 +                break;
  1.2554 +
  1.2555 +            case KeyEvent.KEYCODE_SPACE:
  1.2556 +                if (KeyEventCompat.hasNoModifiers(event)) {
  1.2557 +                    handled = resurrectSelectionIfNeeded() ||
  1.2558 +                            pageScroll(mIsVertical ? View.FOCUS_DOWN : View.FOCUS_RIGHT);
  1.2559 +                } else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_SHIFT_ON)) {
  1.2560 +                    handled = resurrectSelectionIfNeeded() ||
  1.2561 +                            fullScroll(mIsVertical ? View.FOCUS_UP : View.FOCUS_LEFT);
  1.2562 +                }
  1.2563 +
  1.2564 +                handled = true;
  1.2565 +                break;
  1.2566 +
  1.2567 +            case KeyEvent.KEYCODE_PAGE_UP:
  1.2568 +                if (KeyEventCompat.hasNoModifiers(event)) {
  1.2569 +                    handled = resurrectSelectionIfNeeded() ||
  1.2570 +                            pageScroll(mIsVertical ? View.FOCUS_UP : View.FOCUS_LEFT);
  1.2571 +                } else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_ALT_ON)) {
  1.2572 +                    handled = resurrectSelectionIfNeeded() ||
  1.2573 +                            fullScroll(mIsVertical ? View.FOCUS_UP : View.FOCUS_LEFT);
  1.2574 +                }
  1.2575 +                break;
  1.2576 +
  1.2577 +            case KeyEvent.KEYCODE_PAGE_DOWN:
  1.2578 +                if (KeyEventCompat.hasNoModifiers(event)) {
  1.2579 +                    handled = resurrectSelectionIfNeeded() ||
  1.2580 +                            pageScroll(mIsVertical ? View.FOCUS_DOWN : View.FOCUS_RIGHT);
  1.2581 +                } else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_ALT_ON)) {
  1.2582 +                    handled = resurrectSelectionIfNeeded() ||
  1.2583 +                            fullScroll(mIsVertical ? View.FOCUS_DOWN : View.FOCUS_RIGHT);
  1.2584 +                }
  1.2585 +                break;
  1.2586 +
  1.2587 +            case KeyEvent.KEYCODE_MOVE_HOME:
  1.2588 +                if (KeyEventCompat.hasNoModifiers(event)) {
  1.2589 +                    handled = resurrectSelectionIfNeeded() ||
  1.2590 +                            fullScroll(mIsVertical ? View.FOCUS_UP : View.FOCUS_LEFT);
  1.2591 +                }
  1.2592 +                break;
  1.2593 +
  1.2594 +            case KeyEvent.KEYCODE_MOVE_END:
  1.2595 +                if (KeyEventCompat.hasNoModifiers(event)) {
  1.2596 +                    handled = resurrectSelectionIfNeeded() ||
  1.2597 +                            fullScroll(mIsVertical ? View.FOCUS_DOWN : View.FOCUS_RIGHT);
  1.2598 +                }
  1.2599 +                break;
  1.2600 +            }
  1.2601 +        }
  1.2602 +
  1.2603 +        if (handled) {
  1.2604 +            return true;
  1.2605 +        }
  1.2606 +
  1.2607 +        switch (action) {
  1.2608 +        case KeyEvent.ACTION_DOWN:
  1.2609 +            return super.onKeyDown(keyCode, event);
  1.2610 +
  1.2611 +        case KeyEvent.ACTION_UP:
  1.2612 +            if (!isEnabled()) {
  1.2613 +                return true;
  1.2614 +            }
  1.2615 +
  1.2616 +            if (isClickable() && isPressed() &&
  1.2617 +                    mSelectedPosition >= 0 && mAdapter != null &&
  1.2618 +                    mSelectedPosition < mAdapter.getCount()) {
  1.2619 +
  1.2620 +                final View child = getChildAt(mSelectedPosition - mFirstPosition);
  1.2621 +                if (child != null) {
  1.2622 +                    performItemClick(child, mSelectedPosition, mSelectedRowId);
  1.2623 +                    child.setPressed(false);
  1.2624 +                }
  1.2625 +
  1.2626 +                setPressed(false);
  1.2627 +                return true;
  1.2628 +            }
  1.2629 +
  1.2630 +            return false;
  1.2631 +
  1.2632 +        case KeyEvent.ACTION_MULTIPLE:
  1.2633 +            return super.onKeyMultiple(keyCode, count, event);
  1.2634 +
  1.2635 +        default:
  1.2636 +            return false;
  1.2637 +        }
  1.2638 +    }
  1.2639 +
  1.2640 +    private void initOrResetVelocityTracker() {
  1.2641 +        if (mVelocityTracker == null) {
  1.2642 +            mVelocityTracker = VelocityTracker.obtain();
  1.2643 +        } else {
  1.2644 +            mVelocityTracker.clear();
  1.2645 +        }
  1.2646 +    }
  1.2647 +
  1.2648 +    private void initVelocityTrackerIfNotExists() {
  1.2649 +        if (mVelocityTracker == null) {
  1.2650 +            mVelocityTracker = VelocityTracker.obtain();
  1.2651 +        }
  1.2652 +    }
  1.2653 +
  1.2654 +    private void recycleVelocityTracker() {
  1.2655 +        if (mVelocityTracker != null) {
  1.2656 +            mVelocityTracker.recycle();
  1.2657 +            mVelocityTracker = null;
  1.2658 +        }
  1.2659 +    }
  1.2660 +
  1.2661 +    /**
  1.2662 +     * Notify our scroll listener (if there is one) of a change in scroll state
  1.2663 +     */
  1.2664 +    private void invokeOnItemScrollListener() {
  1.2665 +        if (mOnScrollListener != null) {
  1.2666 +            mOnScrollListener.onScroll(this, mFirstPosition, getChildCount(), mItemCount);
  1.2667 +        }
  1.2668 +
  1.2669 +        // Dummy values, View's implementation does not use these.
  1.2670 +        onScrollChanged(0, 0, 0, 0);
  1.2671 +    }
  1.2672 +
  1.2673 +    private void reportScrollStateChange(int newState) {
  1.2674 +        if (newState == mLastScrollState) {
  1.2675 +            return;
  1.2676 +        }
  1.2677 +
  1.2678 +        if (mOnScrollListener != null) {
  1.2679 +            mLastScrollState = newState;
  1.2680 +            mOnScrollListener.onScrollStateChanged(this, newState);
  1.2681 +        }
  1.2682 +    }
  1.2683 +
  1.2684 +    private boolean maybeStartScrolling(int delta) {
  1.2685 +        final boolean isOverScroll = (mOverScroll != 0);
  1.2686 +        if (Math.abs(delta) <= mTouchSlop && !isOverScroll) {
  1.2687 +            return false;
  1.2688 +        }
  1.2689 +
  1.2690 +        if (isOverScroll) {
  1.2691 +            mTouchMode = TOUCH_MODE_OVERSCROLL;
  1.2692 +        } else {
  1.2693 +            mTouchMode = TOUCH_MODE_DRAGGING;
  1.2694 +        }
  1.2695 +
  1.2696 +        // Time to start stealing events! Once we've stolen them, don't
  1.2697 +        // let anyone steal from us.
  1.2698 +        final ViewParent parent = getParent();
  1.2699 +        if (parent != null) {
  1.2700 +            parent.requestDisallowInterceptTouchEvent(true);
  1.2701 +        }
  1.2702 +
  1.2703 +        cancelCheckForLongPress();
  1.2704 +
  1.2705 +        setPressed(false);
  1.2706 +        View motionView = getChildAt(mMotionPosition - mFirstPosition);
  1.2707 +        if (motionView != null) {
  1.2708 +            motionView.setPressed(false);
  1.2709 +        }
  1.2710 +
  1.2711 +        reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
  1.2712 +
  1.2713 +        return true;
  1.2714 +    }
  1.2715 +
  1.2716 +    private void maybeScroll(int delta) {
  1.2717 +        if (mTouchMode == TOUCH_MODE_DRAGGING) {
  1.2718 +            handleDragChange(delta);
  1.2719 +        } else if (mTouchMode == TOUCH_MODE_OVERSCROLL) {
  1.2720 +            handleOverScrollChange(delta);
  1.2721 +        }
  1.2722 +    }
  1.2723 +
  1.2724 +    private void handleDragChange(int delta) {
  1.2725 +        // Time to start stealing events! Once we've stolen them, don't
  1.2726 +        // let anyone steal from us.
  1.2727 +        final ViewParent parent = getParent();
  1.2728 +        if (parent != null) {
  1.2729 +            parent.requestDisallowInterceptTouchEvent(true);
  1.2730 +        }
  1.2731 +
  1.2732 +        final int motionIndex;
  1.2733 +        if (mMotionPosition >= 0) {
  1.2734 +            motionIndex = mMotionPosition - mFirstPosition;
  1.2735 +        } else {
  1.2736 +            // If we don't have a motion position that we can reliably track,
  1.2737 +            // pick something in the middle to make a best guess at things below.
  1.2738 +            motionIndex = getChildCount() / 2;
  1.2739 +        }
  1.2740 +
  1.2741 +        int motionViewPrevStart = 0;
  1.2742 +        View motionView = this.getChildAt(motionIndex);
  1.2743 +        if (motionView != null) {
  1.2744 +            motionViewPrevStart = (mIsVertical ? motionView.getTop() : motionView.getLeft());
  1.2745 +        }
  1.2746 +
  1.2747 +        boolean atEdge = trackMotionScroll(delta);
  1.2748 +
  1.2749 +        motionView = this.getChildAt(motionIndex);
  1.2750 +        if (motionView != null) {
  1.2751 +            final int motionViewRealStart =
  1.2752 +                    (mIsVertical ? motionView.getTop() : motionView.getLeft());
  1.2753 +
  1.2754 +            if (atEdge) {
  1.2755 +                final int overscroll = -delta - (motionViewRealStart - motionViewPrevStart);
  1.2756 +                updateOverScrollState(delta, overscroll);
  1.2757 +            }
  1.2758 +        }
  1.2759 +    }
  1.2760 +
  1.2761 +    private void updateOverScrollState(int delta, int overscroll) {
  1.2762 +        overScrollByInternal((mIsVertical ? 0 : overscroll),
  1.2763 +                             (mIsVertical ? overscroll : 0),
  1.2764 +                             (mIsVertical ? 0 : mOverScroll),
  1.2765 +                             (mIsVertical ? mOverScroll : 0),
  1.2766 +                             0, 0,
  1.2767 +                             (mIsVertical ? 0 : mOverscrollDistance),
  1.2768 +                             (mIsVertical ? mOverscrollDistance : 0),
  1.2769 +                             true);
  1.2770 +
  1.2771 +        if (Math.abs(mOverscrollDistance) == Math.abs(mOverScroll)) {
  1.2772 +            // Break fling velocity if we impacted an edge
  1.2773 +            if (mVelocityTracker != null) {
  1.2774 +                mVelocityTracker.clear();
  1.2775 +            }
  1.2776 +        }
  1.2777 +
  1.2778 +        final int overscrollMode = ViewCompat.getOverScrollMode(this);
  1.2779 +        if (overscrollMode == ViewCompat.OVER_SCROLL_ALWAYS ||
  1.2780 +                (overscrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS && !contentFits())) {
  1.2781 +            mTouchMode = TOUCH_MODE_OVERSCROLL;
  1.2782 +
  1.2783 +            float pull = (float) overscroll / (mIsVertical ? getHeight() : getWidth());
  1.2784 +            if (delta > 0) {
  1.2785 +                mStartEdge.onPull(pull);
  1.2786 +
  1.2787 +                if (!mEndEdge.isFinished()) {
  1.2788 +                    mEndEdge.onRelease();
  1.2789 +                }
  1.2790 +            } else if (delta < 0) {
  1.2791 +                mEndEdge.onPull(pull);
  1.2792 +
  1.2793 +                if (!mStartEdge.isFinished()) {
  1.2794 +                    mStartEdge.onRelease();
  1.2795 +                }
  1.2796 +            }
  1.2797 +
  1.2798 +            if (delta != 0) {
  1.2799 +                ViewCompat.postInvalidateOnAnimation(this);
  1.2800 +            }
  1.2801 +        }
  1.2802 +    }
  1.2803 +
  1.2804 +    private void handleOverScrollChange(int delta) {
  1.2805 +        final int oldOverScroll = mOverScroll;
  1.2806 +        final int newOverScroll = oldOverScroll - delta;
  1.2807 +
  1.2808 +        int overScrollDistance = -delta;
  1.2809 +        if ((newOverScroll < 0 && oldOverScroll >= 0) ||
  1.2810 +                (newOverScroll > 0 && oldOverScroll <= 0)) {
  1.2811 +            overScrollDistance = -oldOverScroll;
  1.2812 +            delta += overScrollDistance;
  1.2813 +        } else {
  1.2814 +            delta = 0;
  1.2815 +        }
  1.2816 +
  1.2817 +        if (overScrollDistance != 0) {
  1.2818 +            updateOverScrollState(delta, overScrollDistance);
  1.2819 +        }
  1.2820 +
  1.2821 +        if (delta != 0) {
  1.2822 +            if (mOverScroll != 0) {
  1.2823 +                mOverScroll = 0;
  1.2824 +                ViewCompat.postInvalidateOnAnimation(this);
  1.2825 +            }
  1.2826 +
  1.2827 +            trackMotionScroll(delta);
  1.2828 +            mTouchMode = TOUCH_MODE_DRAGGING;
  1.2829 +
  1.2830 +            // We did not scroll the full amount. Treat this essentially like the
  1.2831 +            // start of a new touch scroll
  1.2832 +            mMotionPosition = findClosestMotionRowOrColumn((int) mLastTouchPos);
  1.2833 +            mTouchRemainderPos = 0;
  1.2834 +        }
  1.2835 +    }
  1.2836 +
  1.2837 +    /**
  1.2838 +     * What is the distance between the source and destination rectangles given the direction of
  1.2839 +     * focus navigation between them? The direction basically helps figure out more quickly what is
  1.2840 +     * self evident by the relationship between the rects...
  1.2841 +     *
  1.2842 +     * @param source the source rectangle
  1.2843 +     * @param dest the destination rectangle
  1.2844 +     * @param direction the direction
  1.2845 +     * @return the distance between the rectangles
  1.2846 +     */
  1.2847 +    private static int getDistance(Rect source, Rect dest, int direction) {
  1.2848 +        int sX, sY; // source x, y
  1.2849 +        int dX, dY; // dest x, y
  1.2850 +
  1.2851 +        switch (direction) {
  1.2852 +        case View.FOCUS_RIGHT:
  1.2853 +            sX = source.right;
  1.2854 +            sY = source.top + source.height() / 2;
  1.2855 +            dX = dest.left;
  1.2856 +            dY = dest.top + dest.height() / 2;
  1.2857 +            break;
  1.2858 +
  1.2859 +        case View.FOCUS_DOWN:
  1.2860 +            sX = source.left + source.width() / 2;
  1.2861 +            sY = source.bottom;
  1.2862 +            dX = dest.left + dest.width() / 2;
  1.2863 +            dY = dest.top;
  1.2864 +            break;
  1.2865 +
  1.2866 +        case View.FOCUS_LEFT:
  1.2867 +            sX = source.left;
  1.2868 +            sY = source.top + source.height() / 2;
  1.2869 +            dX = dest.right;
  1.2870 +            dY = dest.top + dest.height() / 2;
  1.2871 +            break;
  1.2872 +
  1.2873 +        case View.FOCUS_UP:
  1.2874 +            sX = source.left + source.width() / 2;
  1.2875 +            sY = source.top;
  1.2876 +            dX = dest.left + dest.width() / 2;
  1.2877 +            dY = dest.bottom;
  1.2878 +            break;
  1.2879 +
  1.2880 +        case View.FOCUS_FORWARD:
  1.2881 +        case View.FOCUS_BACKWARD:
  1.2882 +            sX = source.right + source.width() / 2;
  1.2883 +            sY = source.top + source.height() / 2;
  1.2884 +            dX = dest.left + dest.width() / 2;
  1.2885 +            dY = dest.top + dest.height() / 2;
  1.2886 +            break;
  1.2887 +
  1.2888 +        default:
  1.2889 +            throw new IllegalArgumentException("direction must be one of "
  1.2890 +                    + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, "
  1.2891 +                    + "FOCUS_FORWARD, FOCUS_BACKWARD}.");
  1.2892 +        }
  1.2893 +
  1.2894 +        int deltaX = dX - sX;
  1.2895 +        int deltaY = dY - sY;
  1.2896 +
  1.2897 +        return deltaY * deltaY + deltaX * deltaX;
  1.2898 +    }
  1.2899 +
  1.2900 +    private int findMotionRowOrColumn(int motionPos) {
  1.2901 +        int childCount = getChildCount();
  1.2902 +        if (childCount == 0) {
  1.2903 +            return INVALID_POSITION;
  1.2904 +        }
  1.2905 +
  1.2906 +        for (int i = 0; i < childCount; i++) {
  1.2907 +            View v = getChildAt(i);
  1.2908 +
  1.2909 +            if ((mIsVertical && motionPos <= v.getBottom()) ||
  1.2910 +                    (!mIsVertical && motionPos <= v.getRight())) {
  1.2911 +                return mFirstPosition + i;
  1.2912 +            }
  1.2913 +        }
  1.2914 +
  1.2915 +        return INVALID_POSITION;
  1.2916 +    }
  1.2917 +
  1.2918 +    private int findClosestMotionRowOrColumn(int motionPos) {
  1.2919 +        final int childCount = getChildCount();
  1.2920 +        if (childCount == 0) {
  1.2921 +            return INVALID_POSITION;
  1.2922 +        }
  1.2923 +
  1.2924 +        final int motionRow = findMotionRowOrColumn(motionPos);
  1.2925 +        if (motionRow != INVALID_POSITION) {
  1.2926 +            return motionRow;
  1.2927 +        } else {
  1.2928 +            return mFirstPosition + childCount - 1;
  1.2929 +        }
  1.2930 +    }
  1.2931 +
  1.2932 +    @TargetApi(9)
  1.2933 +    private int getScaledOverscrollDistance(ViewConfiguration vc) {
  1.2934 +        if (Build.VERSION.SDK_INT < 9) {
  1.2935 +            return 0;
  1.2936 +        }
  1.2937 +
  1.2938 +        return vc.getScaledOverscrollDistance();
  1.2939 +    }
  1.2940 +
  1.2941 +    private boolean contentFits() {
  1.2942 +        final int childCount = getChildCount();
  1.2943 +        if (childCount == 0) {
  1.2944 +            return true;
  1.2945 +        }
  1.2946 +
  1.2947 +        if (childCount != mItemCount) {
  1.2948 +            return false;
  1.2949 +        }
  1.2950 +
  1.2951 +        View first = getChildAt(0);
  1.2952 +        View last = getChildAt(childCount - 1);
  1.2953 +
  1.2954 +        if (mIsVertical) {
  1.2955 +            return first.getTop() >= getPaddingTop() &&
  1.2956 +                    last.getBottom() <= getHeight() - getPaddingBottom();
  1.2957 +        } else {
  1.2958 +            return first.getLeft() >= getPaddingLeft() &&
  1.2959 +                    last.getRight() <= getWidth() - getPaddingRight();
  1.2960 +        }
  1.2961 +    }
  1.2962 +
  1.2963 +    private void updateScrollbarsDirection() {
  1.2964 +        setHorizontalScrollBarEnabled(!mIsVertical);
  1.2965 +        setVerticalScrollBarEnabled(mIsVertical);
  1.2966 +    }
  1.2967 +
  1.2968 +    private void triggerCheckForTap() {
  1.2969 +        if (mPendingCheckForTap == null) {
  1.2970 +            mPendingCheckForTap = new CheckForTap();
  1.2971 +        }
  1.2972 +
  1.2973 +        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
  1.2974 +    }
  1.2975 +
  1.2976 +    private void cancelCheckForTap() {
  1.2977 +        if (mPendingCheckForTap == null) {
  1.2978 +            return;
  1.2979 +        }
  1.2980 +
  1.2981 +        removeCallbacks(mPendingCheckForTap);
  1.2982 +    }
  1.2983 +
  1.2984 +    private void triggerCheckForLongPress() {
  1.2985 +        if (mPendingCheckForLongPress == null) {
  1.2986 +            mPendingCheckForLongPress = new CheckForLongPress();
  1.2987 +        }
  1.2988 +
  1.2989 +        mPendingCheckForLongPress.rememberWindowAttachCount();
  1.2990 +
  1.2991 +        postDelayed(mPendingCheckForLongPress,
  1.2992 +                ViewConfiguration.getLongPressTimeout());
  1.2993 +    }
  1.2994 +
  1.2995 +    private void cancelCheckForLongPress() {
  1.2996 +        if (mPendingCheckForLongPress == null) {
  1.2997 +            return;
  1.2998 +        }
  1.2999 +
  1.3000 +        removeCallbacks(mPendingCheckForLongPress);
  1.3001 +    }
  1.3002 +
  1.3003 +    boolean trackMotionScroll(int incrementalDelta) {
  1.3004 +        final int childCount = getChildCount();
  1.3005 +        if (childCount == 0) {
  1.3006 +            return true;
  1.3007 +        }
  1.3008 +
  1.3009 +        final View first = getChildAt(0);
  1.3010 +        final int firstStart = (mIsVertical ? first.getTop() : first.getLeft());
  1.3011 +
  1.3012 +        final View last = getChildAt(childCount - 1);
  1.3013 +        final int lastEnd = (mIsVertical ? last.getBottom() : last.getRight());
  1.3014 +
  1.3015 +        final int paddingTop = getPaddingTop();
  1.3016 +        final int paddingBottom = getPaddingBottom();
  1.3017 +        final int paddingLeft = getPaddingLeft();
  1.3018 +        final int paddingRight = getPaddingRight();
  1.3019 +
  1.3020 +        final int paddingStart = (mIsVertical ? paddingTop : paddingLeft);
  1.3021 +
  1.3022 +        final int spaceBefore = paddingStart - firstStart;
  1.3023 +        final int end = (mIsVertical ? getHeight() - paddingBottom :
  1.3024 +            getWidth() - paddingRight);
  1.3025 +        final int spaceAfter = lastEnd - end;
  1.3026 +
  1.3027 +        final int size;
  1.3028 +        if (mIsVertical) {
  1.3029 +            size = getHeight() - paddingBottom - paddingTop;
  1.3030 +        } else {
  1.3031 +            size = getWidth() - paddingRight - paddingLeft;
  1.3032 +        }
  1.3033 +
  1.3034 +        if (incrementalDelta < 0) {
  1.3035 +            incrementalDelta = Math.max(-(size - 1), incrementalDelta);
  1.3036 +        } else {
  1.3037 +            incrementalDelta = Math.min(size - 1, incrementalDelta);
  1.3038 +        }
  1.3039 +
  1.3040 +        final int firstPosition = mFirstPosition;
  1.3041 +
  1.3042 +        final boolean cannotScrollDown = (firstPosition == 0 &&
  1.3043 +                firstStart >= paddingStart && incrementalDelta >= 0);
  1.3044 +        final boolean cannotScrollUp = (firstPosition + childCount == mItemCount &&
  1.3045 +                lastEnd <= end && incrementalDelta <= 0);
  1.3046 +
  1.3047 +        if (cannotScrollDown || cannotScrollUp) {
  1.3048 +            return incrementalDelta != 0;
  1.3049 +        }
  1.3050 +
  1.3051 +        final boolean inTouchMode = isInTouchMode();
  1.3052 +        if (inTouchMode) {
  1.3053 +            hideSelector();
  1.3054 +        }
  1.3055 +
  1.3056 +        int start = 0;
  1.3057 +        int count = 0;
  1.3058 +
  1.3059 +        final boolean down = (incrementalDelta < 0);
  1.3060 +        if (down) {
  1.3061 +            int childrenStart = -incrementalDelta + paddingStart;
  1.3062 +
  1.3063 +            for (int i = 0; i < childCount; i++) {
  1.3064 +                final View child = getChildAt(i);
  1.3065 +                final int childEnd = (mIsVertical ? child.getBottom() : child.getRight());
  1.3066 +
  1.3067 +                if (childEnd >= childrenStart) {
  1.3068 +                    break;
  1.3069 +                }
  1.3070 +
  1.3071 +                count++;
  1.3072 +                mRecycler.addScrapView(child, firstPosition + i);
  1.3073 +            }
  1.3074 +        } else {
  1.3075 +            int childrenEnd = end - incrementalDelta;
  1.3076 +
  1.3077 +            for (int i = childCount - 1; i >= 0; i--) {
  1.3078 +                final View child = getChildAt(i);
  1.3079 +                final int childStart = (mIsVertical ? child.getTop() : child.getLeft());
  1.3080 +
  1.3081 +                if (childStart <= childrenEnd) {
  1.3082 +                    break;
  1.3083 +                }
  1.3084 +
  1.3085 +                start = i;
  1.3086 +                count++;
  1.3087 +                mRecycler.addScrapView(child, firstPosition + i);
  1.3088 +            }
  1.3089 +        }
  1.3090 +
  1.3091 +        mBlockLayoutRequests = true;
  1.3092 +
  1.3093 +        if (count > 0) {
  1.3094 +            detachViewsFromParent(start, count);
  1.3095 +        }
  1.3096 +
  1.3097 +        // invalidate before moving the children to avoid unnecessary invalidate
  1.3098 +        // calls to bubble up from the children all the way to the top
  1.3099 +        if (!awakenScrollbarsInternal()) {
  1.3100 +           invalidate();
  1.3101 +        }
  1.3102 +
  1.3103 +        offsetChildren(incrementalDelta);
  1.3104 +
  1.3105 +        if (down) {
  1.3106 +            mFirstPosition += count;
  1.3107 +        }
  1.3108 +
  1.3109 +        final int absIncrementalDelta = Math.abs(incrementalDelta);
  1.3110 +        if (spaceBefore < absIncrementalDelta || spaceAfter < absIncrementalDelta) {
  1.3111 +            fillGap(down);
  1.3112 +        }
  1.3113 +
  1.3114 +        if (!inTouchMode && mSelectedPosition != INVALID_POSITION) {
  1.3115 +            final int childIndex = mSelectedPosition - mFirstPosition;
  1.3116 +            if (childIndex >= 0 && childIndex < getChildCount()) {
  1.3117 +                positionSelector(mSelectedPosition, getChildAt(childIndex));
  1.3118 +            }
  1.3119 +        } else if (mSelectorPosition != INVALID_POSITION) {
  1.3120 +            final int childIndex = mSelectorPosition - mFirstPosition;
  1.3121 +            if (childIndex >= 0 && childIndex < getChildCount()) {
  1.3122 +                positionSelector(INVALID_POSITION, getChildAt(childIndex));
  1.3123 +            }
  1.3124 +        } else {
  1.3125 +            mSelectorRect.setEmpty();
  1.3126 +        }
  1.3127 +
  1.3128 +        mBlockLayoutRequests = false;
  1.3129 +
  1.3130 +        invokeOnItemScrollListener();
  1.3131 +
  1.3132 +        return false;
  1.3133 +    }
  1.3134 +
  1.3135 +    @TargetApi(14)
  1.3136 +    private final float getCurrVelocity() {
  1.3137 +        if (Build.VERSION.SDK_INT >= 14) {
  1.3138 +            return mScroller.getCurrVelocity();
  1.3139 +        }
  1.3140 +
  1.3141 +        return 0;
  1.3142 +    }
  1.3143 +
  1.3144 +    @TargetApi(5)
  1.3145 +    private boolean awakenScrollbarsInternal() {
  1.3146 +        if (Build.VERSION.SDK_INT >= 5) {
  1.3147 +            return super.awakenScrollBars();
  1.3148 +        } else {
  1.3149 +            return false;
  1.3150 +        }
  1.3151 +    }
  1.3152 +
  1.3153 +    @Override
  1.3154 +    public void computeScroll() {
  1.3155 +        if (!mScroller.computeScrollOffset()) {
  1.3156 +            return;
  1.3157 +        }
  1.3158 +
  1.3159 +        final int pos;
  1.3160 +        if (mIsVertical) {
  1.3161 +            pos = mScroller.getCurrY();
  1.3162 +        } else {
  1.3163 +            pos = mScroller.getCurrX();
  1.3164 +        }
  1.3165 +
  1.3166 +        final int diff = (int) (pos - mLastTouchPos);
  1.3167 +        mLastTouchPos = pos;
  1.3168 +
  1.3169 +        final boolean stopped = trackMotionScroll(diff);
  1.3170 +
  1.3171 +        if (!stopped && !mScroller.isFinished()) {
  1.3172 +            ViewCompat.postInvalidateOnAnimation(this);
  1.3173 +        } else {
  1.3174 +            if (stopped) {
  1.3175 +                final int overScrollMode = ViewCompat.getOverScrollMode(this);
  1.3176 +                if (overScrollMode != ViewCompat.OVER_SCROLL_NEVER) {
  1.3177 +                    final EdgeEffectCompat edge =
  1.3178 +                            (diff > 0 ? mStartEdge : mEndEdge);
  1.3179 +
  1.3180 +                    boolean needsInvalidate =
  1.3181 +                            edge.onAbsorb(Math.abs((int) getCurrVelocity()));
  1.3182 +
  1.3183 +                    if (needsInvalidate) {
  1.3184 +                        ViewCompat.postInvalidateOnAnimation(this);
  1.3185 +                    }
  1.3186 +                }
  1.3187 +
  1.3188 +                mScroller.abortAnimation();
  1.3189 +            }
  1.3190 +
  1.3191 +            mTouchMode = TOUCH_MODE_REST;
  1.3192 +            reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
  1.3193 +        }
  1.3194 +    }
  1.3195 +
  1.3196 +    private void finishEdgeGlows() {
  1.3197 +        if (mStartEdge != null) {
  1.3198 +            mStartEdge.finish();
  1.3199 +        }
  1.3200 +
  1.3201 +        if (mEndEdge != null) {
  1.3202 +            mEndEdge.finish();
  1.3203 +        }
  1.3204 +    }
  1.3205 +
  1.3206 +    private boolean drawStartEdge(Canvas canvas) {
  1.3207 +        if (mStartEdge.isFinished()) {
  1.3208 +            return false;
  1.3209 +        }
  1.3210 +
  1.3211 +        if (mIsVertical) {
  1.3212 +            return mStartEdge.draw(canvas);
  1.3213 +        }
  1.3214 +
  1.3215 +        final int restoreCount = canvas.save();
  1.3216 +        final int height = getHeight() - getPaddingTop() - getPaddingBottom();
  1.3217 +
  1.3218 +        canvas.translate(0, height);
  1.3219 +        canvas.rotate(270);
  1.3220 +
  1.3221 +        final boolean needsInvalidate = mStartEdge.draw(canvas);
  1.3222 +        canvas.restoreToCount(restoreCount);
  1.3223 +        return needsInvalidate;
  1.3224 +    }
  1.3225 +
  1.3226 +    private boolean drawEndEdge(Canvas canvas) {
  1.3227 +        if (mEndEdge.isFinished()) {
  1.3228 +            return false;
  1.3229 +        }
  1.3230 +
  1.3231 +        final int restoreCount = canvas.save();
  1.3232 +        final int width = getWidth() - getPaddingLeft() - getPaddingRight();
  1.3233 +        final int height = getHeight() - getPaddingTop() - getPaddingBottom();
  1.3234 +
  1.3235 +        if (mIsVertical) {
  1.3236 +            canvas.translate(-width, height);
  1.3237 +            canvas.rotate(180, width, 0);
  1.3238 +        } else {
  1.3239 +            canvas.translate(width, 0);
  1.3240 +            canvas.rotate(90);
  1.3241 +        }
  1.3242 +
  1.3243 +        final boolean needsInvalidate = mEndEdge.draw(canvas);
  1.3244 +        canvas.restoreToCount(restoreCount);
  1.3245 +        return needsInvalidate;
  1.3246 +    }
  1.3247 +
  1.3248 +    private void drawSelector(Canvas canvas) {
  1.3249 +        if (!mSelectorRect.isEmpty()) {
  1.3250 +            final Drawable selector = mSelector;
  1.3251 +            selector.setBounds(mSelectorRect);
  1.3252 +            selector.draw(canvas);
  1.3253 +        }
  1.3254 +    }
  1.3255 +
  1.3256 +    private void useDefaultSelector() {
  1.3257 +        setSelector(getResources().getDrawable(
  1.3258 +                android.R.drawable.list_selector_background));
  1.3259 +    }
  1.3260 +
  1.3261 +    private boolean shouldShowSelector() {
  1.3262 +        return (hasFocus() && !isInTouchMode()) || touchModeDrawsInPressedState();
  1.3263 +    }
  1.3264 +
  1.3265 +    private void positionSelector(int position, View selected) {
  1.3266 +        if (position != INVALID_POSITION) {
  1.3267 +            mSelectorPosition = position;
  1.3268 +        }
  1.3269 +
  1.3270 +        mSelectorRect.set(selected.getLeft(), selected.getTop(), selected.getRight(),
  1.3271 +                selected.getBottom());
  1.3272 +
  1.3273 +        final boolean isChildViewEnabled = mIsChildViewEnabled;
  1.3274 +        if (selected.isEnabled() != isChildViewEnabled) {
  1.3275 +            mIsChildViewEnabled = !isChildViewEnabled;
  1.3276 +
  1.3277 +            if (getSelectedItemPosition() != INVALID_POSITION) {
  1.3278 +                refreshDrawableState();
  1.3279 +            }
  1.3280 +        }
  1.3281 +    }
  1.3282 +
  1.3283 +    private void hideSelector() {
  1.3284 +        if (mSelectedPosition != INVALID_POSITION) {
  1.3285 +            if (mLayoutMode != LAYOUT_SPECIFIC) {
  1.3286 +                mResurrectToPosition = mSelectedPosition;
  1.3287 +            }
  1.3288 +
  1.3289 +            if (mNextSelectedPosition >= 0 && mNextSelectedPosition != mSelectedPosition) {
  1.3290 +                mResurrectToPosition = mNextSelectedPosition;
  1.3291 +            }
  1.3292 +
  1.3293 +            setSelectedPositionInt(INVALID_POSITION);
  1.3294 +            setNextSelectedPositionInt(INVALID_POSITION);
  1.3295 +
  1.3296 +            mSelectedStart = 0;
  1.3297 +        }
  1.3298 +    }
  1.3299 +
  1.3300 +    private void setSelectedPositionInt(int position) {
  1.3301 +        mSelectedPosition = position;
  1.3302 +        mSelectedRowId = getItemIdAtPosition(position);
  1.3303 +    }
  1.3304 +
  1.3305 +    private void setSelectionInt(int position) {
  1.3306 +        setNextSelectedPositionInt(position);
  1.3307 +        boolean awakeScrollbars = false;
  1.3308 +
  1.3309 +        final int selectedPosition = mSelectedPosition;
  1.3310 +        if (selectedPosition >= 0) {
  1.3311 +            if (position == selectedPosition - 1) {
  1.3312 +                awakeScrollbars = true;
  1.3313 +            } else if (position == selectedPosition + 1) {
  1.3314 +                awakeScrollbars = true;
  1.3315 +            }
  1.3316 +        }
  1.3317 +
  1.3318 +        layoutChildren();
  1.3319 +
  1.3320 +        if (awakeScrollbars) {
  1.3321 +            awakenScrollbarsInternal();
  1.3322 +        }
  1.3323 +    }
  1.3324 +
  1.3325 +    private void setNextSelectedPositionInt(int position) {
  1.3326 +        mNextSelectedPosition = position;
  1.3327 +        mNextSelectedRowId = getItemIdAtPosition(position);
  1.3328 +
  1.3329 +        // If we are trying to sync to the selection, update that too
  1.3330 +        if (mNeedSync && mSyncMode == SYNC_SELECTED_POSITION && position >= 0) {
  1.3331 +            mSyncPosition = position;
  1.3332 +            mSyncRowId = mNextSelectedRowId;
  1.3333 +        }
  1.3334 +    }
  1.3335 +
  1.3336 +    private boolean touchModeDrawsInPressedState() {
  1.3337 +        switch (mTouchMode) {
  1.3338 +        case TOUCH_MODE_TAP:
  1.3339 +        case TOUCH_MODE_DONE_WAITING:
  1.3340 +            return true;
  1.3341 +        default:
  1.3342 +            return false;
  1.3343 +        }
  1.3344 +    }
  1.3345 +
  1.3346 +    /**
  1.3347 +     * Sets the selector state to "pressed" and posts a CheckForKeyLongPress to see if
  1.3348 +     * this is a long press.
  1.3349 +     */
  1.3350 +    private void keyPressed() {
  1.3351 +        if (!isEnabled() || !isClickable()) {
  1.3352 +            return;
  1.3353 +        }
  1.3354 +
  1.3355 +        final Drawable selector = mSelector;
  1.3356 +        final Rect selectorRect = mSelectorRect;
  1.3357 +
  1.3358 +        if (selector != null && (isFocused() || touchModeDrawsInPressedState())
  1.3359 +                && !selectorRect.isEmpty()) {
  1.3360 +
  1.3361 +            final View child = getChildAt(mSelectedPosition - mFirstPosition);
  1.3362 +
  1.3363 +            if (child != null) {
  1.3364 +                if (child.hasFocusable()) {
  1.3365 +                    return;
  1.3366 +                }
  1.3367 +
  1.3368 +                child.setPressed(true);
  1.3369 +            }
  1.3370 +
  1.3371 +            setPressed(true);
  1.3372 +
  1.3373 +            final boolean longClickable = isLongClickable();
  1.3374 +            final Drawable d = selector.getCurrent();
  1.3375 +            if (d != null && d instanceof TransitionDrawable) {
  1.3376 +                if (longClickable) {
  1.3377 +                    ((TransitionDrawable) d).startTransition(
  1.3378 +                            ViewConfiguration.getLongPressTimeout());
  1.3379 +                } else {
  1.3380 +                    ((TransitionDrawable) d).resetTransition();
  1.3381 +                }
  1.3382 +            }
  1.3383 +
  1.3384 +            if (longClickable && !mDataChanged) {
  1.3385 +                if (mPendingCheckForKeyLongPress == null) {
  1.3386 +                    mPendingCheckForKeyLongPress = new CheckForKeyLongPress();
  1.3387 +                }
  1.3388 +
  1.3389 +                mPendingCheckForKeyLongPress.rememberWindowAttachCount();
  1.3390 +                postDelayed(mPendingCheckForKeyLongPress, ViewConfiguration.getLongPressTimeout());
  1.3391 +            }
  1.3392 +        }
  1.3393 +    }
  1.3394 +
  1.3395 +    private void updateSelectorState() {
  1.3396 +        if (mSelector != null) {
  1.3397 +            if (shouldShowSelector()) {
  1.3398 +                mSelector.setState(getDrawableState());
  1.3399 +            } else {
  1.3400 +                mSelector.setState(STATE_NOTHING);
  1.3401 +            }
  1.3402 +        }
  1.3403 +    }
  1.3404 +
  1.3405 +    private void checkSelectionChanged() {
  1.3406 +        if ((mSelectedPosition != mOldSelectedPosition) || (mSelectedRowId != mOldSelectedRowId)) {
  1.3407 +            selectionChanged();
  1.3408 +            mOldSelectedPosition = mSelectedPosition;
  1.3409 +            mOldSelectedRowId = mSelectedRowId;
  1.3410 +        }
  1.3411 +    }
  1.3412 +
  1.3413 +    private void selectionChanged() {
  1.3414 +        OnItemSelectedListener listener = getOnItemSelectedListener();
  1.3415 +        if (listener == null) {
  1.3416 +            return;
  1.3417 +        }
  1.3418 +
  1.3419 +        if (mInLayout || mBlockLayoutRequests) {
  1.3420 +            // If we are in a layout traversal, defer notification
  1.3421 +            // by posting. This ensures that the view tree is
  1.3422 +            // in a consistent state and is able to accommodate
  1.3423 +            // new layout or invalidate requests.
  1.3424 +            if (mSelectionNotifier == null) {
  1.3425 +                mSelectionNotifier = new SelectionNotifier();
  1.3426 +            }
  1.3427 +
  1.3428 +            post(mSelectionNotifier);
  1.3429 +        } else {
  1.3430 +            fireOnSelected();
  1.3431 +            performAccessibilityActionsOnSelected();
  1.3432 +        }
  1.3433 +    }
  1.3434 +
  1.3435 +    private void fireOnSelected() {
  1.3436 +        OnItemSelectedListener listener = getOnItemSelectedListener();
  1.3437 +        if (listener == null) {
  1.3438 +            return;
  1.3439 +        }
  1.3440 +
  1.3441 +        final int selection = getSelectedItemPosition();
  1.3442 +        if (selection >= 0) {
  1.3443 +            View v = getSelectedView();
  1.3444 +            listener.onItemSelected(this, v, selection,
  1.3445 +                    mAdapter.getItemId(selection));
  1.3446 +        } else {
  1.3447 +            listener.onNothingSelected(this);
  1.3448 +        }
  1.3449 +    }
  1.3450 +
  1.3451 +    private void performAccessibilityActionsOnSelected() {
  1.3452 +        final int position = getSelectedItemPosition();
  1.3453 +        if (position >= 0) {
  1.3454 +            // We fire selection events here not in View
  1.3455 +            sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
  1.3456 +        }
  1.3457 +    }
  1.3458 +
  1.3459 +    private int lookForSelectablePosition(int position) {
  1.3460 +        return lookForSelectablePosition(position, true);
  1.3461 +    }
  1.3462 +
  1.3463 +    private int lookForSelectablePosition(int position, boolean lookDown) {
  1.3464 +        final ListAdapter adapter = mAdapter;
  1.3465 +        if (adapter == null || isInTouchMode()) {
  1.3466 +            return INVALID_POSITION;
  1.3467 +        }
  1.3468 +
  1.3469 +        final int itemCount = mItemCount;
  1.3470 +        if (!mAreAllItemsSelectable) {
  1.3471 +            if (lookDown) {
  1.3472 +                position = Math.max(0, position);
  1.3473 +                while (position < itemCount && !adapter.isEnabled(position)) {
  1.3474 +                    position++;
  1.3475 +                }
  1.3476 +            } else {
  1.3477 +                position = Math.min(position, itemCount - 1);
  1.3478 +                while (position >= 0 && !adapter.isEnabled(position)) {
  1.3479 +                    position--;
  1.3480 +                }
  1.3481 +            }
  1.3482 +
  1.3483 +            if (position < 0 || position >= itemCount) {
  1.3484 +                return INVALID_POSITION;
  1.3485 +            }
  1.3486 +
  1.3487 +            return position;
  1.3488 +        } else {
  1.3489 +            if (position < 0 || position >= itemCount) {
  1.3490 +                return INVALID_POSITION;
  1.3491 +            }
  1.3492 +
  1.3493 +            return position;
  1.3494 +        }
  1.3495 +    }
  1.3496 +
  1.3497 +    /**
  1.3498 +     * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} or
  1.3499 +     *        {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT} depending on the
  1.3500 +     *        current view orientation.
  1.3501 +     *
  1.3502 +     * @return The position of the next selectable position of the views that
  1.3503 +     *         are currently visible, taking into account the fact that there might
  1.3504 +     *         be no selection.  Returns {@link #INVALID_POSITION} if there is no
  1.3505 +     *         selectable view on screen in the given direction.
  1.3506 +     */
  1.3507 +    private int lookForSelectablePositionOnScreen(int direction) {
  1.3508 +        forceValidFocusDirection(direction);
  1.3509 +
  1.3510 +        final int firstPosition = mFirstPosition;
  1.3511 +        final ListAdapter adapter = getAdapter();
  1.3512 +
  1.3513 +        if (direction == View.FOCUS_DOWN || direction == View.FOCUS_RIGHT) {
  1.3514 +            int startPos = (mSelectedPosition != INVALID_POSITION ?
  1.3515 +                    mSelectedPosition + 1 : firstPosition);
  1.3516 +
  1.3517 +            if (startPos >= adapter.getCount()) {
  1.3518 +                return INVALID_POSITION;
  1.3519 +            }
  1.3520 +
  1.3521 +            if (startPos < firstPosition) {
  1.3522 +                startPos = firstPosition;
  1.3523 +            }
  1.3524 +
  1.3525 +            final int lastVisiblePos = getLastVisiblePosition();
  1.3526 +
  1.3527 +            for (int pos = startPos; pos <= lastVisiblePos; pos++) {
  1.3528 +                if (adapter.isEnabled(pos)
  1.3529 +                        && getChildAt(pos - firstPosition).getVisibility() == View.VISIBLE) {
  1.3530 +                    return pos;
  1.3531 +                }
  1.3532 +            }
  1.3533 +        } else {
  1.3534 +            final int last = firstPosition + getChildCount() - 1;
  1.3535 +
  1.3536 +            int startPos = (mSelectedPosition != INVALID_POSITION) ?
  1.3537 +                    mSelectedPosition - 1 : firstPosition + getChildCount() - 1;
  1.3538 +
  1.3539 +            if (startPos < 0 || startPos >= adapter.getCount()) {
  1.3540 +                return INVALID_POSITION;
  1.3541 +            }
  1.3542 +
  1.3543 +            if (startPos > last) {
  1.3544 +                startPos = last;
  1.3545 +            }
  1.3546 +
  1.3547 +            for (int pos = startPos; pos >= firstPosition; pos--) {
  1.3548 +                if (adapter.isEnabled(pos)
  1.3549 +                        && getChildAt(pos - firstPosition).getVisibility() == View.VISIBLE) {
  1.3550 +                    return pos;
  1.3551 +                }
  1.3552 +            }
  1.3553 +        }
  1.3554 +
  1.3555 +        return INVALID_POSITION;
  1.3556 +    }
  1.3557 +
  1.3558 +    @Override
  1.3559 +    protected void drawableStateChanged() {
  1.3560 +        super.drawableStateChanged();
  1.3561 +        updateSelectorState();
  1.3562 +    }
  1.3563 +
  1.3564 +    @Override
  1.3565 +    protected int[] onCreateDrawableState(int extraSpace) {
  1.3566 +        // If the child view is enabled then do the default behavior.
  1.3567 +        if (mIsChildViewEnabled) {
  1.3568 +            // Common case
  1.3569 +            return super.onCreateDrawableState(extraSpace);
  1.3570 +        }
  1.3571 +
  1.3572 +        // The selector uses this View's drawable state. The selected child view
  1.3573 +        // is disabled, so we need to remove the enabled state from the drawable
  1.3574 +        // states.
  1.3575 +        final int enabledState = ENABLED_STATE_SET[0];
  1.3576 +
  1.3577 +        // If we don't have any extra space, it will return one of the static state arrays,
  1.3578 +        // and clearing the enabled state on those arrays is a bad thing!  If we specify
  1.3579 +        // we need extra space, it will create+copy into a new array that safely mutable.
  1.3580 +        int[] state = super.onCreateDrawableState(extraSpace + 1);
  1.3581 +        int enabledPos = -1;
  1.3582 +        for (int i = state.length - 1; i >= 0; i--) {
  1.3583 +            if (state[i] == enabledState) {
  1.3584 +                enabledPos = i;
  1.3585 +                break;
  1.3586 +            }
  1.3587 +        }
  1.3588 +
  1.3589 +        // Remove the enabled state
  1.3590 +        if (enabledPos >= 0) {
  1.3591 +            System.arraycopy(state, enabledPos + 1, state, enabledPos,
  1.3592 +                    state.length - enabledPos - 1);
  1.3593 +        }
  1.3594 +
  1.3595 +        return state;
  1.3596 +    }
  1.3597 +
  1.3598 +    @Override
  1.3599 +    protected boolean canAnimate() {
  1.3600 +        return (super.canAnimate() && mItemCount > 0);
  1.3601 +    }
  1.3602 +
  1.3603 +    @Override
  1.3604 +    protected void dispatchDraw(Canvas canvas) {
  1.3605 +        final boolean drawSelectorOnTop = mDrawSelectorOnTop;
  1.3606 +        if (!drawSelectorOnTop) {
  1.3607 +            drawSelector(canvas);
  1.3608 +        }
  1.3609 +
  1.3610 +        super.dispatchDraw(canvas);
  1.3611 +
  1.3612 +        if (drawSelectorOnTop) {
  1.3613 +            drawSelector(canvas);
  1.3614 +        }
  1.3615 +    }
  1.3616 +
  1.3617 +    @Override
  1.3618 +    public void draw(Canvas canvas) {
  1.3619 +        super.draw(canvas);
  1.3620 +
  1.3621 +        boolean needsInvalidate = false;
  1.3622 +
  1.3623 +        if (mStartEdge != null) {
  1.3624 +            needsInvalidate |= drawStartEdge(canvas);
  1.3625 +        }
  1.3626 +
  1.3627 +        if (mEndEdge != null) {
  1.3628 +            needsInvalidate |= drawEndEdge(canvas);
  1.3629 +        }
  1.3630 +
  1.3631 +        if (needsInvalidate) {
  1.3632 +            ViewCompat.postInvalidateOnAnimation(this);
  1.3633 +        }
  1.3634 +    }
  1.3635 +
  1.3636 +    @Override
  1.3637 +    public void requestLayout() {
  1.3638 +        if (!mInLayout && !mBlockLayoutRequests) {
  1.3639 +            super.requestLayout();
  1.3640 +        }
  1.3641 +    }
  1.3642 +
  1.3643 +    @Override
  1.3644 +    public View getSelectedView() {
  1.3645 +        if (mItemCount > 0 && mSelectedPosition >= 0) {
  1.3646 +            return getChildAt(mSelectedPosition - mFirstPosition);
  1.3647 +        } else {
  1.3648 +            return null;
  1.3649 +        }
  1.3650 +    }
  1.3651 +
  1.3652 +    @Override
  1.3653 +    public void setSelection(int position) {
  1.3654 +        setSelectionFromOffset(position, 0);
  1.3655 +    }
  1.3656 +
  1.3657 +    public void setSelectionFromOffset(int position, int offset) {
  1.3658 +        if (mAdapter == null) {
  1.3659 +            return;
  1.3660 +        }
  1.3661 +
  1.3662 +        if (!isInTouchMode()) {
  1.3663 +            position = lookForSelectablePosition(position);
  1.3664 +            if (position >= 0) {
  1.3665 +                setNextSelectedPositionInt(position);
  1.3666 +            }
  1.3667 +        } else {
  1.3668 +            mResurrectToPosition = position;
  1.3669 +        }
  1.3670 +
  1.3671 +        if (position >= 0) {
  1.3672 +            mLayoutMode = LAYOUT_SPECIFIC;
  1.3673 +
  1.3674 +            if (mIsVertical) {
  1.3675 +                mSpecificStart = getPaddingTop() + offset;
  1.3676 +            } else {
  1.3677 +                mSpecificStart = getPaddingLeft() + offset;
  1.3678 +            }
  1.3679 +
  1.3680 +            if (mNeedSync) {
  1.3681 +                mSyncPosition = position;
  1.3682 +                mSyncRowId = mAdapter.getItemId(position);
  1.3683 +            }
  1.3684 +
  1.3685 +            requestLayout();
  1.3686 +        }
  1.3687 +    }
  1.3688 +
  1.3689 +    @Override
  1.3690 +    public boolean dispatchKeyEvent(KeyEvent event) {
  1.3691 +        // Dispatch in the normal way
  1.3692 +        boolean handled = super.dispatchKeyEvent(event);
  1.3693 +        if (!handled) {
  1.3694 +            // If we didn't handle it...
  1.3695 +            final View focused = getFocusedChild();
  1.3696 +            if (focused != null && event.getAction() == KeyEvent.ACTION_DOWN) {
  1.3697 +                // ... and our focused child didn't handle it
  1.3698 +                // ... give it to ourselves so we can scroll if necessary
  1.3699 +                handled = onKeyDown(event.getKeyCode(), event);
  1.3700 +            }
  1.3701 +        }
  1.3702 +
  1.3703 +        return handled;
  1.3704 +    }
  1.3705 +
  1.3706 +    @Override
  1.3707 +    protected void dispatchSetPressed(boolean pressed) {
  1.3708 +        // Don't dispatch setPressed to our children. We call setPressed on ourselves to
  1.3709 +        // get the selector in the right state, but we don't want to press each child.
  1.3710 +    }
  1.3711 +
  1.3712 +    @Override
  1.3713 +    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  1.3714 +        if (mSelector == null) {
  1.3715 +            useDefaultSelector();
  1.3716 +        }
  1.3717 +
  1.3718 +        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
  1.3719 +        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
  1.3720 +        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
  1.3721 +        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
  1.3722 +
  1.3723 +        int childWidth = 0;
  1.3724 +        int childHeight = 0;
  1.3725 +
  1.3726 +        mItemCount = (mAdapter == null ? 0 : mAdapter.getCount());
  1.3727 +        if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED ||
  1.3728 +                heightMode == MeasureSpec.UNSPECIFIED)) {
  1.3729 +            final View child = obtainView(0, mIsScrap);
  1.3730 +
  1.3731 +            final int secondaryMeasureSpec =
  1.3732 +                    (mIsVertical ? widthMeasureSpec : heightMeasureSpec);
  1.3733 +
  1.3734 +            measureScrapChild(child, 0, secondaryMeasureSpec);
  1.3735 +
  1.3736 +            childWidth = child.getMeasuredWidth();
  1.3737 +            childHeight = child.getMeasuredHeight();
  1.3738 +
  1.3739 +            if (recycleOnMeasure()) {
  1.3740 +                mRecycler.addScrapView(child, -1);
  1.3741 +            }
  1.3742 +        }
  1.3743 +
  1.3744 +        if (widthMode == MeasureSpec.UNSPECIFIED) {
  1.3745 +            widthSize = getPaddingLeft() + getPaddingRight() + childWidth;
  1.3746 +            if (mIsVertical) {
  1.3747 +                widthSize += getVerticalScrollbarWidth();
  1.3748 +            }
  1.3749 +        }
  1.3750 +
  1.3751 +        if (heightMode == MeasureSpec.UNSPECIFIED) {
  1.3752 +            heightSize = getPaddingTop() + getPaddingBottom() + childHeight;
  1.3753 +            if (!mIsVertical) {
  1.3754 +                heightSize += getHorizontalScrollbarHeight();
  1.3755 +            }
  1.3756 +        }
  1.3757 +
  1.3758 +        if (mIsVertical && heightMode == MeasureSpec.AT_MOST) {
  1.3759 +            heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
  1.3760 +        }
  1.3761 +
  1.3762 +        if (!mIsVertical && widthMode == MeasureSpec.AT_MOST) {
  1.3763 +            widthSize = measureWidthOfChildren(heightMeasureSpec, 0, NO_POSITION, widthSize, -1);
  1.3764 +        }
  1.3765 +
  1.3766 +        setMeasuredDimension(widthSize, heightSize);
  1.3767 +    }
  1.3768 +
  1.3769 +    @Override
  1.3770 +    protected void onLayout(boolean changed, int l, int t, int r, int b) {
  1.3771 +        mInLayout = true;
  1.3772 +
  1.3773 +        if (changed) {
  1.3774 +            final int childCount = getChildCount();
  1.3775 +            for (int i = 0; i < childCount; i++) {
  1.3776 +                getChildAt(i).forceLayout();
  1.3777 +            }
  1.3778 +
  1.3779 +            mRecycler.markChildrenDirty();
  1.3780 +        }
  1.3781 +
  1.3782 +        layoutChildren();
  1.3783 +
  1.3784 +        mInLayout = false;
  1.3785 +
  1.3786 +        final int width = r - l - getPaddingLeft() - getPaddingRight();
  1.3787 +        final int height = b - t - getPaddingTop() - getPaddingBottom();
  1.3788 +
  1.3789 +        if (mStartEdge != null && mEndEdge != null) {
  1.3790 +            if (mIsVertical) {
  1.3791 +                mStartEdge.setSize(width, height);
  1.3792 +                mEndEdge.setSize(width, height);
  1.3793 +            } else {
  1.3794 +                mStartEdge.setSize(height, width);
  1.3795 +                mEndEdge.setSize(height, width);
  1.3796 +            }
  1.3797 +        }
  1.3798 +    }
  1.3799 +
  1.3800 +    private void layoutChildren() {
  1.3801 +        if (getWidth() == 0 || getHeight() == 0) {
  1.3802 +            return;
  1.3803 +        }
  1.3804 +
  1.3805 +        final boolean blockLayoutRequests = mBlockLayoutRequests;
  1.3806 +        if (!blockLayoutRequests) {
  1.3807 +            mBlockLayoutRequests = true;
  1.3808 +        } else {
  1.3809 +            return;
  1.3810 +        }
  1.3811 +
  1.3812 +        try {
  1.3813 +            invalidate();
  1.3814 +
  1.3815 +            if (mAdapter == null) {
  1.3816 +                resetState();
  1.3817 +                return;
  1.3818 +            }
  1.3819 +
  1.3820 +            final int start = (mIsVertical ? getPaddingTop() : getPaddingLeft());
  1.3821 +            final int end =
  1.3822 +                    (mIsVertical ? getHeight() - getPaddingBottom() : getWidth() - getPaddingRight());
  1.3823 +
  1.3824 +            int childCount = getChildCount();
  1.3825 +            int index = 0;
  1.3826 +            int delta = 0;
  1.3827 +
  1.3828 +            View focusLayoutRestoreView = null;
  1.3829 +
  1.3830 +            View selected = null;
  1.3831 +            View oldSelected = null;
  1.3832 +            View newSelected = null;
  1.3833 +            View oldFirstChild = null;
  1.3834 +
  1.3835 +            switch (mLayoutMode) {
  1.3836 +            case LAYOUT_SET_SELECTION:
  1.3837 +                index = mNextSelectedPosition - mFirstPosition;
  1.3838 +                if (index >= 0 && index < childCount) {
  1.3839 +                    newSelected = getChildAt(index);
  1.3840 +                }
  1.3841 +
  1.3842 +                break;
  1.3843 +
  1.3844 +            case LAYOUT_FORCE_TOP:
  1.3845 +            case LAYOUT_FORCE_BOTTOM:
  1.3846 +            case LAYOUT_SPECIFIC:
  1.3847 +            case LAYOUT_SYNC:
  1.3848 +                break;
  1.3849 +
  1.3850 +            case LAYOUT_MOVE_SELECTION:
  1.3851 +            default:
  1.3852 +                // Remember the previously selected view
  1.3853 +                index = mSelectedPosition - mFirstPosition;
  1.3854 +                if (index >= 0 && index < childCount) {
  1.3855 +                    oldSelected = getChildAt(index);
  1.3856 +                }
  1.3857 +
  1.3858 +                // Remember the previous first child
  1.3859 +                oldFirstChild = getChildAt(0);
  1.3860 +
  1.3861 +                if (mNextSelectedPosition >= 0) {
  1.3862 +                    delta = mNextSelectedPosition - mSelectedPosition;
  1.3863 +                }
  1.3864 +
  1.3865 +                // Caution: newSelected might be null
  1.3866 +                newSelected = getChildAt(index + delta);
  1.3867 +            }
  1.3868 +
  1.3869 +            final boolean dataChanged = mDataChanged;
  1.3870 +            if (dataChanged) {
  1.3871 +                handleDataChanged();
  1.3872 +            }
  1.3873 +
  1.3874 +            // Handle the empty set by removing all views that are visible
  1.3875 +            // and calling it a day
  1.3876 +            if (mItemCount == 0) {
  1.3877 +                resetState();
  1.3878 +                return;
  1.3879 +            } else if (mItemCount != mAdapter.getCount()) {
  1.3880 +                throw new IllegalStateException("The content of the adapter has changed but "
  1.3881 +                        + "TwoWayView did not receive a notification. Make sure the content of "
  1.3882 +                        + "your adapter is not modified from a background thread, but only "
  1.3883 +                        + "from the UI thread. [in TwoWayView(" + getId() + ", " + getClass()
  1.3884 +                        + ") with Adapter(" + mAdapter.getClass() + ")]");
  1.3885 +            }
  1.3886 +
  1.3887 +            setSelectedPositionInt(mNextSelectedPosition);
  1.3888 +
  1.3889 +            // Reset the focus restoration
  1.3890 +            View focusLayoutRestoreDirectChild = null;
  1.3891 +
  1.3892 +            // Pull all children into the RecycleBin.
  1.3893 +            // These views will be reused if possible
  1.3894 +            final int firstPosition = mFirstPosition;
  1.3895 +            final RecycleBin recycleBin = mRecycler;
  1.3896 +
  1.3897 +            if (dataChanged) {
  1.3898 +                for (int i = 0; i < childCount; i++) {
  1.3899 +                    recycleBin.addScrapView(getChildAt(i), firstPosition + i);
  1.3900 +                }
  1.3901 +            } else {
  1.3902 +                recycleBin.fillActiveViews(childCount, firstPosition);
  1.3903 +            }
  1.3904 +
  1.3905 +            // Take focus back to us temporarily to avoid the eventual
  1.3906 +            // call to clear focus when removing the focused child below
  1.3907 +            // from messing things up when ViewAncestor assigns focus back
  1.3908 +            // to someone else.
  1.3909 +            final View focusedChild = getFocusedChild();
  1.3910 +            if (focusedChild != null) {
  1.3911 +                // We can remember the focused view to restore after relayout if the
  1.3912 +                // data hasn't changed, or if the focused position is a header or footer.
  1.3913 +                if (!dataChanged) {
  1.3914 +                    focusLayoutRestoreDirectChild = focusedChild;
  1.3915 +
  1.3916 +                    // Remember the specific view that had focus
  1.3917 +                    focusLayoutRestoreView = findFocus();
  1.3918 +                    if (focusLayoutRestoreView != null) {
  1.3919 +                        // Tell it we are going to mess with it
  1.3920 +                        focusLayoutRestoreView.onStartTemporaryDetach();
  1.3921 +                    }
  1.3922 +                }
  1.3923 +
  1.3924 +                requestFocus();
  1.3925 +            }
  1.3926 +
  1.3927 +            // FIXME: We need a way to save current accessibility focus here
  1.3928 +            // so that it can be restored after we re-attach the children on each
  1.3929 +            // layout round.
  1.3930 +
  1.3931 +            detachAllViewsFromParent();
  1.3932 +
  1.3933 +            switch (mLayoutMode) {
  1.3934 +            case LAYOUT_SET_SELECTION:
  1.3935 +                if (newSelected != null) {
  1.3936 +                    final int newSelectedStart =
  1.3937 +                            (mIsVertical ? newSelected.getTop() : newSelected.getLeft());
  1.3938 +
  1.3939 +                    selected = fillFromSelection(newSelectedStart, start, end);
  1.3940 +                } else {
  1.3941 +                    selected = fillFromMiddle(start, end);
  1.3942 +                }
  1.3943 +
  1.3944 +                break;
  1.3945 +
  1.3946 +            case LAYOUT_SYNC:
  1.3947 +                selected = fillSpecific(mSyncPosition, mSpecificStart);
  1.3948 +                break;
  1.3949 +
  1.3950 +            case LAYOUT_FORCE_BOTTOM:
  1.3951 +                selected = fillBefore(mItemCount - 1, end);
  1.3952 +                adjustViewsStartOrEnd();
  1.3953 +                break;
  1.3954 +
  1.3955 +            case LAYOUT_FORCE_TOP:
  1.3956 +                mFirstPosition = 0;
  1.3957 +                selected = fillFromOffset(start);
  1.3958 +                adjustViewsStartOrEnd();
  1.3959 +                break;
  1.3960 +
  1.3961 +            case LAYOUT_SPECIFIC:
  1.3962 +                selected = fillSpecific(reconcileSelectedPosition(), mSpecificStart);
  1.3963 +                break;
  1.3964 +
  1.3965 +            case LAYOUT_MOVE_SELECTION:
  1.3966 +                selected = moveSelection(oldSelected, newSelected, delta, start, end);
  1.3967 +                break;
  1.3968 +
  1.3969 +            default:
  1.3970 +                if (childCount == 0) {
  1.3971 +                    final int position = lookForSelectablePosition(0);
  1.3972 +                    setSelectedPositionInt(position);
  1.3973 +                    selected = fillFromOffset(start);
  1.3974 +                } else {
  1.3975 +                    if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {
  1.3976 +                        int offset = start;
  1.3977 +                        if (oldSelected != null) {
  1.3978 +                            offset = (mIsVertical ? oldSelected.getTop() : oldSelected.getLeft());
  1.3979 +                        }
  1.3980 +                        selected = fillSpecific(mSelectedPosition, offset);
  1.3981 +                    } else if (mFirstPosition < mItemCount) {
  1.3982 +                        int offset = start;
  1.3983 +                        if (oldFirstChild != null) {
  1.3984 +                            offset = (mIsVertical ? oldFirstChild.getTop() : oldFirstChild.getLeft());
  1.3985 +                        }
  1.3986 +
  1.3987 +                        selected = fillSpecific(mFirstPosition, offset);
  1.3988 +                    } else {
  1.3989 +                        selected = fillSpecific(0, start);
  1.3990 +                    }
  1.3991 +                }
  1.3992 +
  1.3993 +                break;
  1.3994 +
  1.3995 +            }
  1.3996 +
  1.3997 +            recycleBin.scrapActiveViews();
  1.3998 +
  1.3999 +            if (selected != null) {
  1.4000 +                if (mItemsCanFocus && hasFocus() && !selected.hasFocus()) {
  1.4001 +                    final boolean focusWasTaken = (selected == focusLayoutRestoreDirectChild &&
  1.4002 +                            focusLayoutRestoreView != null &&
  1.4003 +                            focusLayoutRestoreView.requestFocus()) || selected.requestFocus();
  1.4004 +
  1.4005 +                    if (!focusWasTaken) {
  1.4006 +                        // Selected item didn't take focus, fine, but still want
  1.4007 +                        // to make sure something else outside of the selected view
  1.4008 +                        // has focus
  1.4009 +                        final View focused = getFocusedChild();
  1.4010 +                        if (focused != null) {
  1.4011 +                            focused.clearFocus();
  1.4012 +                        }
  1.4013 +
  1.4014 +                        positionSelector(INVALID_POSITION, selected);
  1.4015 +                    } else {
  1.4016 +                        selected.setSelected(false);
  1.4017 +                        mSelectorRect.setEmpty();
  1.4018 +                    }
  1.4019 +                } else {
  1.4020 +                    positionSelector(INVALID_POSITION, selected);
  1.4021 +                }
  1.4022 +
  1.4023 +                mSelectedStart = (mIsVertical ? selected.getTop() : selected.getLeft());
  1.4024 +            } else {
  1.4025 +                if (mTouchMode > TOUCH_MODE_DOWN && mTouchMode < TOUCH_MODE_DRAGGING) {
  1.4026 +                    View child = getChildAt(mMotionPosition - mFirstPosition);
  1.4027 +
  1.4028 +                    if (child != null) {
  1.4029 +                        positionSelector(mMotionPosition, child);
  1.4030 +                    }
  1.4031 +                } else {
  1.4032 +                    mSelectedStart = 0;
  1.4033 +                    mSelectorRect.setEmpty();
  1.4034 +                }
  1.4035 +
  1.4036 +                // Even if there is not selected position, we may need to restore
  1.4037 +                // focus (i.e. something focusable in touch mode)
  1.4038 +                if (hasFocus() && focusLayoutRestoreView != null) {
  1.4039 +                    focusLayoutRestoreView.requestFocus();
  1.4040 +                }
  1.4041 +            }
  1.4042 +
  1.4043 +            // Tell focus view we are done mucking with it, if it is still in
  1.4044 +            // our view hierarchy.
  1.4045 +            if (focusLayoutRestoreView != null
  1.4046 +                    && focusLayoutRestoreView.getWindowToken() != null) {
  1.4047 +                focusLayoutRestoreView.onFinishTemporaryDetach();
  1.4048 +            }
  1.4049 +
  1.4050 +            mLayoutMode = LAYOUT_NORMAL;
  1.4051 +            mDataChanged = false;
  1.4052 +            mNeedSync = false;
  1.4053 +
  1.4054 +            setNextSelectedPositionInt(mSelectedPosition);
  1.4055 +            if (mItemCount > 0) {
  1.4056 +                checkSelectionChanged();
  1.4057 +            }
  1.4058 +
  1.4059 +            invokeOnItemScrollListener();
  1.4060 +        } finally {
  1.4061 +            if (!blockLayoutRequests) {
  1.4062 +                mBlockLayoutRequests = false;
  1.4063 +                mDataChanged = false;
  1.4064 +            }
  1.4065 +        }
  1.4066 +    }
  1.4067 +
  1.4068 +    protected boolean recycleOnMeasure() {
  1.4069 +        return true;
  1.4070 +    }
  1.4071 +
  1.4072 +    private void offsetChildren(int offset) {
  1.4073 +        final int childCount = getChildCount();
  1.4074 +
  1.4075 +        for (int i = 0; i < childCount; i++) {
  1.4076 +            final View child = getChildAt(i);
  1.4077 +
  1.4078 +            if (mIsVertical) {
  1.4079 +                child.offsetTopAndBottom(offset);
  1.4080 +            } else {
  1.4081 +                child.offsetLeftAndRight(offset);
  1.4082 +            }
  1.4083 +        }
  1.4084 +    }
  1.4085 +
  1.4086 +    private View moveSelection(View oldSelected, View newSelected, int delta, int start,
  1.4087 +            int end) {
  1.4088 +        final int selectedPosition = mSelectedPosition;
  1.4089 +
  1.4090 +        final int oldSelectedStart = (mIsVertical ? oldSelected.getTop() : oldSelected.getLeft());
  1.4091 +        final int oldSelectedEnd = (mIsVertical ? oldSelected.getBottom() : oldSelected.getRight());
  1.4092 +
  1.4093 +        View selected = null;
  1.4094 +
  1.4095 +        if (delta > 0) {
  1.4096 +            /*
  1.4097 +             * Case 1: Scrolling down.
  1.4098 +             */
  1.4099 +
  1.4100 +            /*
  1.4101 +             *     Before           After
  1.4102 +             *    |       |        |       |
  1.4103 +             *    +-------+        +-------+
  1.4104 +             *    |   A   |        |   A   |
  1.4105 +             *    |   1   |   =>   +-------+
  1.4106 +             *    +-------+        |   B   |
  1.4107 +             *    |   B   |        |   2   |
  1.4108 +             *    +-------+        +-------+
  1.4109 +             *    |       |        |       |
  1.4110 +             *
  1.4111 +             *    Try to keep the top of the previously selected item where it was.
  1.4112 +             *    oldSelected = A
  1.4113 +             *    selected = B
  1.4114 +             */
  1.4115 +
  1.4116 +            // Put oldSelected (A) where it belongs
  1.4117 +            oldSelected = makeAndAddView(selectedPosition - 1, oldSelectedStart, true, false);
  1.4118 +
  1.4119 +            final int itemMargin = mItemMargin;
  1.4120 +
  1.4121 +            // Now put the new selection (B) below that
  1.4122 +            selected = makeAndAddView(selectedPosition, oldSelectedEnd + itemMargin, true, true);
  1.4123 +
  1.4124 +            final int selectedStart = (mIsVertical ? selected.getTop() : selected.getLeft());
  1.4125 +            final int selectedEnd = (mIsVertical ? selected.getBottom() : selected.getRight());
  1.4126 +
  1.4127 +            // Some of the newly selected item extends below the bottom of the list
  1.4128 +            if (selectedEnd > end) {
  1.4129 +                // Find space available above the selection into which we can scroll upwards
  1.4130 +                final int spaceBefore = selectedStart - start;
  1.4131 +
  1.4132 +                // Find space required to bring the bottom of the selected item fully into view
  1.4133 +                final int spaceAfter = selectedEnd - end;
  1.4134 +
  1.4135 +                // Don't scroll more than half the size of the list
  1.4136 +                final int halfSpace = (end - start) / 2;
  1.4137 +                int offset = Math.min(spaceBefore, spaceAfter);
  1.4138 +                offset = Math.min(offset, halfSpace);
  1.4139 +
  1.4140 +                if (mIsVertical) {
  1.4141 +                    oldSelected.offsetTopAndBottom(-offset);
  1.4142 +                    selected.offsetTopAndBottom(-offset);
  1.4143 +                } else {
  1.4144 +                    oldSelected.offsetLeftAndRight(-offset);
  1.4145 +                    selected.offsetLeftAndRight(-offset);
  1.4146 +                }
  1.4147 +            }
  1.4148 +
  1.4149 +            // Fill in views before and after
  1.4150 +            fillBefore(mSelectedPosition - 2, selectedStart - itemMargin);
  1.4151 +            adjustViewsStartOrEnd();
  1.4152 +            fillAfter(mSelectedPosition + 1, selectedEnd + itemMargin);
  1.4153 +        } else if (delta < 0) {
  1.4154 +            /*
  1.4155 +             * Case 2: Scrolling up.
  1.4156 +             */
  1.4157 +
  1.4158 +            /*
  1.4159 +             *     Before           After
  1.4160 +             *    |       |        |       |
  1.4161 +             *    +-------+        +-------+
  1.4162 +             *    |   A   |        |   A   |
  1.4163 +             *    +-------+   =>   |   1   |
  1.4164 +             *    |   B   |        +-------+
  1.4165 +             *    |   2   |        |   B   |
  1.4166 +             *    +-------+        +-------+
  1.4167 +             *    |       |        |       |
  1.4168 +             *
  1.4169 +             *    Try to keep the top of the item about to become selected where it was.
  1.4170 +             *    newSelected = A
  1.4171 +             *    olSelected = B
  1.4172 +             */
  1.4173 +
  1.4174 +            if (newSelected != null) {
  1.4175 +                // Try to position the top of newSel (A) where it was before it was selected
  1.4176 +                final int newSelectedStart = (mIsVertical ? newSelected.getTop() : newSelected.getLeft());
  1.4177 +                selected = makeAndAddView(selectedPosition, newSelectedStart, true, true);
  1.4178 +            } else {
  1.4179 +                // If (A) was not on screen and so did not have a view, position
  1.4180 +                // it above the oldSelected (B)
  1.4181 +                selected = makeAndAddView(selectedPosition, oldSelectedStart, false, true);
  1.4182 +            }
  1.4183 +
  1.4184 +            final int selectedStart = (mIsVertical ? selected.getTop() : selected.getLeft());
  1.4185 +            final int selectedEnd = (mIsVertical ? selected.getBottom() : selected.getRight());
  1.4186 +
  1.4187 +            // Some of the newly selected item extends above the top of the list
  1.4188 +            if (selectedStart < start) {
  1.4189 +                // Find space required to bring the top of the selected item fully into view
  1.4190 +                final int spaceBefore = start - selectedStart;
  1.4191 +
  1.4192 +               // Find space available below the selection into which we can scroll downwards
  1.4193 +                final int spaceAfter = end - selectedEnd;
  1.4194 +
  1.4195 +                // Don't scroll more than half the height of the list
  1.4196 +                final int halfSpace = (end - start) / 2;
  1.4197 +                int offset = Math.min(spaceBefore, spaceAfter);
  1.4198 +                offset = Math.min(offset, halfSpace);
  1.4199 +
  1.4200 +                if (mIsVertical) {
  1.4201 +                    selected.offsetTopAndBottom(offset);
  1.4202 +                } else {
  1.4203 +                    selected.offsetLeftAndRight(offset);
  1.4204 +                }
  1.4205 +            }
  1.4206 +
  1.4207 +            // Fill in views above and below
  1.4208 +            fillBeforeAndAfter(selected, selectedPosition);
  1.4209 +        } else {
  1.4210 +            /*
  1.4211 +             * Case 3: Staying still
  1.4212 +             */
  1.4213 +
  1.4214 +            selected = makeAndAddView(selectedPosition, oldSelectedStart, true, true);
  1.4215 +
  1.4216 +            final int selectedStart = (mIsVertical ? selected.getTop() : selected.getLeft());
  1.4217 +            final int selectedEnd = (mIsVertical ? selected.getBottom() : selected.getRight());
  1.4218 +
  1.4219 +            // We're staying still...
  1.4220 +            if (oldSelectedStart < start) {
  1.4221 +                // ... but the top of the old selection was off screen.
  1.4222 +                // (This can happen if the data changes size out from under us)
  1.4223 +                int newEnd = selectedEnd;
  1.4224 +                if (newEnd < start + 20) {
  1.4225 +                    // Not enough visible -- bring it onscreen
  1.4226 +                    if (mIsVertical) {
  1.4227 +                        selected.offsetTopAndBottom(start - selectedStart);
  1.4228 +                    } else {
  1.4229 +                        selected.offsetLeftAndRight(start - selectedStart);
  1.4230 +                    }
  1.4231 +                }
  1.4232 +            }
  1.4233 +
  1.4234 +            // Fill in views above and below
  1.4235 +            fillBeforeAndAfter(selected, selectedPosition);
  1.4236 +        }
  1.4237 +
  1.4238 +        return selected;
  1.4239 +    }
  1.4240 +
  1.4241 +    void confirmCheckedPositionsById() {
  1.4242 +        // Clear out the positional check states, we'll rebuild it below from IDs.
  1.4243 +        mCheckStates.clear();
  1.4244 +
  1.4245 +        for (int checkedIndex = 0; checkedIndex < mCheckedIdStates.size(); checkedIndex++) {
  1.4246 +            final long id = mCheckedIdStates.keyAt(checkedIndex);
  1.4247 +            final int lastPos = mCheckedIdStates.valueAt(checkedIndex);
  1.4248 +
  1.4249 +            final long lastPosId = mAdapter.getItemId(lastPos);
  1.4250 +            if (id != lastPosId) {
  1.4251 +                // Look around to see if the ID is nearby. If not, uncheck it.
  1.4252 +                final int start = Math.max(0, lastPos - CHECK_POSITION_SEARCH_DISTANCE);
  1.4253 +                final int end = Math.min(lastPos + CHECK_POSITION_SEARCH_DISTANCE, mItemCount);
  1.4254 +                boolean found = false;
  1.4255 +
  1.4256 +                for (int searchPos = start; searchPos < end; searchPos++) {
  1.4257 +                    final long searchId = mAdapter.getItemId(searchPos);
  1.4258 +                    if (id == searchId) {
  1.4259 +                        found = true;
  1.4260 +                        mCheckStates.put(searchPos, true);
  1.4261 +                        mCheckedIdStates.setValueAt(checkedIndex, searchPos);
  1.4262 +                        break;
  1.4263 +                    }
  1.4264 +                }
  1.4265 +
  1.4266 +                if (!found) {
  1.4267 +                    mCheckedIdStates.delete(id);
  1.4268 +                    checkedIndex--;
  1.4269 +                    mCheckedItemCount--;
  1.4270 +                }
  1.4271 +            } else {
  1.4272 +                mCheckStates.put(lastPos, true);
  1.4273 +            }
  1.4274 +        }
  1.4275 +    }
  1.4276 +
  1.4277 +    private void handleDataChanged() {
  1.4278 +        if (mChoiceMode.compareTo(ChoiceMode.NONE) != 0 && mAdapter != null && mAdapter.hasStableIds()) {
  1.4279 +            confirmCheckedPositionsById();
  1.4280 +        }
  1.4281 +
  1.4282 +        mRecycler.clearTransientStateViews();
  1.4283 +
  1.4284 +        final int itemCount = mItemCount;
  1.4285 +        if (itemCount > 0) {
  1.4286 +            int newPos;
  1.4287 +            int selectablePos;
  1.4288 +
  1.4289 +            // Find the row we are supposed to sync to
  1.4290 +            if (mNeedSync) {
  1.4291 +                // Update this first, since setNextSelectedPositionInt inspects it
  1.4292 +                mNeedSync = false;
  1.4293 +                mPendingSync = null;
  1.4294 +
  1.4295 +                switch (mSyncMode) {
  1.4296 +                case SYNC_SELECTED_POSITION:
  1.4297 +                    if (isInTouchMode()) {
  1.4298 +                        // We saved our state when not in touch mode. (We know this because
  1.4299 +                        // mSyncMode is SYNC_SELECTED_POSITION.) Now we are trying to
  1.4300 +                        // restore in touch mode. Just leave mSyncPosition as it is (possibly
  1.4301 +                        // adjusting if the available range changed) and return.
  1.4302 +                        mLayoutMode = LAYOUT_SYNC;
  1.4303 +                        mSyncPosition = Math.min(Math.max(0, mSyncPosition), itemCount - 1);
  1.4304 +
  1.4305 +                        return;
  1.4306 +                    } else {
  1.4307 +                        // See if we can find a position in the new data with the same
  1.4308 +                        // id as the old selection. This will change mSyncPosition.
  1.4309 +                        newPos = findSyncPosition();
  1.4310 +                        if (newPos >= 0) {
  1.4311 +                            // Found it. Now verify that new selection is still selectable
  1.4312 +                            selectablePos = lookForSelectablePosition(newPos, true);
  1.4313 +                            if (selectablePos == newPos) {
  1.4314 +                                // Same row id is selected
  1.4315 +                                mSyncPosition = newPos;
  1.4316 +
  1.4317 +                                if (mSyncHeight == getHeight()) {
  1.4318 +                                    // If we are at the same height as when we saved state, try
  1.4319 +                                    // to restore the scroll position too.
  1.4320 +                                    mLayoutMode = LAYOUT_SYNC;
  1.4321 +                                } else {
  1.4322 +                                    // We are not the same height as when the selection was saved, so
  1.4323 +                                    // don't try to restore the exact position
  1.4324 +                                    mLayoutMode = LAYOUT_SET_SELECTION;
  1.4325 +                                }
  1.4326 +
  1.4327 +                                // Restore selection
  1.4328 +                                setNextSelectedPositionInt(newPos);
  1.4329 +                                return;
  1.4330 +                            }
  1.4331 +                        }
  1.4332 +                    }
  1.4333 +                    break;
  1.4334 +
  1.4335 +                case SYNC_FIRST_POSITION:
  1.4336 +                    // Leave mSyncPosition as it is -- just pin to available range
  1.4337 +                    mLayoutMode = LAYOUT_SYNC;
  1.4338 +                    mSyncPosition = Math.min(Math.max(0, mSyncPosition), itemCount - 1);
  1.4339 +
  1.4340 +                    return;
  1.4341 +                }
  1.4342 +            }
  1.4343 +
  1.4344 +            if (!isInTouchMode()) {
  1.4345 +                // We couldn't find matching data -- try to use the same position
  1.4346 +                newPos = getSelectedItemPosition();
  1.4347 +
  1.4348 +                // Pin position to the available range
  1.4349 +                if (newPos >= itemCount) {
  1.4350 +                    newPos = itemCount - 1;
  1.4351 +                }
  1.4352 +                if (newPos < 0) {
  1.4353 +                    newPos = 0;
  1.4354 +                }
  1.4355 +
  1.4356 +                // Make sure we select something selectable -- first look down
  1.4357 +                selectablePos = lookForSelectablePosition(newPos, true);
  1.4358 +
  1.4359 +                if (selectablePos >= 0) {
  1.4360 +                    setNextSelectedPositionInt(selectablePos);
  1.4361 +                    return;
  1.4362 +                } else {
  1.4363 +                    // Looking down didn't work -- try looking up
  1.4364 +                    selectablePos = lookForSelectablePosition(newPos, false);
  1.4365 +                    if (selectablePos >= 0) {
  1.4366 +                        setNextSelectedPositionInt(selectablePos);
  1.4367 +                        return;
  1.4368 +                    }
  1.4369 +                }
  1.4370 +            } else {
  1.4371 +                // We already know where we want to resurrect the selection
  1.4372 +                if (mResurrectToPosition >= 0) {
  1.4373 +                    return;
  1.4374 +                }
  1.4375 +            }
  1.4376 +        }
  1.4377 +
  1.4378 +        // Nothing is selected. Give up and reset everything.
  1.4379 +        mLayoutMode = LAYOUT_FORCE_TOP;
  1.4380 +        mSelectedPosition = INVALID_POSITION;
  1.4381 +        mSelectedRowId = INVALID_ROW_ID;
  1.4382 +        mNextSelectedPosition = INVALID_POSITION;
  1.4383 +        mNextSelectedRowId = INVALID_ROW_ID;
  1.4384 +        mNeedSync = false;
  1.4385 +        mPendingSync = null;
  1.4386 +        mSelectorPosition = INVALID_POSITION;
  1.4387 +
  1.4388 +        checkSelectionChanged();
  1.4389 +    }
  1.4390 +
  1.4391 +    private int reconcileSelectedPosition() {
  1.4392 +        int position = mSelectedPosition;
  1.4393 +        if (position < 0) {
  1.4394 +            position = mResurrectToPosition;
  1.4395 +        }
  1.4396 +
  1.4397 +        position = Math.max(0, position);
  1.4398 +        position = Math.min(position, mItemCount - 1);
  1.4399 +
  1.4400 +        return position;
  1.4401 +    }
  1.4402 +
  1.4403 +    boolean resurrectSelection() {
  1.4404 +        final int childCount = getChildCount();
  1.4405 +        if (childCount <= 0) {
  1.4406 +            return false;
  1.4407 +        }
  1.4408 +
  1.4409 +        int selectedStart = 0;
  1.4410 +        int selectedPosition;
  1.4411 +
  1.4412 +        final int start = (mIsVertical ? getPaddingTop() : getPaddingLeft());
  1.4413 +        final int end =
  1.4414 +                (mIsVertical ? getHeight() - getPaddingBottom() : getWidth() - getPaddingRight());
  1.4415 +
  1.4416 +        final int firstPosition = mFirstPosition;
  1.4417 +        final int toPosition = mResurrectToPosition;
  1.4418 +        boolean down = true;
  1.4419 +
  1.4420 +        if (toPosition >= firstPosition && toPosition < firstPosition + childCount) {
  1.4421 +            selectedPosition = toPosition;
  1.4422 +
  1.4423 +            final View selected = getChildAt(selectedPosition - mFirstPosition);
  1.4424 +            selectedStart = (mIsVertical ? selected.getTop() : selected.getLeft());
  1.4425 +        } else if (toPosition < firstPosition) {
  1.4426 +            // Default to selecting whatever is first
  1.4427 +            selectedPosition = firstPosition;
  1.4428 +
  1.4429 +            for (int i = 0; i < childCount; i++) {
  1.4430 +                final View child = getChildAt(i);
  1.4431 +                final int childStart = (mIsVertical ? child.getTop() : child.getLeft());
  1.4432 +
  1.4433 +                if (i == 0) {
  1.4434 +                    // Remember the position of the first item
  1.4435 +                    selectedStart = childStart;
  1.4436 +                }
  1.4437 +
  1.4438 +                if (childStart >= start) {
  1.4439 +                    // Found a view whose top is fully visible
  1.4440 +                    selectedPosition = firstPosition + i;
  1.4441 +                    selectedStart = childStart;
  1.4442 +                    break;
  1.4443 +                }
  1.4444 +            }
  1.4445 +        } else {
  1.4446 +            selectedPosition = firstPosition + childCount - 1;
  1.4447 +            down = false;
  1.4448 +
  1.4449 +            for (int i = childCount - 1; i >= 0; i--) {
  1.4450 +                final View child = getChildAt(i);
  1.4451 +                final int childStart = (mIsVertical ? child.getTop() : child.getLeft());
  1.4452 +                final int childEnd = (mIsVertical ? child.getBottom() : child.getRight());
  1.4453 +
  1.4454 +                if (i == childCount - 1) {
  1.4455 +                    selectedStart = childStart;
  1.4456 +                }
  1.4457 +
  1.4458 +                if (childEnd <= end) {
  1.4459 +                    selectedPosition = firstPosition + i;
  1.4460 +                    selectedStart = childStart;
  1.4461 +                    break;
  1.4462 +                }
  1.4463 +            }
  1.4464 +        }
  1.4465 +
  1.4466 +        mResurrectToPosition = INVALID_POSITION;
  1.4467 +        mTouchMode = TOUCH_MODE_REST;
  1.4468 +        reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
  1.4469 +
  1.4470 +        mSpecificStart = selectedStart;
  1.4471 +
  1.4472 +        selectedPosition = lookForSelectablePosition(selectedPosition, down);
  1.4473 +        if (selectedPosition >= firstPosition && selectedPosition <= getLastVisiblePosition()) {
  1.4474 +            mLayoutMode = LAYOUT_SPECIFIC;
  1.4475 +            updateSelectorState();
  1.4476 +            setSelectionInt(selectedPosition);
  1.4477 +            invokeOnItemScrollListener();
  1.4478 +        } else {
  1.4479 +            selectedPosition = INVALID_POSITION;
  1.4480 +        }
  1.4481 +
  1.4482 +        return selectedPosition >= 0;
  1.4483 +    }
  1.4484 +
  1.4485 +    /**
  1.4486 +     * If there is a selection returns false.
  1.4487 +     * Otherwise resurrects the selection and returns true if resurrected.
  1.4488 +     */
  1.4489 +    boolean resurrectSelectionIfNeeded() {
  1.4490 +        if (mSelectedPosition < 0 && resurrectSelection()) {
  1.4491 +            updateSelectorState();
  1.4492 +            return true;
  1.4493 +        }
  1.4494 +
  1.4495 +        return false;
  1.4496 +    }
  1.4497 +
  1.4498 +    private int getChildWidthMeasureSpec(LayoutParams lp) {
  1.4499 +        if (!mIsVertical && lp.width == LayoutParams.WRAP_CONTENT) {
  1.4500 +            return MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
  1.4501 +        } else if (mIsVertical) {
  1.4502 +            final int maxWidth = getWidth() - getPaddingLeft() - getPaddingRight();
  1.4503 +            return MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.EXACTLY);
  1.4504 +        } else {
  1.4505 +            return MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
  1.4506 +        }
  1.4507 +    }
  1.4508 +
  1.4509 +    private int getChildHeightMeasureSpec(LayoutParams lp) {
  1.4510 +        if (mIsVertical && lp.height == LayoutParams.WRAP_CONTENT) {
  1.4511 +            return MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
  1.4512 +        } else if (!mIsVertical) {
  1.4513 +            final int maxHeight = getHeight() - getPaddingTop() - getPaddingBottom();
  1.4514 +            return MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.EXACTLY);
  1.4515 +        } else {
  1.4516 +            return MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
  1.4517 +        }
  1.4518 +    }
  1.4519 +
  1.4520 +    private void measureChild(View child) {
  1.4521 +        measureChild(child, (LayoutParams) child.getLayoutParams());
  1.4522 +    }
  1.4523 +
  1.4524 +    private void measureChild(View child, LayoutParams lp) {
  1.4525 +        final int widthSpec = getChildWidthMeasureSpec(lp);
  1.4526 +        final int heightSpec = getChildHeightMeasureSpec(lp);
  1.4527 +        child.measure(widthSpec, heightSpec);
  1.4528 +    }
  1.4529 +
  1.4530 +    private void relayoutMeasuredChild(View child) {
  1.4531 +        final int w = child.getMeasuredWidth();
  1.4532 +        final int h = child.getMeasuredHeight();
  1.4533 +
  1.4534 +        final int childLeft = getPaddingLeft();
  1.4535 +        final int childRight = childLeft + w;
  1.4536 +        final int childTop = child.getTop();
  1.4537 +        final int childBottom = childTop + h;
  1.4538 +
  1.4539 +        child.layout(childLeft, childTop, childRight, childBottom);
  1.4540 +    }
  1.4541 +
  1.4542 +    private void measureScrapChild(View scrapChild, int position, int secondaryMeasureSpec) {
  1.4543 +        LayoutParams lp = (LayoutParams) scrapChild.getLayoutParams();
  1.4544 +        if (lp == null) {
  1.4545 +            lp = generateDefaultLayoutParams();
  1.4546 +            scrapChild.setLayoutParams(lp);
  1.4547 +        }
  1.4548 +
  1.4549 +        lp.viewType = mAdapter.getItemViewType(position);
  1.4550 +        lp.forceAdd = true;
  1.4551 +
  1.4552 +        final int widthMeasureSpec;
  1.4553 +        final int heightMeasureSpec;
  1.4554 +        if (mIsVertical) {
  1.4555 +            widthMeasureSpec = secondaryMeasureSpec;
  1.4556 +            heightMeasureSpec = getChildHeightMeasureSpec(lp);
  1.4557 +        } else {
  1.4558 +            widthMeasureSpec = getChildWidthMeasureSpec(lp);
  1.4559 +            heightMeasureSpec = secondaryMeasureSpec;
  1.4560 +        }
  1.4561 +
  1.4562 +        scrapChild.measure(widthMeasureSpec, heightMeasureSpec);
  1.4563 +    }
  1.4564 +
  1.4565 +    /**
  1.4566 +     * Measures the height of the given range of children (inclusive) and
  1.4567 +     * returns the height with this TwoWayView's padding and item margin heights
  1.4568 +     * included. If maxHeight is provided, the measuring will stop when the
  1.4569 +     * current height reaches maxHeight.
  1.4570 +     *
  1.4571 +     * @param widthMeasureSpec The width measure spec to be given to a child's
  1.4572 +     *            {@link View#measure(int, int)}.
  1.4573 +     * @param startPosition The position of the first child to be shown.
  1.4574 +     * @param endPosition The (inclusive) position of the last child to be
  1.4575 +     *            shown. Specify {@link #NO_POSITION} if the last child should be
  1.4576 +     *            the last available child from the adapter.
  1.4577 +     * @param maxHeight The maximum height that will be returned (if all the
  1.4578 +     *            children don't fit in this value, this value will be
  1.4579 +     *            returned).
  1.4580 +     * @param disallowPartialChildPosition In general, whether the returned
  1.4581 +     *            height should only contain entire children. This is more
  1.4582 +     *            powerful--it is the first inclusive position at which partial
  1.4583 +     *            children will not be allowed. Example: it looks nice to have
  1.4584 +     *            at least 3 completely visible children, and in portrait this
  1.4585 +     *            will most likely fit; but in landscape there could be times
  1.4586 +     *            when even 2 children can not be completely shown, so a value
  1.4587 +     *            of 2 (remember, inclusive) would be good (assuming
  1.4588 +     *            startPosition is 0).
  1.4589 +     * @return The height of this TwoWayView with the given children.
  1.4590 +     */
  1.4591 +    private int measureHeightOfChildren(int widthMeasureSpec, int startPosition, int endPosition,
  1.4592 +            final int maxHeight, int disallowPartialChildPosition) {
  1.4593 +
  1.4594 +        final int paddingTop = getPaddingTop();
  1.4595 +        final int paddingBottom = getPaddingBottom();
  1.4596 +
  1.4597 +        final ListAdapter adapter = mAdapter;
  1.4598 +        if (adapter == null) {
  1.4599 +            return paddingTop + paddingBottom;
  1.4600 +        }
  1.4601 +
  1.4602 +        // Include the padding of the list
  1.4603 +        int returnedHeight = paddingTop + paddingBottom;
  1.4604 +        final int itemMargin = mItemMargin;
  1.4605 +
  1.4606 +        // The previous height value that was less than maxHeight and contained
  1.4607 +        // no partial children
  1.4608 +        int prevHeightWithoutPartialChild = 0;
  1.4609 +        int i;
  1.4610 +        View child;
  1.4611 +
  1.4612 +        // mItemCount - 1 since endPosition parameter is inclusive
  1.4613 +        endPosition = (endPosition == NO_POSITION) ? adapter.getCount() - 1 : endPosition;
  1.4614 +        final RecycleBin recycleBin = mRecycler;
  1.4615 +        final boolean shouldRecycle = recycleOnMeasure();
  1.4616 +        final boolean[] isScrap = mIsScrap;
  1.4617 +
  1.4618 +        for (i = startPosition; i <= endPosition; ++i) {
  1.4619 +            child = obtainView(i, isScrap);
  1.4620 +
  1.4621 +            measureScrapChild(child, i, widthMeasureSpec);
  1.4622 +
  1.4623 +            if (i > 0) {
  1.4624 +                // Count the item margin for all but one child
  1.4625 +                returnedHeight += itemMargin;
  1.4626 +            }
  1.4627 +
  1.4628 +            // Recycle the view before we possibly return from the method
  1.4629 +            if (shouldRecycle) {
  1.4630 +                recycleBin.addScrapView(child, -1);
  1.4631 +            }
  1.4632 +
  1.4633 +            returnedHeight += child.getMeasuredHeight();
  1.4634 +
  1.4635 +            if (returnedHeight >= maxHeight) {
  1.4636 +                // We went over, figure out which height to return.  If returnedHeight > maxHeight,
  1.4637 +                // then the i'th position did not fit completely.
  1.4638 +                return (disallowPartialChildPosition >= 0) // Disallowing is enabled (> -1)
  1.4639 +                            && (i > disallowPartialChildPosition) // We've past the min pos
  1.4640 +                            && (prevHeightWithoutPartialChild > 0) // We have a prev height
  1.4641 +                            && (returnedHeight != maxHeight) // i'th child did not fit completely
  1.4642 +                        ? prevHeightWithoutPartialChild
  1.4643 +                        : maxHeight;
  1.4644 +            }
  1.4645 +
  1.4646 +            if ((disallowPartialChildPosition >= 0) && (i >= disallowPartialChildPosition)) {
  1.4647 +                prevHeightWithoutPartialChild = returnedHeight;
  1.4648 +            }
  1.4649 +        }
  1.4650 +
  1.4651 +        // At this point, we went through the range of children, and they each
  1.4652 +        // completely fit, so return the returnedHeight
  1.4653 +        return returnedHeight;
  1.4654 +    }
  1.4655 +
  1.4656 +    /**
  1.4657 +     * Measures the width of the given range of children (inclusive) and
  1.4658 +     * returns the width with this TwoWayView's padding and item margin widths
  1.4659 +     * included. If maxWidth is provided, the measuring will stop when the
  1.4660 +     * current width reaches maxWidth.
  1.4661 +     *
  1.4662 +     * @param heightMeasureSpec The height measure spec to be given to a child's
  1.4663 +     *            {@link View#measure(int, int)}.
  1.4664 +     * @param startPosition The position of the first child to be shown.
  1.4665 +     * @param endPosition The (inclusive) position of the last child to be
  1.4666 +     *            shown. Specify {@link #NO_POSITION} if the last child should be
  1.4667 +     *            the last available child from the adapter.
  1.4668 +     * @param maxWidth The maximum width that will be returned (if all the
  1.4669 +     *            children don't fit in this value, this value will be
  1.4670 +     *            returned).
  1.4671 +     * @param disallowPartialChildPosition In general, whether the returned
  1.4672 +     *            width should only contain entire children. This is more
  1.4673 +     *            powerful--it is the first inclusive position at which partial
  1.4674 +     *            children will not be allowed. Example: it looks nice to have
  1.4675 +     *            at least 3 completely visible children, and in portrait this
  1.4676 +     *            will most likely fit; but in landscape there could be times
  1.4677 +     *            when even 2 children can not be completely shown, so a value
  1.4678 +     *            of 2 (remember, inclusive) would be good (assuming
  1.4679 +     *            startPosition is 0).
  1.4680 +     * @return The width of this TwoWayView with the given children.
  1.4681 +     */
  1.4682 +    private int measureWidthOfChildren(int heightMeasureSpec, int startPosition, int endPosition,
  1.4683 +            final int maxWidth, int disallowPartialChildPosition) {
  1.4684 +
  1.4685 +        final int paddingLeft = getPaddingLeft();
  1.4686 +        final int paddingRight = getPaddingRight();
  1.4687 +
  1.4688 +        final ListAdapter adapter = mAdapter;
  1.4689 +        if (adapter == null) {
  1.4690 +            return paddingLeft + paddingRight;
  1.4691 +        }
  1.4692 +
  1.4693 +        // Include the padding of the list
  1.4694 +        int returnedWidth = paddingLeft + paddingRight;
  1.4695 +        final int itemMargin = mItemMargin;
  1.4696 +
  1.4697 +        // The previous height value that was less than maxHeight and contained
  1.4698 +        // no partial children
  1.4699 +        int prevWidthWithoutPartialChild = 0;
  1.4700 +        int i;
  1.4701 +        View child;
  1.4702 +
  1.4703 +        // mItemCount - 1 since endPosition parameter is inclusive
  1.4704 +        endPosition = (endPosition == NO_POSITION) ? adapter.getCount() - 1 : endPosition;
  1.4705 +        final RecycleBin recycleBin = mRecycler;
  1.4706 +        final boolean shouldRecycle = recycleOnMeasure();
  1.4707 +        final boolean[] isScrap = mIsScrap;
  1.4708 +
  1.4709 +        for (i = startPosition; i <= endPosition; ++i) {
  1.4710 +            child = obtainView(i, isScrap);
  1.4711 +
  1.4712 +            measureScrapChild(child, i, heightMeasureSpec);
  1.4713 +
  1.4714 +            if (i > 0) {
  1.4715 +                // Count the item margin for all but one child
  1.4716 +                returnedWidth += itemMargin;
  1.4717 +            }
  1.4718 +
  1.4719 +            // Recycle the view before we possibly return from the method
  1.4720 +            if (shouldRecycle) {
  1.4721 +                recycleBin.addScrapView(child, -1);
  1.4722 +            }
  1.4723 +
  1.4724 +            returnedWidth += child.getMeasuredHeight();
  1.4725 +
  1.4726 +            if (returnedWidth >= maxWidth) {
  1.4727 +                // We went over, figure out which width to return.  If returnedWidth > maxWidth,
  1.4728 +                // then the i'th position did not fit completely.
  1.4729 +                return (disallowPartialChildPosition >= 0) // Disallowing is enabled (> -1)
  1.4730 +                            && (i > disallowPartialChildPosition) // We've past the min pos
  1.4731 +                            && (prevWidthWithoutPartialChild > 0) // We have a prev width
  1.4732 +                            && (returnedWidth != maxWidth) // i'th child did not fit completely
  1.4733 +                        ? prevWidthWithoutPartialChild
  1.4734 +                        : maxWidth;
  1.4735 +            }
  1.4736 +
  1.4737 +            if ((disallowPartialChildPosition >= 0) && (i >= disallowPartialChildPosition)) {
  1.4738 +                prevWidthWithoutPartialChild = returnedWidth;
  1.4739 +            }
  1.4740 +        }
  1.4741 +
  1.4742 +        // At this point, we went through the range of children, and they each
  1.4743 +        // completely fit, so return the returnedWidth
  1.4744 +        return returnedWidth;
  1.4745 +    }
  1.4746 +
  1.4747 +    private View makeAndAddView(int position, int offset, boolean flow, boolean selected) {
  1.4748 +        final int top;
  1.4749 +        final int left;
  1.4750 +
  1.4751 +        if (mIsVertical) {
  1.4752 +            top = offset;
  1.4753 +            left = getPaddingLeft();
  1.4754 +        } else {
  1.4755 +            top = getPaddingTop();
  1.4756 +            left = offset;
  1.4757 +        }
  1.4758 +
  1.4759 +        if (!mDataChanged) {
  1.4760 +            // Try to use an existing view for this position
  1.4761 +            final View activeChild = mRecycler.getActiveView(position);
  1.4762 +            if (activeChild != null) {
  1.4763 +                // Found it -- we're using an existing child
  1.4764 +                // This just needs to be positioned
  1.4765 +                setupChild(activeChild, position, top, left, flow, selected, true);
  1.4766 +
  1.4767 +                return activeChild;
  1.4768 +            }
  1.4769 +        }
  1.4770 +
  1.4771 +        // Make a new view for this position, or convert an unused view if possible
  1.4772 +        final View child = obtainView(position, mIsScrap);
  1.4773 +
  1.4774 +        // This needs to be positioned and measured
  1.4775 +        setupChild(child, position, top, left, flow, selected, mIsScrap[0]);
  1.4776 +
  1.4777 +        return child;
  1.4778 +    }
  1.4779 +
  1.4780 +    @TargetApi(11)
  1.4781 +    private void setupChild(View child, int position, int top, int left,
  1.4782 +            boolean flow, boolean selected, boolean recycled) {
  1.4783 +        final boolean isSelected = selected && shouldShowSelector();
  1.4784 +        final boolean updateChildSelected = isSelected != child.isSelected();
  1.4785 +        final int touchMode = mTouchMode;
  1.4786 +
  1.4787 +        final boolean isPressed = touchMode > TOUCH_MODE_DOWN && touchMode < TOUCH_MODE_DRAGGING &&
  1.4788 +                mMotionPosition == position;
  1.4789 +
  1.4790 +        final boolean updateChildPressed = isPressed != child.isPressed();
  1.4791 +        final boolean needToMeasure = !recycled || updateChildSelected || child.isLayoutRequested();
  1.4792 +
  1.4793 +        // Respect layout params that are already in the view. Otherwise make some up...
  1.4794 +        LayoutParams lp = (LayoutParams) child.getLayoutParams();
  1.4795 +        if (lp == null) {
  1.4796 +            lp = generateDefaultLayoutParams();
  1.4797 +        }
  1.4798 +
  1.4799 +        lp.viewType = mAdapter.getItemViewType(position);
  1.4800 +
  1.4801 +        if (recycled && !lp.forceAdd) {
  1.4802 +            attachViewToParent(child, (flow ? -1 : 0), lp);
  1.4803 +        } else {
  1.4804 +            lp.forceAdd = false;
  1.4805 +            addViewInLayout(child, (flow ? -1 : 0), lp, true);
  1.4806 +        }
  1.4807 +
  1.4808 +        if (updateChildSelected) {
  1.4809 +            child.setSelected(isSelected);
  1.4810 +        }
  1.4811 +
  1.4812 +        if (updateChildPressed) {
  1.4813 +            child.setPressed(isPressed);
  1.4814 +        }
  1.4815 +
  1.4816 +        if (mChoiceMode.compareTo(ChoiceMode.NONE) != 0 && mCheckStates != null) {
  1.4817 +            if (child instanceof Checkable) {
  1.4818 +                ((Checkable) child).setChecked(mCheckStates.get(position));
  1.4819 +            } else if (getContext().getApplicationInfo().targetSdkVersion
  1.4820 +                    >= Build.VERSION_CODES.HONEYCOMB) {
  1.4821 +                child.setActivated(mCheckStates.get(position));
  1.4822 +            }
  1.4823 +        }
  1.4824 +
  1.4825 +        if (needToMeasure) {
  1.4826 +            measureChild(child, lp);
  1.4827 +        } else {
  1.4828 +            cleanupLayoutState(child);
  1.4829 +        }
  1.4830 +
  1.4831 +        final int w = child.getMeasuredWidth();
  1.4832 +        final int h = child.getMeasuredHeight();
  1.4833 +
  1.4834 +        final int childTop = (mIsVertical && !flow ? top - h : top);
  1.4835 +        final int childLeft = (!mIsVertical && !flow ? left - w : left);
  1.4836 +
  1.4837 +        if (needToMeasure) {
  1.4838 +            final int childRight = childLeft + w;
  1.4839 +            final int childBottom = childTop + h;
  1.4840 +
  1.4841 +            child.layout(childLeft, childTop, childRight, childBottom);
  1.4842 +        } else {
  1.4843 +            child.offsetLeftAndRight(childLeft - child.getLeft());
  1.4844 +            child.offsetTopAndBottom(childTop - child.getTop());
  1.4845 +        }
  1.4846 +    }
  1.4847 +
  1.4848 +    void fillGap(boolean down) {
  1.4849 +        final int childCount = getChildCount();
  1.4850 +
  1.4851 +        if (down) {
  1.4852 +            final int paddingStart = (mIsVertical ? getPaddingTop() : getPaddingLeft());
  1.4853 +
  1.4854 +            final int lastEnd;
  1.4855 +            if (mIsVertical) {
  1.4856 +                lastEnd = getChildAt(childCount - 1).getBottom();
  1.4857 +            } else {
  1.4858 +                lastEnd = getChildAt(childCount - 1).getRight();
  1.4859 +            }
  1.4860 +
  1.4861 +            final int offset = (childCount > 0 ? lastEnd + mItemMargin : paddingStart);
  1.4862 +            fillAfter(mFirstPosition + childCount, offset);
  1.4863 +            correctTooHigh(getChildCount());
  1.4864 +        } else {
  1.4865 +            final int end;
  1.4866 +            final int firstStart;
  1.4867 +
  1.4868 +            if (mIsVertical) {
  1.4869 +                end = getHeight() - getPaddingBottom();
  1.4870 +                firstStart = getChildAt(0).getTop();
  1.4871 +            } else {
  1.4872 +                end = getWidth() - getPaddingRight();
  1.4873 +                firstStart = getChildAt(0).getLeft();
  1.4874 +            }
  1.4875 +
  1.4876 +            final int offset = (childCount > 0 ? firstStart - mItemMargin : end);
  1.4877 +            fillBefore(mFirstPosition - 1, offset);
  1.4878 +            correctTooLow(getChildCount());
  1.4879 +        }
  1.4880 +    }
  1.4881 +
  1.4882 +    private View fillBefore(int pos, int nextOffset) {
  1.4883 +        View selectedView = null;
  1.4884 +
  1.4885 +        final int start = (mIsVertical ? getPaddingTop() : getPaddingLeft());
  1.4886 +
  1.4887 +        while (nextOffset > start && pos >= 0) {
  1.4888 +            boolean isSelected = (pos == mSelectedPosition);
  1.4889 +            View child = makeAndAddView(pos, nextOffset, false, isSelected);
  1.4890 +
  1.4891 +            if (mIsVertical) {
  1.4892 +                nextOffset = child.getTop() - mItemMargin;
  1.4893 +            } else {
  1.4894 +                nextOffset = child.getLeft() - mItemMargin;
  1.4895 +            }
  1.4896 +
  1.4897 +            if (isSelected) {
  1.4898 +                selectedView = child;
  1.4899 +            }
  1.4900 +
  1.4901 +            pos--;
  1.4902 +        }
  1.4903 +
  1.4904 +        mFirstPosition = pos + 1;
  1.4905 +
  1.4906 +        return selectedView;
  1.4907 +    }
  1.4908 +
  1.4909 +    private View fillAfter(int pos, int nextOffset) {
  1.4910 +        View selectedView = null;
  1.4911 +
  1.4912 +        final int end =
  1.4913 +                (mIsVertical ? getHeight() - getPaddingBottom() : getWidth() - getPaddingRight());
  1.4914 +
  1.4915 +        while (nextOffset < end && pos < mItemCount) {
  1.4916 +            boolean selected = (pos == mSelectedPosition);
  1.4917 +
  1.4918 +            View child = makeAndAddView(pos, nextOffset, true, selected);
  1.4919 +
  1.4920 +            if (mIsVertical) {
  1.4921 +                nextOffset = child.getBottom() + mItemMargin;
  1.4922 +            } else {
  1.4923 +                nextOffset = child.getRight() + mItemMargin;
  1.4924 +            }
  1.4925 +
  1.4926 +            if (selected) {
  1.4927 +                selectedView = child;
  1.4928 +            }
  1.4929 +
  1.4930 +            pos++;
  1.4931 +        }
  1.4932 +
  1.4933 +        return selectedView;
  1.4934 +    }
  1.4935 +
  1.4936 +    private View fillSpecific(int position, int offset) {
  1.4937 +        final boolean tempIsSelected = (position == mSelectedPosition);
  1.4938 +        View temp = makeAndAddView(position, offset, true, tempIsSelected);
  1.4939 +
  1.4940 +        // Possibly changed again in fillBefore if we add rows above this one.
  1.4941 +        mFirstPosition = position;
  1.4942 +
  1.4943 +        final int itemMargin = mItemMargin;
  1.4944 +
  1.4945 +        final int offsetBefore;
  1.4946 +        if (mIsVertical) {
  1.4947 +            offsetBefore = temp.getTop() - itemMargin;
  1.4948 +        } else {
  1.4949 +            offsetBefore = temp.getLeft() - itemMargin;
  1.4950 +        }
  1.4951 +        final View before = fillBefore(position - 1, offsetBefore);
  1.4952 +
  1.4953 +        // This will correct for the top of the first view not touching the top of the list
  1.4954 +        adjustViewsStartOrEnd();
  1.4955 +
  1.4956 +        final int offsetAfter;
  1.4957 +        if (mIsVertical) {
  1.4958 +            offsetAfter = temp.getBottom() + itemMargin;
  1.4959 +        } else {
  1.4960 +            offsetAfter = temp.getRight() + itemMargin;
  1.4961 +        }
  1.4962 +        final View after = fillAfter(position + 1, offsetAfter);
  1.4963 +
  1.4964 +        final int childCount = getChildCount();
  1.4965 +        if (childCount > 0) {
  1.4966 +            correctTooHigh(childCount);
  1.4967 +        }
  1.4968 +
  1.4969 +        if (tempIsSelected) {
  1.4970 +            return temp;
  1.4971 +        } else if (before != null) {
  1.4972 +            return before;
  1.4973 +        } else {
  1.4974 +            return after;
  1.4975 +        }
  1.4976 +    }
  1.4977 +
  1.4978 +    private View fillFromOffset(int nextOffset) {
  1.4979 +        mFirstPosition = Math.min(mFirstPosition, mSelectedPosition);
  1.4980 +        mFirstPosition = Math.min(mFirstPosition, mItemCount - 1);
  1.4981 +
  1.4982 +        if (mFirstPosition < 0) {
  1.4983 +            mFirstPosition = 0;
  1.4984 +        }
  1.4985 +
  1.4986 +        return fillAfter(mFirstPosition, nextOffset);
  1.4987 +    }
  1.4988 +
  1.4989 +    private View fillFromMiddle(int start, int end) {
  1.4990 +        final int size = end - start;
  1.4991 +        int position = reconcileSelectedPosition();
  1.4992 +
  1.4993 +        View selected = makeAndAddView(position, start, true, true);
  1.4994 +        mFirstPosition = position;
  1.4995 +
  1.4996 +        if (mIsVertical) {
  1.4997 +            int selectedHeight = selected.getMeasuredHeight();
  1.4998 +            if (selectedHeight <= size) {
  1.4999 +                selected.offsetTopAndBottom((size - selectedHeight) / 2);
  1.5000 +            }
  1.5001 +        } else {
  1.5002 +            int selectedWidth = selected.getMeasuredWidth();
  1.5003 +            if (selectedWidth <= size) {
  1.5004 +                selected.offsetLeftAndRight((size - selectedWidth) / 2);
  1.5005 +            }
  1.5006 +        }
  1.5007 +
  1.5008 +        fillBeforeAndAfter(selected, position);
  1.5009 +        correctTooHigh(getChildCount());
  1.5010 +
  1.5011 +        return selected;
  1.5012 +    }
  1.5013 +
  1.5014 +    private void fillBeforeAndAfter(View selected, int position) {
  1.5015 +        final int itemMargin = mItemMargin;
  1.5016 +
  1.5017 +        final int offsetBefore;
  1.5018 +        if (mIsVertical) {
  1.5019 +            offsetBefore = selected.getTop() - itemMargin;
  1.5020 +        } else {
  1.5021 +            offsetBefore = selected.getLeft() - itemMargin;
  1.5022 +        }
  1.5023 +
  1.5024 +        fillBefore(position - 1, offsetBefore);
  1.5025 +
  1.5026 +        adjustViewsStartOrEnd();
  1.5027 +
  1.5028 +        final int offsetAfter;
  1.5029 +        if (mIsVertical) {
  1.5030 +            offsetAfter = selected.getBottom() + itemMargin;
  1.5031 +        } else {
  1.5032 +            offsetAfter = selected.getRight() + itemMargin;
  1.5033 +        }
  1.5034 +
  1.5035 +        fillAfter(position + 1, offsetAfter);
  1.5036 +    }
  1.5037 +
  1.5038 +    private View fillFromSelection(int selectedTop, int start, int end) {
  1.5039 +        final int selectedPosition = mSelectedPosition;
  1.5040 +        View selected;
  1.5041 +
  1.5042 +        selected = makeAndAddView(selectedPosition, selectedTop, true, true);
  1.5043 +
  1.5044 +        final int selectedStart = (mIsVertical ? selected.getTop() : selected.getLeft());
  1.5045 +        final int selectedEnd = (mIsVertical ? selected.getBottom() : selected.getRight());
  1.5046 +
  1.5047 +        // Some of the newly selected item extends below the bottom of the list
  1.5048 +        if (selectedEnd > end) {
  1.5049 +            // Find space available above the selection into which we can scroll
  1.5050 +            // upwards
  1.5051 +            final int spaceAbove = selectedStart - start;
  1.5052 +
  1.5053 +            // Find space required to bring the bottom of the selected item
  1.5054 +            // fully into view
  1.5055 +            final int spaceBelow = selectedEnd - end;
  1.5056 +
  1.5057 +            final int offset = Math.min(spaceAbove, spaceBelow);
  1.5058 +
  1.5059 +            // Now offset the selected item to get it into view
  1.5060 +            selected.offsetTopAndBottom(-offset);
  1.5061 +        } else if (selectedStart < start) {
  1.5062 +            // Find space required to bring the top of the selected item fully
  1.5063 +            // into view
  1.5064 +            final int spaceAbove = start - selectedStart;
  1.5065 +
  1.5066 +            // Find space available below the selection into which we can scroll
  1.5067 +            // downwards
  1.5068 +            final int spaceBelow = end - selectedEnd;
  1.5069 +
  1.5070 +            final int offset = Math.min(spaceAbove, spaceBelow);
  1.5071 +
  1.5072 +            // Offset the selected item to get it into view
  1.5073 +            selected.offsetTopAndBottom(offset);
  1.5074 +        }
  1.5075 +
  1.5076 +        // Fill in views above and below
  1.5077 +        fillBeforeAndAfter(selected, selectedPosition);
  1.5078 +        correctTooHigh(getChildCount());
  1.5079 +
  1.5080 +        return selected;
  1.5081 +    }
  1.5082 +
  1.5083 +    private void correctTooHigh(int childCount) {
  1.5084 +        // First see if the last item is visible. If it is not, it is OK for the
  1.5085 +        // top of the list to be pushed up.
  1.5086 +        final int lastPosition = mFirstPosition + childCount - 1;
  1.5087 +        if (lastPosition != mItemCount - 1 || childCount == 0) {
  1.5088 +            return;
  1.5089 +        }
  1.5090 +
  1.5091 +        // Get the last child ...
  1.5092 +        final View lastChild = getChildAt(childCount - 1);
  1.5093 +
  1.5094 +        // ... and its end edge
  1.5095 +        final int lastEnd;
  1.5096 +        if (mIsVertical) {
  1.5097 +            lastEnd = lastChild.getBottom();
  1.5098 +        } else {
  1.5099 +            lastEnd = lastChild.getRight();
  1.5100 +        }
  1.5101 +
  1.5102 +        // This is bottom of our drawable area
  1.5103 +        final int start = (mIsVertical ? getPaddingTop() : getPaddingLeft());
  1.5104 +        final int end =
  1.5105 +                (mIsVertical ? getHeight() - getPaddingBottom() : getWidth() - getPaddingRight());
  1.5106 +
  1.5107 +        // This is how far the end edge of the last view is from the end of the
  1.5108 +        // drawable area
  1.5109 +        int endOffset = end - lastEnd;
  1.5110 +
  1.5111 +        View firstChild = getChildAt(0);
  1.5112 +        int firstStart = (mIsVertical ? firstChild.getTop() : firstChild.getLeft());
  1.5113 +
  1.5114 +        // Make sure we are 1) Too high, and 2) Either there are more rows above the
  1.5115 +        // first row or the first row is scrolled off the top of the drawable area
  1.5116 +        if (endOffset > 0 && (mFirstPosition > 0 || firstStart < start))  {
  1.5117 +            if (mFirstPosition == 0) {
  1.5118 +                // Don't pull the top too far down
  1.5119 +                endOffset = Math.min(endOffset, start - firstStart);
  1.5120 +            }
  1.5121 +
  1.5122 +            // Move everything down
  1.5123 +            offsetChildren(endOffset);
  1.5124 +
  1.5125 +            if (mFirstPosition > 0) {
  1.5126 +                firstStart = (mIsVertical ? firstChild.getTop() : firstChild.getLeft());
  1.5127 +
  1.5128 +                // Fill the gap that was opened above mFirstPosition with more rows, if
  1.5129 +                // possible
  1.5130 +                fillBefore(mFirstPosition - 1, firstStart - mItemMargin);
  1.5131 +
  1.5132 +                // Close up the remaining gap
  1.5133 +                adjustViewsStartOrEnd();
  1.5134 +            }
  1.5135 +        }
  1.5136 +    }
  1.5137 +
  1.5138 +    private void correctTooLow(int childCount) {
  1.5139 +        // First see if the first item is visible. If it is not, it is OK for the
  1.5140 +        // bottom of the list to be pushed down.
  1.5141 +        if (mFirstPosition != 0 || childCount == 0) {
  1.5142 +            return;
  1.5143 +        }
  1.5144 +
  1.5145 +        final View first = getChildAt(0);
  1.5146 +        final int firstStart = (mIsVertical ? first.getTop() : first.getLeft());
  1.5147 +
  1.5148 +        final int start = (mIsVertical ? getPaddingTop() : getPaddingLeft());
  1.5149 +
  1.5150 +        final int end;
  1.5151 +        if (mIsVertical) {
  1.5152 +            end = getHeight() - getPaddingBottom();
  1.5153 +        } else {
  1.5154 +            end = getWidth() - getPaddingRight();
  1.5155 +        }
  1.5156 +
  1.5157 +        // This is how far the start edge of the first view is from the start of the
  1.5158 +        // drawable area
  1.5159 +        int startOffset = firstStart - start;
  1.5160 +
  1.5161 +        View last = getChildAt(childCount - 1);
  1.5162 +        int lastEnd = (mIsVertical ? last.getBottom() : last.getRight());
  1.5163 +
  1.5164 +        int lastPosition = mFirstPosition + childCount - 1;
  1.5165 +
  1.5166 +        // Make sure we are 1) Too low, and 2) Either there are more columns/rows below the
  1.5167 +        // last column/row or the last column/row is scrolled off the end of the
  1.5168 +        // drawable area
  1.5169 +        if (startOffset > 0) {
  1.5170 +            if (lastPosition < mItemCount - 1 || lastEnd > end)  {
  1.5171 +                if (lastPosition == mItemCount - 1) {
  1.5172 +                    // Don't pull the bottom too far up
  1.5173 +                    startOffset = Math.min(startOffset, lastEnd - end);
  1.5174 +                }
  1.5175 +
  1.5176 +                // Move everything up
  1.5177 +                offsetChildren(-startOffset);
  1.5178 +
  1.5179 +                if (lastPosition < mItemCount - 1) {
  1.5180 +                    lastEnd = (mIsVertical ? last.getBottom() : last.getRight());
  1.5181 +
  1.5182 +                    // Fill the gap that was opened below the last position with more rows, if
  1.5183 +                    // possible
  1.5184 +                    fillAfter(lastPosition + 1, lastEnd + mItemMargin);
  1.5185 +
  1.5186 +                    // Close up the remaining gap
  1.5187 +                    adjustViewsStartOrEnd();
  1.5188 +                }
  1.5189 +            } else if (lastPosition == mItemCount - 1) {
  1.5190 +                adjustViewsStartOrEnd();
  1.5191 +            }
  1.5192 +        }
  1.5193 +    }
  1.5194 +
  1.5195 +    private void adjustViewsStartOrEnd() {
  1.5196 +        if (getChildCount() == 0) {
  1.5197 +            return;
  1.5198 +        }
  1.5199 +
  1.5200 +        final View firstChild = getChildAt(0);
  1.5201 +
  1.5202 +        int delta;
  1.5203 +        if (mIsVertical) {
  1.5204 +            delta = firstChild.getTop() - getPaddingTop() - mItemMargin;
  1.5205 +        } else {
  1.5206 +            delta = firstChild.getLeft() - getPaddingLeft() - mItemMargin;
  1.5207 +        }
  1.5208 +
  1.5209 +        if (delta < 0) {
  1.5210 +            // We only are looking to see if we are too low, not too high
  1.5211 +            delta = 0;
  1.5212 +        }
  1.5213 +
  1.5214 +        if (delta != 0) {
  1.5215 +            offsetChildren(-delta);
  1.5216 +        }
  1.5217 +    }
  1.5218 +
  1.5219 +    @TargetApi(14)
  1.5220 +    private SparseBooleanArray cloneCheckStates() {
  1.5221 +        if (mCheckStates == null) {
  1.5222 +            return null;
  1.5223 +        }
  1.5224 +
  1.5225 +        SparseBooleanArray checkedStates;
  1.5226 +
  1.5227 +        if (Build.VERSION.SDK_INT >= 14) {
  1.5228 +            checkedStates = mCheckStates.clone();
  1.5229 +        } else {
  1.5230 +            checkedStates = new SparseBooleanArray();
  1.5231 +
  1.5232 +            for (int i = 0; i < mCheckStates.size(); i++) {
  1.5233 +                checkedStates.put(mCheckStates.keyAt(i), mCheckStates.valueAt(i));
  1.5234 +            }
  1.5235 +        }
  1.5236 +
  1.5237 +        return checkedStates;
  1.5238 +    }
  1.5239 +
  1.5240 +    private int findSyncPosition() {
  1.5241 +        int itemCount = mItemCount;
  1.5242 +
  1.5243 +        if (itemCount == 0) {
  1.5244 +            return INVALID_POSITION;
  1.5245 +        }
  1.5246 +
  1.5247 +        final long idToMatch = mSyncRowId;
  1.5248 +
  1.5249 +        // If there isn't a selection don't hunt for it
  1.5250 +        if (idToMatch == INVALID_ROW_ID) {
  1.5251 +            return INVALID_POSITION;
  1.5252 +        }
  1.5253 +
  1.5254 +        // Pin seed to reasonable values
  1.5255 +        int seed = mSyncPosition;
  1.5256 +        seed = Math.max(0, seed);
  1.5257 +        seed = Math.min(itemCount - 1, seed);
  1.5258 +
  1.5259 +        long endTime = SystemClock.uptimeMillis() + SYNC_MAX_DURATION_MILLIS;
  1.5260 +
  1.5261 +        long rowId;
  1.5262 +
  1.5263 +        // first position scanned so far
  1.5264 +        int first = seed;
  1.5265 +
  1.5266 +        // last position scanned so far
  1.5267 +        int last = seed;
  1.5268 +
  1.5269 +        // True if we should move down on the next iteration
  1.5270 +        boolean next = false;
  1.5271 +
  1.5272 +        // True when we have looked at the first item in the data
  1.5273 +        boolean hitFirst;
  1.5274 +
  1.5275 +        // True when we have looked at the last item in the data
  1.5276 +        boolean hitLast;
  1.5277 +
  1.5278 +        // Get the item ID locally (instead of getItemIdAtPosition), so
  1.5279 +        // we need the adapter
  1.5280 +        final ListAdapter adapter = mAdapter;
  1.5281 +        if (adapter == null) {
  1.5282 +            return INVALID_POSITION;
  1.5283 +        }
  1.5284 +
  1.5285 +        while (SystemClock.uptimeMillis() <= endTime) {
  1.5286 +            rowId = adapter.getItemId(seed);
  1.5287 +            if (rowId == idToMatch) {
  1.5288 +                // Found it!
  1.5289 +                return seed;
  1.5290 +            }
  1.5291 +
  1.5292 +            hitLast = (last == itemCount - 1);
  1.5293 +            hitFirst = (first == 0);
  1.5294 +
  1.5295 +            if (hitLast && hitFirst) {
  1.5296 +                // Looked at everything
  1.5297 +                break;
  1.5298 +            }
  1.5299 +
  1.5300 +            if (hitFirst || (next && !hitLast)) {
  1.5301 +                // Either we hit the top, or we are trying to move down
  1.5302 +                last++;
  1.5303 +                seed = last;
  1.5304 +
  1.5305 +                // Try going up next time
  1.5306 +                next = false;
  1.5307 +            } else if (hitLast || (!next && !hitFirst)) {
  1.5308 +                // Either we hit the bottom, or we are trying to move up
  1.5309 +                first--;
  1.5310 +                seed = first;
  1.5311 +
  1.5312 +                // Try going down next time
  1.5313 +                next = true;
  1.5314 +            }
  1.5315 +        }
  1.5316 +
  1.5317 +        return INVALID_POSITION;
  1.5318 +    }
  1.5319 +
  1.5320 +    @TargetApi(16)
  1.5321 +    private View obtainView(int position, boolean[] isScrap) {
  1.5322 +        isScrap[0] = false;
  1.5323 +
  1.5324 +        View scrapView = mRecycler.getTransientStateView(position);
  1.5325 +        if (scrapView != null) {
  1.5326 +            return scrapView;
  1.5327 +        }
  1.5328 +
  1.5329 +        scrapView = mRecycler.getScrapView(position);
  1.5330 +
  1.5331 +        final View child;
  1.5332 +        if (scrapView != null) {
  1.5333 +            child = mAdapter.getView(position, scrapView, this);
  1.5334 +
  1.5335 +            if (child != scrapView) {
  1.5336 +                mRecycler.addScrapView(scrapView, position);
  1.5337 +            } else {
  1.5338 +                isScrap[0] = true;
  1.5339 +            }
  1.5340 +        } else {
  1.5341 +            child = mAdapter.getView(position, null, this);
  1.5342 +        }
  1.5343 +
  1.5344 +        if (ViewCompat.getImportantForAccessibility(child) == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
  1.5345 +            ViewCompat.setImportantForAccessibility(child, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
  1.5346 +        }
  1.5347 +
  1.5348 +        if (mHasStableIds) {
  1.5349 +            LayoutParams lp = (LayoutParams) child.getLayoutParams();
  1.5350 +
  1.5351 +            if (lp == null) {
  1.5352 +                lp = generateDefaultLayoutParams();
  1.5353 +            } else if (!checkLayoutParams(lp)) {
  1.5354 +                lp = generateLayoutParams(lp);
  1.5355 +            }
  1.5356 +
  1.5357 +            lp.id = mAdapter.getItemId(position);
  1.5358 +
  1.5359 +            child.setLayoutParams(lp);
  1.5360 +        }
  1.5361 +
  1.5362 +        if (mAccessibilityDelegate == null) {
  1.5363 +            mAccessibilityDelegate = new ListItemAccessibilityDelegate();
  1.5364 +        }
  1.5365 +
  1.5366 +        ViewCompat.setAccessibilityDelegate(child, mAccessibilityDelegate);
  1.5367 +
  1.5368 +        return child;
  1.5369 +    }
  1.5370 +
  1.5371 +    void resetState() {
  1.5372 +        removeAllViewsInLayout();
  1.5373 +
  1.5374 +        mSelectedStart = 0;
  1.5375 +        mFirstPosition = 0;
  1.5376 +        mDataChanged = false;
  1.5377 +        mNeedSync = false;
  1.5378 +        mPendingSync = null;
  1.5379 +        mOldSelectedPosition = INVALID_POSITION;
  1.5380 +        mOldSelectedRowId = INVALID_ROW_ID;
  1.5381 +
  1.5382 +        mOverScroll = 0;
  1.5383 +
  1.5384 +        setSelectedPositionInt(INVALID_POSITION);
  1.5385 +        setNextSelectedPositionInt(INVALID_POSITION);
  1.5386 +
  1.5387 +        mSelectorPosition = INVALID_POSITION;
  1.5388 +        mSelectorRect.setEmpty();
  1.5389 +
  1.5390 +        invalidate();
  1.5391 +    }
  1.5392 +
  1.5393 +    private void rememberSyncState() {
  1.5394 +        if (getChildCount() == 0) {
  1.5395 +            return;
  1.5396 +        }
  1.5397 +
  1.5398 +        mNeedSync = true;
  1.5399 +
  1.5400 +        if (mSelectedPosition >= 0) {
  1.5401 +            View child = getChildAt(mSelectedPosition - mFirstPosition);
  1.5402 +
  1.5403 +            mSyncRowId = mNextSelectedRowId;
  1.5404 +            mSyncPosition = mNextSelectedPosition;
  1.5405 +
  1.5406 +            if (child != null) {
  1.5407 +                mSpecificStart = (mIsVertical ? child.getTop() : child.getLeft());
  1.5408 +            }
  1.5409 +
  1.5410 +            mSyncMode = SYNC_SELECTED_POSITION;
  1.5411 +        } else {
  1.5412 +            // Sync the based on the offset of the first view
  1.5413 +            View child = getChildAt(0);
  1.5414 +            ListAdapter adapter = getAdapter();
  1.5415 +
  1.5416 +            if (mFirstPosition >= 0 && mFirstPosition < adapter.getCount()) {
  1.5417 +                mSyncRowId = adapter.getItemId(mFirstPosition);
  1.5418 +            } else {
  1.5419 +                mSyncRowId = NO_ID;
  1.5420 +            }
  1.5421 +
  1.5422 +            mSyncPosition = mFirstPosition;
  1.5423 +
  1.5424 +            if (child != null) {
  1.5425 +                mSpecificStart = child.getTop();
  1.5426 +            }
  1.5427 +
  1.5428 +            mSyncMode = SYNC_FIRST_POSITION;
  1.5429 +        }
  1.5430 +    }
  1.5431 +
  1.5432 +    private ContextMenuInfo createContextMenuInfo(View view, int position, long id) {
  1.5433 +        return new AdapterContextMenuInfo(view, position, id);
  1.5434 +    }
  1.5435 +
  1.5436 +    @TargetApi(11)
  1.5437 +    private void updateOnScreenCheckedViews() {
  1.5438 +        final int firstPos = mFirstPosition;
  1.5439 +        final int count = getChildCount();
  1.5440 +
  1.5441 +        final boolean useActivated = getContext().getApplicationInfo().targetSdkVersion
  1.5442 +                >= Build.VERSION_CODES.HONEYCOMB;
  1.5443 +
  1.5444 +        for (int i = 0; i < count; i++) {
  1.5445 +            final View child = getChildAt(i);
  1.5446 +            final int position = firstPos + i;
  1.5447 +
  1.5448 +            if (child instanceof Checkable) {
  1.5449 +                ((Checkable) child).setChecked(mCheckStates.get(position));
  1.5450 +            } else if (useActivated) {
  1.5451 +                child.setActivated(mCheckStates.get(position));
  1.5452 +            }
  1.5453 +        }
  1.5454 +    }
  1.5455 +
  1.5456 +    @Override
  1.5457 +    public boolean performItemClick(View view, int position, long id) {
  1.5458 +        boolean checkedStateChanged = false;
  1.5459 +
  1.5460 +        if (mChoiceMode.compareTo(ChoiceMode.MULTIPLE) == 0) {
  1.5461 +            boolean checked = !mCheckStates.get(position, false);
  1.5462 +            mCheckStates.put(position, checked);
  1.5463 +
  1.5464 +            if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
  1.5465 +                if (checked) {
  1.5466 +                    mCheckedIdStates.put(mAdapter.getItemId(position), position);
  1.5467 +                } else {
  1.5468 +                    mCheckedIdStates.delete(mAdapter.getItemId(position));
  1.5469 +                }
  1.5470 +            }
  1.5471 +
  1.5472 +            if (checked) {
  1.5473 +                mCheckedItemCount++;
  1.5474 +            } else {
  1.5475 +                mCheckedItemCount--;
  1.5476 +            }
  1.5477 +
  1.5478 +            checkedStateChanged = true;
  1.5479 +        } else if (mChoiceMode.compareTo(ChoiceMode.SINGLE) == 0) {
  1.5480 +            boolean checked = !mCheckStates.get(position, false);
  1.5481 +            if (checked) {
  1.5482 +                mCheckStates.clear();
  1.5483 +                mCheckStates.put(position, true);
  1.5484 +
  1.5485 +                if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
  1.5486 +                    mCheckedIdStates.clear();
  1.5487 +                    mCheckedIdStates.put(mAdapter.getItemId(position), position);
  1.5488 +                }
  1.5489 +
  1.5490 +                mCheckedItemCount = 1;
  1.5491 +            } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
  1.5492 +                mCheckedItemCount = 0;
  1.5493 +            }
  1.5494 +
  1.5495 +            checkedStateChanged = true;
  1.5496 +        }
  1.5497 +
  1.5498 +        if (checkedStateChanged) {
  1.5499 +            updateOnScreenCheckedViews();
  1.5500 +        }
  1.5501 +
  1.5502 +        return super.performItemClick(view, position, id);
  1.5503 +    }
  1.5504 +
  1.5505 +    private boolean performLongPress(final View child,
  1.5506 +            final int longPressPosition, final long longPressId) {
  1.5507 +        // CHOICE_MODE_MULTIPLE_MODAL takes over long press.
  1.5508 +        boolean handled = false;
  1.5509 +
  1.5510 +        OnItemLongClickListener listener = getOnItemLongClickListener();
  1.5511 +        if (listener != null) {
  1.5512 +            handled = listener.onItemLongClick(TwoWayView.this, child,
  1.5513 +                    longPressPosition, longPressId);
  1.5514 +        }
  1.5515 +
  1.5516 +        if (!handled) {
  1.5517 +            mContextMenuInfo = createContextMenuInfo(child, longPressPosition, longPressId);
  1.5518 +            handled = super.showContextMenuForChild(TwoWayView.this);
  1.5519 +        }
  1.5520 +
  1.5521 +        if (handled) {
  1.5522 +            performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
  1.5523 +        }
  1.5524 +
  1.5525 +        return handled;
  1.5526 +    }
  1.5527 +
  1.5528 +    @Override
  1.5529 +    protected LayoutParams generateDefaultLayoutParams() {
  1.5530 +        if (mIsVertical) {
  1.5531 +            return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
  1.5532 +        } else {
  1.5533 +            return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
  1.5534 +        }
  1.5535 +    }
  1.5536 +
  1.5537 +    @Override
  1.5538 +    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
  1.5539 +        return new LayoutParams(lp);
  1.5540 +    }
  1.5541 +
  1.5542 +    @Override
  1.5543 +    protected boolean checkLayoutParams(ViewGroup.LayoutParams lp) {
  1.5544 +        return lp instanceof LayoutParams;
  1.5545 +    }
  1.5546 +
  1.5547 +    @Override
  1.5548 +    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
  1.5549 +        return new LayoutParams(getContext(), attrs);
  1.5550 +    }
  1.5551 +
  1.5552 +    @Override
  1.5553 +    protected ContextMenuInfo getContextMenuInfo() {
  1.5554 +        return mContextMenuInfo;
  1.5555 +    }
  1.5556 +
  1.5557 +    @Override
  1.5558 +    public Parcelable onSaveInstanceState() {
  1.5559 +        Parcelable superState = super.onSaveInstanceState();
  1.5560 +        SavedState ss = new SavedState(superState);
  1.5561 +
  1.5562 +        if (mPendingSync != null) {
  1.5563 +            ss.selectedId = mPendingSync.selectedId;
  1.5564 +            ss.firstId = mPendingSync.firstId;
  1.5565 +            ss.viewStart = mPendingSync.viewStart;
  1.5566 +            ss.position = mPendingSync.position;
  1.5567 +            ss.height = mPendingSync.height;
  1.5568 +
  1.5569 +            return ss;
  1.5570 +        }
  1.5571 +
  1.5572 +        boolean haveChildren = (getChildCount() > 0 && mItemCount > 0);
  1.5573 +        long selectedId = getSelectedItemId();
  1.5574 +        ss.selectedId = selectedId;
  1.5575 +        ss.height = getHeight();
  1.5576 +
  1.5577 +        if (selectedId >= 0) {
  1.5578 +            ss.viewStart = mSelectedStart;
  1.5579 +            ss.position = getSelectedItemPosition();
  1.5580 +            ss.firstId = INVALID_POSITION;
  1.5581 +        } else if (haveChildren && mFirstPosition > 0) {
  1.5582 +            // Remember the position of the first child.
  1.5583 +            // We only do this if we are not currently at the top of
  1.5584 +            // the list, for two reasons:
  1.5585 +            //
  1.5586 +            // (1) The list may be in the process of becoming empty, in
  1.5587 +            // which case mItemCount may not be 0, but if we try to
  1.5588 +            // ask for any information about position 0 we will crash.
  1.5589 +            //
  1.5590 +            // (2) Being "at the top" seems like a special case, anyway,
  1.5591 +            // and the user wouldn't expect to end up somewhere else when
  1.5592 +            // they revisit the list even if its content has changed.
  1.5593 +
  1.5594 +            View child = getChildAt(0);
  1.5595 +            ss.viewStart = (mIsVertical ? child.getTop() : child.getLeft());
  1.5596 +
  1.5597 +            int firstPos = mFirstPosition;
  1.5598 +            if (firstPos >= mItemCount) {
  1.5599 +                firstPos = mItemCount - 1;
  1.5600 +            }
  1.5601 +
  1.5602 +            ss.position = firstPos;
  1.5603 +            ss.firstId = mAdapter.getItemId(firstPos);
  1.5604 +        } else {
  1.5605 +            ss.viewStart = 0;
  1.5606 +            ss.firstId = INVALID_POSITION;
  1.5607 +            ss.position = 0;
  1.5608 +        }
  1.5609 +
  1.5610 +        if (mCheckStates != null) {
  1.5611 +            ss.checkState = cloneCheckStates();
  1.5612 +        }
  1.5613 +
  1.5614 +        if (mCheckedIdStates != null) {
  1.5615 +            final LongSparseArray<Integer> idState = new LongSparseArray<Integer>();
  1.5616 +
  1.5617 +            final int count = mCheckedIdStates.size();
  1.5618 +            for (int i = 0; i < count; i++) {
  1.5619 +                idState.put(mCheckedIdStates.keyAt(i), mCheckedIdStates.valueAt(i));
  1.5620 +            }
  1.5621 +
  1.5622 +            ss.checkIdState = idState;
  1.5623 +        }
  1.5624 +
  1.5625 +        ss.checkedItemCount = mCheckedItemCount;
  1.5626 +
  1.5627 +        return ss;
  1.5628 +    }
  1.5629 +
  1.5630 +    @Override
  1.5631 +    public void onRestoreInstanceState(Parcelable state) {
  1.5632 +        SavedState ss = (SavedState) state;
  1.5633 +        super.onRestoreInstanceState(ss.getSuperState());
  1.5634 +
  1.5635 +        mDataChanged = true;
  1.5636 +        mSyncHeight = ss.height;
  1.5637 +
  1.5638 +        if (ss.selectedId >= 0) {
  1.5639 +            mNeedSync = true;
  1.5640 +            mPendingSync = ss;
  1.5641 +            mSyncRowId = ss.selectedId;
  1.5642 +            mSyncPosition = ss.position;
  1.5643 +            mSpecificStart = ss.viewStart;
  1.5644 +            mSyncMode = SYNC_SELECTED_POSITION;
  1.5645 +        } else if (ss.firstId >= 0) {
  1.5646 +            setSelectedPositionInt(INVALID_POSITION);
  1.5647 +
  1.5648 +            // Do this before setting mNeedSync since setNextSelectedPosition looks at mNeedSync
  1.5649 +            setNextSelectedPositionInt(INVALID_POSITION);
  1.5650 +
  1.5651 +            mSelectorPosition = INVALID_POSITION;
  1.5652 +            mNeedSync = true;
  1.5653 +            mPendingSync = ss;
  1.5654 +            mSyncRowId = ss.firstId;
  1.5655 +            mSyncPosition = ss.position;
  1.5656 +            mSpecificStart = ss.viewStart;
  1.5657 +            mSyncMode = SYNC_FIRST_POSITION;
  1.5658 +        }
  1.5659 +
  1.5660 +        if (ss.checkState != null) {
  1.5661 +            mCheckStates = ss.checkState;
  1.5662 +        }
  1.5663 +
  1.5664 +        if (ss.checkIdState != null) {
  1.5665 +            mCheckedIdStates = ss.checkIdState;
  1.5666 +        }
  1.5667 +
  1.5668 +        mCheckedItemCount = ss.checkedItemCount;
  1.5669 +
  1.5670 +        requestLayout();
  1.5671 +    }
  1.5672 +
  1.5673 +    public static class LayoutParams extends ViewGroup.LayoutParams {
  1.5674 +        /**
  1.5675 +         * Type of this view as reported by the adapter
  1.5676 +         */
  1.5677 +        int viewType;
  1.5678 +
  1.5679 +        /**
  1.5680 +         * The stable ID of the item this view displays
  1.5681 +         */
  1.5682 +        long id = -1;
  1.5683 +
  1.5684 +        /**
  1.5685 +         * The position the view was removed from when pulled out of the
  1.5686 +         * scrap heap.
  1.5687 +         * @hide
  1.5688 +         */
  1.5689 +        int scrappedFromPosition;
  1.5690 +
  1.5691 +        /**
  1.5692 +         * When a TwoWayView is measured with an AT_MOST measure spec, it needs
  1.5693 +         * to obtain children views to measure itself. When doing so, the children
  1.5694 +         * are not attached to the window, but put in the recycler which assumes
  1.5695 +         * they've been attached before. Setting this flag will force the reused
  1.5696 +         * view to be attached to the window rather than just attached to the
  1.5697 +         * parent.
  1.5698 +         */
  1.5699 +        boolean forceAdd;
  1.5700 +
  1.5701 +        public LayoutParams(int width, int height) {
  1.5702 +            super(width, height);
  1.5703 +
  1.5704 +            if (this.width == MATCH_PARENT) {
  1.5705 +                Log.w(LOGTAG, "Constructing LayoutParams with width FILL_PARENT " +
  1.5706 +                        "does not make much sense as the view might change orientation. " +
  1.5707 +                        "Falling back to WRAP_CONTENT");
  1.5708 +                this.width = WRAP_CONTENT;
  1.5709 +            }
  1.5710 +
  1.5711 +            if (this.height == MATCH_PARENT) {
  1.5712 +                Log.w(LOGTAG, "Constructing LayoutParams with height FILL_PARENT " +
  1.5713 +                        "does not make much sense as the view might change orientation. " +
  1.5714 +                        "Falling back to WRAP_CONTENT");
  1.5715 +                this.height = WRAP_CONTENT;
  1.5716 +            }
  1.5717 +        }
  1.5718 +
  1.5719 +        public LayoutParams(Context c, AttributeSet attrs) {
  1.5720 +            super(c, attrs);
  1.5721 +
  1.5722 +            if (this.width == MATCH_PARENT) {
  1.5723 +                Log.w(LOGTAG, "Inflation setting LayoutParams width to MATCH_PARENT - " +
  1.5724 +                        "does not make much sense as the view might change orientation. " +
  1.5725 +                        "Falling back to WRAP_CONTENT");
  1.5726 +                this.width = MATCH_PARENT;
  1.5727 +            }
  1.5728 +
  1.5729 +            if (this.height == MATCH_PARENT) {
  1.5730 +                Log.w(LOGTAG, "Inflation setting LayoutParams height to MATCH_PARENT - " +
  1.5731 +                        "does not make much sense as the view might change orientation. " +
  1.5732 +                        "Falling back to WRAP_CONTENT");
  1.5733 +                this.height = WRAP_CONTENT;
  1.5734 +            }
  1.5735 +        }
  1.5736 +
  1.5737 +        public LayoutParams(ViewGroup.LayoutParams other) {
  1.5738 +            super(other);
  1.5739 +
  1.5740 +            if (this.width == MATCH_PARENT) {
  1.5741 +                Log.w(LOGTAG, "Constructing LayoutParams with height MATCH_PARENT - " +
  1.5742 +                        "does not make much sense as the view might change orientation. " +
  1.5743 +                        "Falling back to WRAP_CONTENT");
  1.5744 +                this.width = WRAP_CONTENT;
  1.5745 +            }
  1.5746 +
  1.5747 +            if (this.height == MATCH_PARENT) {
  1.5748 +                Log.w(LOGTAG, "Constructing LayoutParams with height MATCH_PARENT - " +
  1.5749 +                        "does not make much sense as the view might change orientation. " +
  1.5750 +                        "Falling back to WRAP_CONTENT");
  1.5751 +                this.height = WRAP_CONTENT;
  1.5752 +            }
  1.5753 +        }
  1.5754 +    }
  1.5755 +
  1.5756 +    class RecycleBin {
  1.5757 +        private RecyclerListener mRecyclerListener;
  1.5758 +        private int mFirstActivePosition;
  1.5759 +        private View[] mActiveViews = new View[0];
  1.5760 +        private ArrayList<View>[] mScrapViews;
  1.5761 +        private int mViewTypeCount;
  1.5762 +        private ArrayList<View> mCurrentScrap;
  1.5763 +        private SparseArrayCompat<View> mTransientStateViews;
  1.5764 +
  1.5765 +        public void setViewTypeCount(int viewTypeCount) {
  1.5766 +            if (viewTypeCount < 1) {
  1.5767 +                throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
  1.5768 +            }
  1.5769 +
  1.5770 +            @SuppressWarnings({"unchecked", "rawtypes"})
  1.5771 +            ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
  1.5772 +            for (int i = 0; i < viewTypeCount; i++) {
  1.5773 +                scrapViews[i] = new ArrayList<View>();
  1.5774 +            }
  1.5775 +
  1.5776 +            mViewTypeCount = viewTypeCount;
  1.5777 +            mCurrentScrap = scrapViews[0];
  1.5778 +            mScrapViews = scrapViews;
  1.5779 +        }
  1.5780 +
  1.5781 +        public void markChildrenDirty() {
  1.5782 +            if (mViewTypeCount == 1) {
  1.5783 +                final ArrayList<View> scrap = mCurrentScrap;
  1.5784 +                final int scrapCount = scrap.size();
  1.5785 +
  1.5786 +                for (int i = 0; i < scrapCount; i++) {
  1.5787 +                    scrap.get(i).forceLayout();
  1.5788 +                }
  1.5789 +            } else {
  1.5790 +                final int typeCount = mViewTypeCount;
  1.5791 +                for (int i = 0; i < typeCount; i++) {
  1.5792 +                    final ArrayList<View> scrap = mScrapViews[i];
  1.5793 +                    final int scrapCount = scrap.size();
  1.5794 +
  1.5795 +                    for (int j = 0; j < scrapCount; j++) {
  1.5796 +                        scrap.get(j).forceLayout();
  1.5797 +                    }
  1.5798 +                }
  1.5799 +            }
  1.5800 +
  1.5801 +            if (mTransientStateViews != null) {
  1.5802 +                final int count = mTransientStateViews.size();
  1.5803 +                for (int i = 0; i < count; i++) {
  1.5804 +                    mTransientStateViews.valueAt(i).forceLayout();
  1.5805 +                }
  1.5806 +            }
  1.5807 +        }
  1.5808 +
  1.5809 +        public boolean shouldRecycleViewType(int viewType) {
  1.5810 +            return viewType >= 0;
  1.5811 +        }
  1.5812 +
  1.5813 +        void clear() {
  1.5814 +            if (mViewTypeCount == 1) {
  1.5815 +                final ArrayList<View> scrap = mCurrentScrap;
  1.5816 +                final int scrapCount = scrap.size();
  1.5817 +
  1.5818 +                for (int i = 0; i < scrapCount; i++) {
  1.5819 +                    removeDetachedView(scrap.remove(scrapCount - 1 - i), false);
  1.5820 +                }
  1.5821 +            } else {
  1.5822 +                final int typeCount = mViewTypeCount;
  1.5823 +                for (int i = 0; i < typeCount; i++) {
  1.5824 +                    final ArrayList<View> scrap = mScrapViews[i];
  1.5825 +                    final int scrapCount = scrap.size();
  1.5826 +
  1.5827 +                    for (int j = 0; j < scrapCount; j++) {
  1.5828 +                        removeDetachedView(scrap.remove(scrapCount - 1 - j), false);
  1.5829 +                    }
  1.5830 +                }
  1.5831 +            }
  1.5832 +
  1.5833 +            if (mTransientStateViews != null) {
  1.5834 +                mTransientStateViews.clear();
  1.5835 +            }
  1.5836 +        }
  1.5837 +
  1.5838 +        void fillActiveViews(int childCount, int firstActivePosition) {
  1.5839 +            if (mActiveViews.length < childCount) {
  1.5840 +                mActiveViews = new View[childCount];
  1.5841 +            }
  1.5842 +
  1.5843 +            mFirstActivePosition = firstActivePosition;
  1.5844 +
  1.5845 +            final View[] activeViews = mActiveViews;
  1.5846 +            for (int i = 0; i < childCount; i++) {
  1.5847 +                View child = getChildAt(i);
  1.5848 +
  1.5849 +                // Note:  We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views.
  1.5850 +                //        However, we will NOT place them into scrap views.
  1.5851 +                activeViews[i] = child;
  1.5852 +            }
  1.5853 +        }
  1.5854 +
  1.5855 +        View getActiveView(int position) {
  1.5856 +            final int index = position - mFirstActivePosition;
  1.5857 +            final View[] activeViews = mActiveViews;
  1.5858 +
  1.5859 +            if (index >= 0 && index < activeViews.length) {
  1.5860 +                final View match = activeViews[index];
  1.5861 +                activeViews[index] = null;
  1.5862 +
  1.5863 +                return match;
  1.5864 +            }
  1.5865 +
  1.5866 +            return null;
  1.5867 +        }
  1.5868 +
  1.5869 +        View getTransientStateView(int position) {
  1.5870 +            if (mTransientStateViews == null) {
  1.5871 +                return null;
  1.5872 +            }
  1.5873 +
  1.5874 +            final int index = mTransientStateViews.indexOfKey(position);
  1.5875 +            if (index < 0) {
  1.5876 +                return null;
  1.5877 +            }
  1.5878 +
  1.5879 +            final View result = mTransientStateViews.valueAt(index);
  1.5880 +            mTransientStateViews.removeAt(index);
  1.5881 +
  1.5882 +            return result;
  1.5883 +        }
  1.5884 +
  1.5885 +        void clearTransientStateViews() {
  1.5886 +            if (mTransientStateViews != null) {
  1.5887 +                mTransientStateViews.clear();
  1.5888 +            }
  1.5889 +        }
  1.5890 +
  1.5891 +        View getScrapView(int position) {
  1.5892 +            if (mViewTypeCount == 1) {
  1.5893 +                return retrieveFromScrap(mCurrentScrap, position);
  1.5894 +            } else {
  1.5895 +                int whichScrap = mAdapter.getItemViewType(position);
  1.5896 +                if (whichScrap >= 0 && whichScrap < mScrapViews.length) {
  1.5897 +                    return retrieveFromScrap(mScrapViews[whichScrap], position);
  1.5898 +                }
  1.5899 +            }
  1.5900 +
  1.5901 +            return null;
  1.5902 +        }
  1.5903 +
  1.5904 +        @TargetApi(14)
  1.5905 +        void addScrapView(View scrap, int position) {
  1.5906 +            LayoutParams lp = (LayoutParams) scrap.getLayoutParams();
  1.5907 +            if (lp == null) {
  1.5908 +                return;
  1.5909 +            }
  1.5910 +
  1.5911 +            lp.scrappedFromPosition = position;
  1.5912 +
  1.5913 +            final int viewType = lp.viewType;
  1.5914 +            final boolean scrapHasTransientState = ViewCompat.hasTransientState(scrap);
  1.5915 +
  1.5916 +            // Don't put views that should be ignored into the scrap heap
  1.5917 +            if (!shouldRecycleViewType(viewType) || scrapHasTransientState) {
  1.5918 +                if (scrapHasTransientState) {
  1.5919 +                    if (mTransientStateViews == null) {
  1.5920 +                        mTransientStateViews = new SparseArrayCompat<View>();
  1.5921 +                    }
  1.5922 +
  1.5923 +                    mTransientStateViews.put(position, scrap);
  1.5924 +                }
  1.5925 +
  1.5926 +                return;
  1.5927 +            }
  1.5928 +
  1.5929 +            if (mViewTypeCount == 1) {
  1.5930 +                mCurrentScrap.add(scrap);
  1.5931 +            } else {
  1.5932 +                mScrapViews[viewType].add(scrap);
  1.5933 +            }
  1.5934 +
  1.5935 +            // FIXME: Unfortunately, ViewCompat.setAccessibilityDelegate() doesn't accept
  1.5936 +            // null delegates.
  1.5937 +            if (Build.VERSION.SDK_INT >= 14) {
  1.5938 +                scrap.setAccessibilityDelegate(null);
  1.5939 +            }
  1.5940 +
  1.5941 +            if (mRecyclerListener != null) {
  1.5942 +                mRecyclerListener.onMovedToScrapHeap(scrap);
  1.5943 +            }
  1.5944 +        }
  1.5945 +
  1.5946 +        @TargetApi(14)
  1.5947 +        void scrapActiveViews() {
  1.5948 +            final View[] activeViews = mActiveViews;
  1.5949 +            final boolean multipleScraps = (mViewTypeCount > 1);
  1.5950 +
  1.5951 +            ArrayList<View> scrapViews = mCurrentScrap;
  1.5952 +            final int count = activeViews.length;
  1.5953 +
  1.5954 +            for (int i = count - 1; i >= 0; i--) {
  1.5955 +                final View victim = activeViews[i];
  1.5956 +                if (victim != null) {
  1.5957 +                    final LayoutParams lp = (LayoutParams) victim.getLayoutParams();
  1.5958 +                    int whichScrap = lp.viewType;
  1.5959 +
  1.5960 +                    activeViews[i] = null;
  1.5961 +
  1.5962 +                    final boolean scrapHasTransientState = ViewCompat.hasTransientState(victim);
  1.5963 +                    if (!shouldRecycleViewType(whichScrap) || scrapHasTransientState) {
  1.5964 +                        if (scrapHasTransientState) {
  1.5965 +                            removeDetachedView(victim, false);
  1.5966 +
  1.5967 +                            if (mTransientStateViews == null) {
  1.5968 +                                mTransientStateViews = new SparseArrayCompat<View>();
  1.5969 +                            }
  1.5970 +
  1.5971 +                            mTransientStateViews.put(mFirstActivePosition + i, victim);
  1.5972 +                        }
  1.5973 +
  1.5974 +                        continue;
  1.5975 +                    }
  1.5976 +
  1.5977 +                    if (multipleScraps) {
  1.5978 +                        scrapViews = mScrapViews[whichScrap];
  1.5979 +                    }
  1.5980 +
  1.5981 +                    lp.scrappedFromPosition = mFirstActivePosition + i;
  1.5982 +                    scrapViews.add(victim);
  1.5983 +
  1.5984 +                    // FIXME: Unfortunately, ViewCompat.setAccessibilityDelegate() doesn't accept
  1.5985 +                    // null delegates.
  1.5986 +                    if (Build.VERSION.SDK_INT >= 14) {
  1.5987 +                        victim.setAccessibilityDelegate(null);
  1.5988 +                    }
  1.5989 +
  1.5990 +                    if (mRecyclerListener != null) {
  1.5991 +                        mRecyclerListener.onMovedToScrapHeap(victim);
  1.5992 +                    }
  1.5993 +                }
  1.5994 +            }
  1.5995 +
  1.5996 +            pruneScrapViews();
  1.5997 +        }
  1.5998 +
  1.5999 +        private void pruneScrapViews() {
  1.6000 +            final int maxViews = mActiveViews.length;
  1.6001 +            final int viewTypeCount = mViewTypeCount;
  1.6002 +            final ArrayList<View>[] scrapViews = mScrapViews;
  1.6003 +
  1.6004 +            for (int i = 0; i < viewTypeCount; ++i) {
  1.6005 +                final ArrayList<View> scrapPile = scrapViews[i];
  1.6006 +                int size = scrapPile.size();
  1.6007 +                final int extras = size - maxViews;
  1.6008 +
  1.6009 +                size--;
  1.6010 +
  1.6011 +                for (int j = 0; j < extras; j++) {
  1.6012 +                    removeDetachedView(scrapPile.remove(size--), false);
  1.6013 +                }
  1.6014 +            }
  1.6015 +
  1.6016 +            if (mTransientStateViews != null) {
  1.6017 +                for (int i = 0; i < mTransientStateViews.size(); i++) {
  1.6018 +                    final View v = mTransientStateViews.valueAt(i);
  1.6019 +                    if (!ViewCompat.hasTransientState(v)) {
  1.6020 +                        mTransientStateViews.removeAt(i);
  1.6021 +                        i--;
  1.6022 +                    }
  1.6023 +                }
  1.6024 +            }
  1.6025 +        }
  1.6026 +
  1.6027 +        void reclaimScrapViews(List<View> views) {
  1.6028 +            if (mViewTypeCount == 1) {
  1.6029 +                views.addAll(mCurrentScrap);
  1.6030 +            } else {
  1.6031 +                final int viewTypeCount = mViewTypeCount;
  1.6032 +                final ArrayList<View>[] scrapViews = mScrapViews;
  1.6033 +
  1.6034 +                for (int i = 0; i < viewTypeCount; ++i) {
  1.6035 +                    final ArrayList<View> scrapPile = scrapViews[i];
  1.6036 +                    views.addAll(scrapPile);
  1.6037 +                }
  1.6038 +            }
  1.6039 +        }
  1.6040 +
  1.6041 +        View retrieveFromScrap(ArrayList<View> scrapViews, int position) {
  1.6042 +            int size = scrapViews.size();
  1.6043 +            if (size <= 0) {
  1.6044 +                return null;
  1.6045 +            }
  1.6046 +
  1.6047 +            for (int i = 0; i < size; i++) {
  1.6048 +                final View scrapView = scrapViews.get(i);
  1.6049 +                final LayoutParams lp = (LayoutParams) scrapView.getLayoutParams();
  1.6050 +
  1.6051 +                if (lp.scrappedFromPosition == position) {
  1.6052 +                    scrapViews.remove(i);
  1.6053 +                    return scrapView;
  1.6054 +                }
  1.6055 +            }
  1.6056 +
  1.6057 +            return scrapViews.remove(size - 1);
  1.6058 +        }
  1.6059 +    }
  1.6060 +
  1.6061 +    @Override
  1.6062 +    public void setEmptyView(View emptyView) {
  1.6063 +        super.setEmptyView(emptyView);
  1.6064 +        mEmptyView = emptyView;
  1.6065 +        updateEmptyStatus();
  1.6066 +    }
  1.6067 +
  1.6068 +    @Override
  1.6069 +    public void setFocusable(boolean focusable) {
  1.6070 +        final ListAdapter adapter = getAdapter();
  1.6071 +        final boolean empty = (adapter == null || adapter.getCount() == 0);
  1.6072 +
  1.6073 +        mDesiredFocusableState = focusable;
  1.6074 +        if (!focusable) {
  1.6075 +            mDesiredFocusableInTouchModeState = false;
  1.6076 +        }
  1.6077 +
  1.6078 +        super.setFocusable(focusable && !empty);
  1.6079 +    }
  1.6080 +
  1.6081 +    @Override
  1.6082 +    public void setFocusableInTouchMode(boolean focusable) {
  1.6083 +        final ListAdapter adapter = getAdapter();
  1.6084 +        final boolean empty = (adapter == null || adapter.getCount() == 0);
  1.6085 +
  1.6086 +        mDesiredFocusableInTouchModeState = focusable;
  1.6087 +        if (focusable) {
  1.6088 +            mDesiredFocusableState = true;
  1.6089 +        }
  1.6090 +
  1.6091 +        super.setFocusableInTouchMode(focusable && !empty);
  1.6092 +    }
  1.6093 +
  1.6094 +    private void checkFocus() {
  1.6095 +        final ListAdapter adapter = getAdapter();
  1.6096 +        final boolean focusable = (adapter != null && adapter.getCount() > 0);
  1.6097 +
  1.6098 +        // The order in which we set focusable in touch mode/focusable may matter
  1.6099 +        // for the client, see View.setFocusableInTouchMode() comments for more
  1.6100 +        // details
  1.6101 +        super.setFocusableInTouchMode(focusable && mDesiredFocusableInTouchModeState);
  1.6102 +        super.setFocusable(focusable && mDesiredFocusableState);
  1.6103 +
  1.6104 +        if (mEmptyView != null) {
  1.6105 +            updateEmptyStatus();
  1.6106 +        }
  1.6107 +    }
  1.6108 +
  1.6109 +    private void updateEmptyStatus() {
  1.6110 +        final boolean isEmpty = (mAdapter == null || mAdapter.isEmpty());
  1.6111 +
  1.6112 +        if (isEmpty) {
  1.6113 +            if (mEmptyView != null) {
  1.6114 +                mEmptyView.setVisibility(View.VISIBLE);
  1.6115 +                setVisibility(View.GONE);
  1.6116 +            } else {
  1.6117 +                // If the caller just removed our empty view, make sure the list
  1.6118 +                // view is visible
  1.6119 +                setVisibility(View.VISIBLE);
  1.6120 +            }
  1.6121 +
  1.6122 +            // We are now GONE, so pending layouts will not be dispatched.
  1.6123 +            // Force one here to make sure that the state of the list matches
  1.6124 +            // the state of the adapter.
  1.6125 +            if (mDataChanged) {
  1.6126 +                onLayout(false, getLeft(), getTop(), getRight(), getBottom());
  1.6127 +            }
  1.6128 +        } else {
  1.6129 +            if (mEmptyView != null) {
  1.6130 +                mEmptyView.setVisibility(View.GONE);
  1.6131 +            }
  1.6132 +
  1.6133 +            setVisibility(View.VISIBLE);
  1.6134 +        }
  1.6135 +    }
  1.6136 +
  1.6137 +    private class AdapterDataSetObserver extends DataSetObserver {
  1.6138 +        private Parcelable mInstanceState = null;
  1.6139 +
  1.6140 +        @Override
  1.6141 +        public void onChanged() {
  1.6142 +            mDataChanged = true;
  1.6143 +            mOldItemCount = mItemCount;
  1.6144 +            mItemCount = getAdapter().getCount();
  1.6145 +
  1.6146 +            // Detect the case where a cursor that was previously invalidated has
  1.6147 +            // been re-populated with new data.
  1.6148 +            if (TwoWayView.this.mHasStableIds && mInstanceState != null
  1.6149 +                    && mOldItemCount == 0 && mItemCount > 0) {
  1.6150 +                TwoWayView.this.onRestoreInstanceState(mInstanceState);
  1.6151 +                mInstanceState = null;
  1.6152 +            } else {
  1.6153 +                rememberSyncState();
  1.6154 +            }
  1.6155 +
  1.6156 +            checkFocus();
  1.6157 +            requestLayout();
  1.6158 +        }
  1.6159 +
  1.6160 +        @Override
  1.6161 +        public void onInvalidated() {
  1.6162 +            mDataChanged = true;
  1.6163 +
  1.6164 +            if (TwoWayView.this.mHasStableIds) {
  1.6165 +                // Remember the current state for the case where our hosting activity is being
  1.6166 +                // stopped and later restarted
  1.6167 +                mInstanceState = TwoWayView.this.onSaveInstanceState();
  1.6168 +            }
  1.6169 +
  1.6170 +            // Data is invalid so we should reset our state
  1.6171 +            mOldItemCount = mItemCount;
  1.6172 +            mItemCount = 0;
  1.6173 +
  1.6174 +            mSelectedPosition = INVALID_POSITION;
  1.6175 +            mSelectedRowId = INVALID_ROW_ID;
  1.6176 +
  1.6177 +            mNextSelectedPosition = INVALID_POSITION;
  1.6178 +            mNextSelectedRowId = INVALID_ROW_ID;
  1.6179 +
  1.6180 +            mNeedSync = false;
  1.6181 +
  1.6182 +            checkFocus();
  1.6183 +            requestLayout();
  1.6184 +        }
  1.6185 +    }
  1.6186 +
  1.6187 +    static class SavedState extends BaseSavedState {
  1.6188 +        long selectedId;
  1.6189 +        long firstId;
  1.6190 +        int viewStart;
  1.6191 +        int position;
  1.6192 +        int height;
  1.6193 +        int checkedItemCount;
  1.6194 +        SparseBooleanArray checkState;
  1.6195 +        LongSparseArray<Integer> checkIdState;
  1.6196 +
  1.6197 +        /**
  1.6198 +         * Constructor called from {@link TwoWayView#onSaveInstanceState()}
  1.6199 +         */
  1.6200 +        SavedState(Parcelable superState) {
  1.6201 +            super(superState);
  1.6202 +        }
  1.6203 +
  1.6204 +        /**
  1.6205 +         * Constructor called from {@link #CREATOR}
  1.6206 +         */
  1.6207 +        private SavedState(Parcel in) {
  1.6208 +            super(in);
  1.6209 +
  1.6210 +            selectedId = in.readLong();
  1.6211 +            firstId = in.readLong();
  1.6212 +            viewStart = in.readInt();
  1.6213 +            position = in.readInt();
  1.6214 +            height = in.readInt();
  1.6215 +
  1.6216 +            checkedItemCount = in.readInt();
  1.6217 +            checkState = in.readSparseBooleanArray();
  1.6218 +
  1.6219 +            final int N = in.readInt();
  1.6220 +            if (N > 0) {
  1.6221 +                checkIdState = new LongSparseArray<Integer>();
  1.6222 +                for (int i = 0; i < N; i++) {
  1.6223 +                    final long key = in.readLong();
  1.6224 +                    final int value = in.readInt();
  1.6225 +                    checkIdState.put(key, value);
  1.6226 +                }
  1.6227 +            }
  1.6228 +        }
  1.6229 +
  1.6230 +        @Override
  1.6231 +        public void writeToParcel(Parcel out, int flags) {
  1.6232 +            super.writeToParcel(out, flags);
  1.6233 +
  1.6234 +            out.writeLong(selectedId);
  1.6235 +            out.writeLong(firstId);
  1.6236 +            out.writeInt(viewStart);
  1.6237 +            out.writeInt(position);
  1.6238 +            out.writeInt(height);
  1.6239 +
  1.6240 +            out.writeInt(checkedItemCount);
  1.6241 +            out.writeSparseBooleanArray(checkState);
  1.6242 +
  1.6243 +            final int N = checkIdState != null ? checkIdState.size() : 0;
  1.6244 +            out.writeInt(N);
  1.6245 +
  1.6246 +            for (int i = 0; i < N; i++) {
  1.6247 +                out.writeLong(checkIdState.keyAt(i));
  1.6248 +                out.writeInt(checkIdState.valueAt(i));
  1.6249 +            }
  1.6250 +        }
  1.6251 +
  1.6252 +        @Override
  1.6253 +        public String toString() {
  1.6254 +            return "TwoWayView.SavedState{"
  1.6255 +                    + Integer.toHexString(System.identityHashCode(this))
  1.6256 +                    + " selectedId=" + selectedId
  1.6257 +                    + " firstId=" + firstId
  1.6258 +                    + " viewStart=" + viewStart
  1.6259 +                    + " height=" + height
  1.6260 +                    + " position=" + position
  1.6261 +                    + " checkState=" + checkState + "}";
  1.6262 +        }
  1.6263 +
  1.6264 +        public static final Parcelable.Creator<SavedState> CREATOR
  1.6265 +                = new Parcelable.Creator<SavedState>() {
  1.6266 +            @Override
  1.6267 +            public SavedState createFromParcel(Parcel in) {
  1.6268 +                return new SavedState(in);
  1.6269 +            }
  1.6270 +
  1.6271 +            @Override
  1.6272 +            public SavedState[] newArray(int size) {
  1.6273 +                return new SavedState[size];
  1.6274 +            }
  1.6275 +        };
  1.6276 +    }
  1.6277 +
  1.6278 +    private class SelectionNotifier implements Runnable {
  1.6279 +        @Override
  1.6280 +        public void run() {
  1.6281 +            if (mDataChanged) {
  1.6282 +                // Data has changed between when this SelectionNotifier
  1.6283 +                // was posted and now. We need to wait until the AdapterView
  1.6284 +                // has been synched to the new data.
  1.6285 +                if (mAdapter != null) {
  1.6286 +                    post(this);
  1.6287 +                }
  1.6288 +            } else {
  1.6289 +                fireOnSelected();
  1.6290 +                performAccessibilityActionsOnSelected();
  1.6291 +            }
  1.6292 +        }
  1.6293 +    }
  1.6294 +
  1.6295 +    private class WindowRunnnable {
  1.6296 +        private int mOriginalAttachCount;
  1.6297 +
  1.6298 +        public void rememberWindowAttachCount() {
  1.6299 +            mOriginalAttachCount = getWindowAttachCount();
  1.6300 +        }
  1.6301 +
  1.6302 +        public boolean sameWindow() {
  1.6303 +            return hasWindowFocus() && getWindowAttachCount() == mOriginalAttachCount;
  1.6304 +        }
  1.6305 +    }
  1.6306 +
  1.6307 +    private class PerformClick extends WindowRunnnable implements Runnable {
  1.6308 +        int mClickMotionPosition;
  1.6309 +
  1.6310 +        @Override
  1.6311 +        public void run() {
  1.6312 +            if (mDataChanged) {
  1.6313 +                return;
  1.6314 +            }
  1.6315 +
  1.6316 +            final ListAdapter adapter = mAdapter;
  1.6317 +            final int motionPosition = mClickMotionPosition;
  1.6318 +
  1.6319 +            if (adapter != null && mItemCount > 0 &&
  1.6320 +                motionPosition != INVALID_POSITION &&
  1.6321 +                motionPosition < adapter.getCount() && sameWindow()) {
  1.6322 +
  1.6323 +                final View child = getChildAt(motionPosition - mFirstPosition);
  1.6324 +                if (child != null) {
  1.6325 +                    performItemClick(child, motionPosition, adapter.getItemId(motionPosition));
  1.6326 +                }
  1.6327 +            }
  1.6328 +        }
  1.6329 +    }
  1.6330 +
  1.6331 +    private final class CheckForTap implements Runnable {
  1.6332 +        @Override
  1.6333 +        public void run() {
  1.6334 +            if (mTouchMode != TOUCH_MODE_DOWN) {
  1.6335 +                return;
  1.6336 +            }
  1.6337 +
  1.6338 +            mTouchMode = TOUCH_MODE_TAP;
  1.6339 +
  1.6340 +            final View child = getChildAt(mMotionPosition - mFirstPosition);
  1.6341 +            if (child != null && !child.hasFocusable()) {
  1.6342 +                mLayoutMode = LAYOUT_NORMAL;
  1.6343 +
  1.6344 +                if (!mDataChanged) {
  1.6345 +                    setPressed(true);
  1.6346 +                    child.setPressed(true);
  1.6347 +
  1.6348 +                    layoutChildren();
  1.6349 +                    positionSelector(mMotionPosition, child);
  1.6350 +                    refreshDrawableState();
  1.6351 +
  1.6352 +                    positionSelector(mMotionPosition, child);
  1.6353 +                    refreshDrawableState();
  1.6354 +
  1.6355 +                    final boolean longClickable = isLongClickable();
  1.6356 +
  1.6357 +                    if (mSelector != null) {
  1.6358 +                        Drawable d = mSelector.getCurrent();
  1.6359 +
  1.6360 +                        if (d != null && d instanceof TransitionDrawable) {
  1.6361 +                            if (longClickable) {
  1.6362 +                                final int longPressTimeout = ViewConfiguration.getLongPressTimeout();
  1.6363 +                                ((TransitionDrawable) d).startTransition(longPressTimeout);
  1.6364 +                            } else {
  1.6365 +                                ((TransitionDrawable) d).resetTransition();
  1.6366 +                            }
  1.6367 +                        }
  1.6368 +                    }
  1.6369 +
  1.6370 +                    if (longClickable) {
  1.6371 +                        triggerCheckForLongPress();
  1.6372 +                    } else {
  1.6373 +                        mTouchMode = TOUCH_MODE_DONE_WAITING;
  1.6374 +                    }
  1.6375 +                } else {
  1.6376 +                    mTouchMode = TOUCH_MODE_DONE_WAITING;
  1.6377 +                }
  1.6378 +            }
  1.6379 +        }
  1.6380 +    }
  1.6381 +
  1.6382 +    private class CheckForLongPress extends WindowRunnnable implements Runnable {
  1.6383 +        @Override
  1.6384 +        public void run() {
  1.6385 +            final int motionPosition = mMotionPosition;
  1.6386 +            final View child = getChildAt(motionPosition - mFirstPosition);
  1.6387 +
  1.6388 +            if (child != null) {
  1.6389 +                final long longPressId = mAdapter.getItemId(mMotionPosition);
  1.6390 +
  1.6391 +                boolean handled = false;
  1.6392 +                if (sameWindow() && !mDataChanged) {
  1.6393 +                    handled = performLongPress(child, motionPosition, longPressId);
  1.6394 +                }
  1.6395 +
  1.6396 +                if (handled) {
  1.6397 +                    mTouchMode = TOUCH_MODE_REST;
  1.6398 +                    setPressed(false);
  1.6399 +                    child.setPressed(false);
  1.6400 +                } else {
  1.6401 +                    mTouchMode = TOUCH_MODE_DONE_WAITING;
  1.6402 +                }
  1.6403 +            }
  1.6404 +        }
  1.6405 +    }
  1.6406 +
  1.6407 +    private class CheckForKeyLongPress extends WindowRunnnable implements Runnable {
  1.6408 +        public void run() {
  1.6409 +            if (!isPressed() || mSelectedPosition < 0) {
  1.6410 +                return;
  1.6411 +            }
  1.6412 +
  1.6413 +            final int index = mSelectedPosition - mFirstPosition;
  1.6414 +            final View v = getChildAt(index);
  1.6415 +
  1.6416 +            if (!mDataChanged) {
  1.6417 +                boolean handled = false;
  1.6418 +
  1.6419 +                if (sameWindow()) {
  1.6420 +                    handled = performLongPress(v, mSelectedPosition, mSelectedRowId);
  1.6421 +                }
  1.6422 +
  1.6423 +                if (handled) {
  1.6424 +                    setPressed(false);
  1.6425 +                    v.setPressed(false);
  1.6426 +                }
  1.6427 +            } else {
  1.6428 +                setPressed(false);
  1.6429 +
  1.6430 +                if (v != null) {
  1.6431 +                    v.setPressed(false);
  1.6432 +                }
  1.6433 +            }
  1.6434 +        }
  1.6435 +    }
  1.6436 +
  1.6437 +    private static class ArrowScrollFocusResult {
  1.6438 +        private int mSelectedPosition;
  1.6439 +        private int mAmountToScroll;
  1.6440 +
  1.6441 +        /**
  1.6442 +         * How {@link TwoWayView#arrowScrollFocused} returns its values.
  1.6443 +         */
  1.6444 +        void populate(int selectedPosition, int amountToScroll) {
  1.6445 +            mSelectedPosition = selectedPosition;
  1.6446 +            mAmountToScroll = amountToScroll;
  1.6447 +        }
  1.6448 +
  1.6449 +        public int getSelectedPosition() {
  1.6450 +            return mSelectedPosition;
  1.6451 +        }
  1.6452 +
  1.6453 +        public int getAmountToScroll() {
  1.6454 +            return mAmountToScroll;
  1.6455 +        }
  1.6456 +    }
  1.6457 +
  1.6458 +    private class ListItemAccessibilityDelegate extends AccessibilityDelegateCompat {
  1.6459 +        @Override
  1.6460 +        public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
  1.6461 +            super.onInitializeAccessibilityNodeInfo(host, info);
  1.6462 +
  1.6463 +            final int position = getPositionForView(host);
  1.6464 +            final ListAdapter adapter = getAdapter();
  1.6465 +
  1.6466 +            // Cannot perform actions on invalid items
  1.6467 +            if (position == INVALID_POSITION || adapter == null) {
  1.6468 +                return;
  1.6469 +            }
  1.6470 +
  1.6471 +            // Cannot perform actions on disabled items
  1.6472 +            if (!isEnabled() || !adapter.isEnabled(position)) {
  1.6473 +                return;
  1.6474 +            }
  1.6475 +
  1.6476 +            if (position == getSelectedItemPosition()) {
  1.6477 +                info.setSelected(true);
  1.6478 +                info.addAction(AccessibilityNodeInfoCompat.ACTION_CLEAR_SELECTION);
  1.6479 +            } else {
  1.6480 +                info.addAction(AccessibilityNodeInfoCompat.ACTION_SELECT);
  1.6481 +            }
  1.6482 +
  1.6483 +            if (isClickable()) {
  1.6484 +                info.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
  1.6485 +                info.setClickable(true);
  1.6486 +            }
  1.6487 +
  1.6488 +            if (isLongClickable()) {
  1.6489 +                info.addAction(AccessibilityNodeInfoCompat.ACTION_LONG_CLICK);
  1.6490 +                info.setLongClickable(true);
  1.6491 +            }
  1.6492 +        }
  1.6493 +
  1.6494 +        @Override
  1.6495 +        public boolean performAccessibilityAction(View host, int action, Bundle arguments) {
  1.6496 +            if (super.performAccessibilityAction(host, action, arguments)) {
  1.6497 +                return true;
  1.6498 +            }
  1.6499 +
  1.6500 +            final int position = getPositionForView(host);
  1.6501 +            final ListAdapter adapter = getAdapter();
  1.6502 +
  1.6503 +            // Cannot perform actions on invalid items
  1.6504 +            if (position == INVALID_POSITION || adapter == null) {
  1.6505 +                return false;
  1.6506 +            }
  1.6507 +
  1.6508 +            // Cannot perform actions on disabled items
  1.6509 +            if (!isEnabled() || !adapter.isEnabled(position)) {
  1.6510 +                return false;
  1.6511 +            }
  1.6512 +
  1.6513 +            final long id = getItemIdAtPosition(position);
  1.6514 +
  1.6515 +            switch (action) {
  1.6516 +            case AccessibilityNodeInfoCompat.ACTION_CLEAR_SELECTION:
  1.6517 +                if (getSelectedItemPosition() == position) {
  1.6518 +                    setSelection(INVALID_POSITION);
  1.6519 +                    return true;
  1.6520 +                }
  1.6521 +                return false;
  1.6522 +
  1.6523 +            case AccessibilityNodeInfoCompat.ACTION_SELECT:
  1.6524 +                if (getSelectedItemPosition() != position) {
  1.6525 +                    setSelection(position);
  1.6526 +                    return true;
  1.6527 +                }
  1.6528 +                return false;
  1.6529 +
  1.6530 +            case AccessibilityNodeInfoCompat.ACTION_CLICK:
  1.6531 +                if (isClickable()) {
  1.6532 +                    return performItemClick(host, position, id);
  1.6533 +                }
  1.6534 +                return false;
  1.6535 +
  1.6536 +            case AccessibilityNodeInfoCompat.ACTION_LONG_CLICK:
  1.6537 +                if (isLongClickable()) {
  1.6538 +                    return performLongPress(host, position, id);
  1.6539 +                }
  1.6540 +                return false;
  1.6541 +            }
  1.6542 +
  1.6543 +            return false;
  1.6544 +        }
  1.6545 +    }
  1.6546 +}

mercurial