AbsListView.java revision 499cb9f516062b654952d282f211bee44c31a3c2
14f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project/*
24f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project * Copyright (C) 2006 The Android Open Source Project
34f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project *
44f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project * Licensed under the Apache License, Version 2.0 (the "License");
54f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project * you may not use this file except in compliance with the License.
64f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project * You may obtain a copy of the License at
74f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project *
84f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project *      http://www.apache.org/licenses/LICENSE-2.0
94f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project *
104f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project * Unless required by applicable law or agreed to in writing, software
114f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project * distributed under the License is distributed on an "AS IS" BASIS,
124f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
134f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project * See the License for the specific language governing permissions and
144f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project * limitations under the License.
154f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project */
164f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
174f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Projectpackage android.widget;
184f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
194f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Projectimport com.android.internal.R;
204f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
214f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Projectimport android.content.Context;
224f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Projectimport android.content.Intent;
234f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Projectimport android.content.res.TypedArray;
244f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Projectimport android.graphics.Canvas;
254f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Projectimport android.graphics.Rect;
264f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Projectimport android.graphics.drawable.Drawable;
274f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Projectimport android.graphics.drawable.TransitionDrawable;
282bef93cc20155c3a59cdbb22c564c4b385b2c160Bruce Beareimport android.os.Debug;
2996dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewellimport android.os.Handler;
309b6c850d24df82451862b81f059361b586f5ef0bJean-Baptiste Queruimport android.os.Parcel;
314f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Projectimport android.os.Parcelable;
324f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Projectimport android.text.Editable;
334f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Projectimport android.text.TextUtils;
344f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Projectimport android.text.TextWatcher;
354f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Projectimport android.util.AttributeSet;
364f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Projectimport android.util.Log;
374f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Projectimport android.view.Gravity;
384f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Projectimport android.view.HapticFeedbackConstants;
394f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Projectimport android.view.KeyEvent;
404f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Projectimport android.view.LayoutInflater;
414f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Projectimport android.view.MotionEvent;
424f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Projectimport android.view.VelocityTracker;
434f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Projectimport android.view.View;
444f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Projectimport android.view.ViewConfiguration;
454f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Projectimport android.view.ViewDebug;
464f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Projectimport android.view.ViewGroup;
474f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Projectimport android.view.ViewTreeObserver;
484f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Projectimport android.view.ContextMenu.ContextMenuInfo;
494f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Projectimport android.view.inputmethod.BaseInputConnection;
504f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Projectimport android.view.inputmethod.EditorInfo;
514f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Projectimport android.view.inputmethod.InputConnection;
524f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Projectimport android.view.inputmethod.InputConnectionWrapper;
534f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Projectimport android.view.inputmethod.InputMethodManager;
544f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
554f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Projectimport java.util.ArrayList;
564f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Projectimport java.util.List;
574f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
584f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project/**
594f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project * Base class that can be used to implement virtualized lists of items. A list does
604f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project * not have a spatial definition here. For instance, subclases of this class can
614f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project * display the content of the list in a grid, in a carousel, as stack, etc.
624f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project *
634f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project * @attr ref android.R.styleable#AbsListView_listSelector
644f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop
654f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project * @attr ref android.R.styleable#AbsListView_stackFromBottom
664f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project * @attr ref android.R.styleable#AbsListView_scrollingCache
674f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project * @attr ref android.R.styleable#AbsListView_textFilterEnabled
684f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project * @attr ref android.R.styleable#AbsListView_transcriptMode
694f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project * @attr ref android.R.styleable#AbsListView_cacheColorHint
704f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project * @attr ref android.R.styleable#AbsListView_fastScrollEnabled
714f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project * @attr ref android.R.styleable#AbsListView_smoothScrollbar
724f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project */
734f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Projectpublic abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher,
744f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener,
754f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        ViewTreeObserver.OnTouchModeChangeListener,
764f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        RemoteViewsAdapter.RemoteAdapterConnectionCallback {
774f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
784f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
794f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Disables the transcript mode.
804f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     *
814f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * @see #setTranscriptMode(int)
824f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
834f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    public static final int TRANSCRIPT_MODE_DISABLED = 0;
844f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
854f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * The list will automatically scroll to the bottom when a data set change
864f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * notification is received and only if the last item is already visible
874f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * on screen.
884f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     *
894f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * @see #setTranscriptMode(int)
904f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
914f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    public static final int TRANSCRIPT_MODE_NORMAL = 1;
924f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
934f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * The list will automatically scroll to the bottom, no matter what items
944f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * are currently visible.
954f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     *
964f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * @see #setTranscriptMode(int)
974f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
984f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    public static final int TRANSCRIPT_MODE_ALWAYS_SCROLL = 2;
994f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
1004f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
1014f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Indicates that we are not in the middle of a touch gesture
1024f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
1034f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    static final int TOUCH_MODE_REST = -1;
1044f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
1054f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
1064f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Indicates we just received the touch event and we are waiting to see if the it is a tap or a
1074f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * scroll gesture.
1084f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
1094f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    static final int TOUCH_MODE_DOWN = 0;
1104f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
1114f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
1124f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Indicates the touch has been recognized as a tap and we are now waiting to see if the touch
1134f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * is a longpress
1144f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
1154f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    static final int TOUCH_MODE_TAP = 1;
1164f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
1174f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
1184f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Indicates we have waited for everything we can wait for, but the user's finger is still down
1194f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
1204f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    static final int TOUCH_MODE_DONE_WAITING = 2;
1214f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
1224f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
1234f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Indicates the touch gesture is a scroll
1244f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
1254f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    static final int TOUCH_MODE_SCROLL = 3;
1264f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
1274f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
1284f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Indicates the view is in the process of being flung
1294f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
1304f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    static final int TOUCH_MODE_FLING = 4;
1314f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
1324f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
1334f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Regular layout - usually an unsolicited layout from the view system
1344f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
1354f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    static final int LAYOUT_NORMAL = 0;
1364f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
1374f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
1384f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Show the first item
1394f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
1404f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    static final int LAYOUT_FORCE_TOP = 1;
1414f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
1424f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
1434f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Force the selected item to be on somewhere on the screen
1444f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
1454f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    static final int LAYOUT_SET_SELECTION = 2;
1464f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
1474f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
1484f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Show the last item
1494f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
1504f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    static final int LAYOUT_FORCE_BOTTOM = 3;
1514f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
1524f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
1534f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Make a mSelectedItem appear in a specific location and build the rest of
1544f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * the views from there. The top is specified by mSpecificTop.
1554f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
1564f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    static final int LAYOUT_SPECIFIC = 4;
1574f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
1584f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
1594f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Layout to sync as a result of a data change. Restore mSyncPosition to have its top
1604f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * at mSpecificTop
1614f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
1624f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    static final int LAYOUT_SYNC = 5;
1634f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
1644f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
1654f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Layout as a result of using the navigation keys
1664f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
1674f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    static final int LAYOUT_MOVE_SELECTION = 6;
1684f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
1694f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
1704f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Controls how the next layout will happen
1714f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
1724f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    int mLayoutMode = LAYOUT_NORMAL;
1734f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
1744f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
1754f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Should be used by subclasses to listen to changes in the dataset
1764f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
1774f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    AdapterDataSetObserver mDataSetObserver;
1784f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
1794f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
1804f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * The adapter containing the data to be displayed by this view
1814f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
1824f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    ListAdapter mAdapter;
1834f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
1844f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
1854f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * The remote adapter containing the data to be displayed by this view to be set
1864f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
1874f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    private RemoteViewsAdapter mRemoteAdapter;
1884f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
1894f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
1904f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Indicates whether the list selector should be drawn on top of the children or behind
1914f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
1924f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    boolean mDrawSelectorOnTop = false;
1934f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
1944f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
1954f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * The drawable used to draw the selector
1964f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
1974f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    Drawable mSelector;
1984f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
1994f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
2004f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Defines the selector's location and dimension at drawing time
2014f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
2024f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    Rect mSelectorRect = new Rect();
2034f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
2044f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
2054f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * The data set used to store unused views that should be reused during the next layout
2064f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * to avoid creating new ones
2074f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
2084f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    final RecycleBin mRecycler = new RecycleBin();
2094f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
2104f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
2114f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * The selection's left padding
2124f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
2134f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    int mSelectionLeftPadding = 0;
2144f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
2154f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
2164f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * The selection's top padding
2174f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
2184f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    int mSelectionTopPadding = 0;
2194f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
2204f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
2214f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * The selection's right padding
2224f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
2234f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    int mSelectionRightPadding = 0;
2244f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
2254f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
2264f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * The selection's bottom padding
2274f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
2284f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    int mSelectionBottomPadding = 0;
2294f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
2304f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
2314f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * This view's padding
2324f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
2334f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    Rect mListPadding = new Rect();
2344f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
2354f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
2364f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Subclasses must retain their measure spec from onMeasure() into this member
2374f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
2384f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    int mWidthMeasureSpec = 0;
2394f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
2404f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
2414f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * The top scroll indicator
2424f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
2434f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    View mScrollUp;
2444f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
2454f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
2464f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * The down scroll indicator
2474f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
2484f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    View mScrollDown;
2494f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
2504f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
2514f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * When the view is scrolling, this flag is set to true to indicate subclasses that
2524f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * the drawing cache was enabled on the children
2534f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
2544f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    boolean mCachingStarted;
2554f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
2564f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
2574f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * The position of the view that received the down motion event
2584f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
2594f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    int mMotionPosition;
2604f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
2614f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
2624f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * The offset to the top of the mMotionPosition view when the down motion event was received
2634f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
2644f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    int mMotionViewOriginalTop;
2654f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
2664f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
2674f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * The desired offset to the top of the mMotionPosition view after a scroll
2684f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
2694f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    int mMotionViewNewTop;
2704f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
2714f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
2724f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * The X value associated with the the down motion event
2734f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
2744f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    int mMotionX;
2754f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
2764f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
2774f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * The Y value associated with the the down motion event
2784f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
2794f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    int mMotionY;
2804f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
2814f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
2824f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * One of TOUCH_MODE_REST, TOUCH_MODE_DOWN, TOUCH_MODE_TAP, TOUCH_MODE_SCROLL, or
2834f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * TOUCH_MODE_DONE_WAITING
2844f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
2854f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    int mTouchMode = TOUCH_MODE_REST;
2864f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
2874f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
2884f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Y value from on the previous motion event (if any)
2894f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
2904f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    int mLastY;
2914f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
2924f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
2934f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * How far the finger moved before we started scrolling
2944f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
2954f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    int mMotionCorrection;
2964f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
2974f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
2984f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Determines speed during touch scrolling
2994f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
3004f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    private VelocityTracker mVelocityTracker;
3014f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
3024f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
3034f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Handles one frame of a fling
3044f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
3054f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    private FlingRunnable mFlingRunnable;
3064f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
3074f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
3084f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Handles scrolling between positions within the list.
3094f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
3104f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    private PositionScroller mPositionScroller;
3114f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
3124f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
3134f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * The offset in pixels form the top of the AdapterView to the top
3144f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * of the currently selected view. Used to save and restore state.
3154f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
3164f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    int mSelectedTop = 0;
3174f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
3184f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
3194f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Indicates whether the list is stacked from the bottom edge or
3204f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * the top edge.
3214f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
3224f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    boolean mStackFromBottom;
3234f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
3244f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
3254f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * When set to true, the list automatically discards the children's
3264f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * bitmap cache after scrolling.
3274f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
3284f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    boolean mScrollingCacheEnabled;
3294f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
3304f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
3314f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Whether or not to enable the fast scroll feature on this list
3324f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
3334f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    boolean mFastScrollEnabled;
3344f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
3354f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
3364f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Optional callback to notify client when scroll position has changed
3374f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
3384f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    private OnScrollListener mOnScrollListener;
3394f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
3404f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
3414f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Keeps track of our accessory window
3424f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
3434f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    PopupWindow mPopup;
3444f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
3454f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
3464f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Used with type filter window
3474f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
3484f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    EditText mTextFilter;
3494f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
3504f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
3514f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Indicates whether to use pixels-based or position-based scrollbar
3524f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * properties.
3534f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
3544f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    private boolean mSmoothScrollbarEnabled = true;
3554f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
3564f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
3574f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Indicates that this view supports filtering
3584f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
3594f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    private boolean mTextFilterEnabled;
3604f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
3614f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
3624f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Indicates that this view is currently displaying a filtered view of the data
3634f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
3644f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    private boolean mFiltered;
3654f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
3664f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
3674f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Rectangle used for hit testing children
3684f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
3694f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    private Rect mTouchFrame;
3704f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
3714f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
3724f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * The position to resurrect the selected position to.
3734f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
3744f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    int mResurrectToPosition = INVALID_POSITION;
3754f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
3764f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    private ContextMenuInfo mContextMenuInfo = null;
3774f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
3784f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
3794f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Used to request a layout when we changed touch mode
3804f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
3814f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    private static final int TOUCH_MODE_UNKNOWN = -1;
3824f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    private static final int TOUCH_MODE_ON = 0;
3834f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    private static final int TOUCH_MODE_OFF = 1;
3844f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
3854f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    private int mLastTouchMode = TOUCH_MODE_UNKNOWN;
3864f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
3874f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    private static final boolean PROFILE_SCROLLING = false;
3884f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    private boolean mScrollProfilingStarted = false;
3894f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
3904f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    private static final boolean PROFILE_FLINGING = false;
3914f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    private boolean mFlingProfilingStarted = false;
3924f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
3934f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
3944f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * The last CheckForLongPress runnable we posted, if any
3954f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
3964f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    private CheckForLongPress mPendingCheckForLongPress;
3974f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
3984f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
3994f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * The last CheckForTap runnable we posted, if any
4004f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
4014f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    private Runnable mPendingCheckForTap;
4024f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
4034f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
4044f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * The last CheckForKeyLongPress runnable we posted, if any
4054f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
4064f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    private CheckForKeyLongPress mPendingCheckForKeyLongPress;
4074f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
4084f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
4094f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Acts upon click
4104f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
4114f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    private AbsListView.PerformClick mPerformClick;
4124f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
4134f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
4144f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * This view is in transcript mode -- it shows the bottom of the list when the data
4154f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * changes
4164f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
4174f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    private int mTranscriptMode;
4184f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
4194f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
4204f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Indicates that this list is always drawn on top of a solid, single-color, opaque
4214f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * background
4224f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
4234f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    private int mCacheColorHint;
4244f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
4254f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
4264f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * The select child's view (from the adapter's getView) is enabled.
4274f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
4284f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    private boolean mIsChildViewEnabled;
4294f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
4304f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
4314f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * The last scroll state reported to clients through {@link OnScrollListener}.
4324f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
4334f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    private int mLastScrollState = OnScrollListener.SCROLL_STATE_IDLE;
4344f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
4354f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
4364f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Helper object that renders and controls the fast scroll thumb.
4374f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
4384f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    private FastScroller mFastScroller;
4394f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
4404f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    private boolean mGlobalLayoutListenerAddedFilter;
4414f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
4424f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    private int mTouchSlop;
4434f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    private float mDensityScale;
4444f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
4454f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    private InputConnection mDefInputConnection;
4464f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    private InputConnectionWrapper mPublicInputConnection;
4474f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
4484f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    private Runnable mClearScrollingCache;
4494f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    private int mMinimumVelocity;
4504f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    private int mMaximumVelocity;
4514f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
4524f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    final boolean[] mIsScrap = new boolean[1];
4534f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
4544f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    // True when the popup should be hidden because of a call to
4554f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    // dispatchDisplayHint()
4564f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    private boolean mPopupHidden;
4574f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
4584f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
4594f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * ID of the active pointer. This is used to retain consistency during
4604f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * drags/flings if multiple pointers are used.
4614f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
4624f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    private int mActivePointerId = INVALID_POINTER;
4634f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
4644f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
4654f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Sentinel value for no current active pointer.
4664f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Used by {@link #mActivePointerId}.
4672bc2b792782b304b15d8c48b54916a9b3fa3a7acPaul Lind     */
4682bc2b792782b304b15d8c48b54916a9b3fa3a7acPaul Lind    private static final int INVALID_POINTER = -1;
4692bc2b792782b304b15d8c48b54916a9b3fa3a7acPaul Lind
4704f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
4714f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Interface definition for a callback to be invoked when the list or grid
4724f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * has been scrolled.
4734f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
4744f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    public interface OnScrollListener {
4754f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
4764f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        /**
4774f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project         * The view is not scrolling. Note navigating the list using the trackball counts as
4782bc2b792782b304b15d8c48b54916a9b3fa3a7acPaul Lind         * being in the idle state since these transitions are not animated.
4792bc2b792782b304b15d8c48b54916a9b3fa3a7acPaul Lind         */
4802bc2b792782b304b15d8c48b54916a9b3fa3a7acPaul Lind        public static int SCROLL_STATE_IDLE = 0;
4814f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
4824f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        /**
4834f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project         * The user is scrolling using touch, and their finger is still on the screen
4844f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project         */
4854f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        public static int SCROLL_STATE_TOUCH_SCROLL = 1;
4864f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
4874f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        /**
4884f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project         * The user had previously been scrolling using touch and had performed a fling. The
4894f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project         * animation is now coasting to a stop
4904f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project         */
4914f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        public static int SCROLL_STATE_FLING = 2;
4924f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
4934f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        /**
4944f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project         * Callback method to be invoked while the list view or grid view is being scrolled. If the
4954f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project         * view is being scrolled, this method will be called before the next frame of the scroll is
4964f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project         * rendered. In particular, it will be called before any calls to
4974f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project         * {@link Adapter#getView(int, View, ViewGroup)}.
4984f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project         *
4994f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project         * @param view The view whose scroll state is being reported
5004f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project         *
5014f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project         * @param scrollState The current scroll state. One of {@link #SCROLL_STATE_IDLE},
5024f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project         * {@link #SCROLL_STATE_TOUCH_SCROLL} or {@link #SCROLL_STATE_IDLE}.
5034f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project         */
5044f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        public void onScrollStateChanged(AbsListView view, int scrollState);
5054f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
5064f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        /**
5074f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project         * Callback method to be invoked when the list or grid has been scrolled. This will be
5084f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project         * called after the scroll has completed
5094f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project         * @param view The view whose scroll state is being reported
5104f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project         * @param firstVisibleItem the index of the first visible cell (ignore if
5114f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project         *        visibleItemCount == 0)
5124f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project         * @param visibleItemCount the number of visible cells
5134f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project         * @param totalItemCount the number of items in the list adaptor
5144f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project         */
5154f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
5164f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                int totalItemCount);
5174f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    }
5184f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
5192bc2b792782b304b15d8c48b54916a9b3fa3a7acPaul Lind    public AbsListView(Context context) {
5202bc2b792782b304b15d8c48b54916a9b3fa3a7acPaul Lind        super(context);
5212bc2b792782b304b15d8c48b54916a9b3fa3a7acPaul Lind        initAbsListView();
5224f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
5234f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        setVerticalScrollBarEnabled(true);
5244f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        TypedArray a = context.obtainStyledAttributes(R.styleable.View);
5254f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        initializeScrollbars(a);
5264f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        a.recycle();
5274f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    }
5284f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
5294f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    public AbsListView(Context context, AttributeSet attrs) {
5304f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        this(context, attrs, com.android.internal.R.attr.absListViewStyle);
5314f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    }
5324f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
5334f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    public AbsListView(Context context, AttributeSet attrs, int defStyle) {
5344f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        super(context, attrs, defStyle);
5354f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        initAbsListView();
5364f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
5374f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        TypedArray a = context.obtainStyledAttributes(attrs,
5384f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                com.android.internal.R.styleable.AbsListView, defStyle, 0);
5394f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
5404f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        Drawable d = a.getDrawable(com.android.internal.R.styleable.AbsListView_listSelector);
5414f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        if (d != null) {
5424f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            setSelector(d);
5434f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        }
5444f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
5454f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        mDrawSelectorOnTop = a.getBoolean(
5464f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                com.android.internal.R.styleable.AbsListView_drawSelectorOnTop, false);
5474f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
5484f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        boolean stackFromBottom = a.getBoolean(R.styleable.AbsListView_stackFromBottom, false);
5494f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        setStackFromBottom(stackFromBottom);
5504f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
5514f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        boolean scrollingCacheEnabled = a.getBoolean(R.styleable.AbsListView_scrollingCache, true);
5524f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        setScrollingCacheEnabled(scrollingCacheEnabled);
5534f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
5544f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        boolean useTextFilter = a.getBoolean(R.styleable.AbsListView_textFilterEnabled, false);
5554f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        setTextFilterEnabled(useTextFilter);
5564f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
5574f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        int transcriptMode = a.getInt(R.styleable.AbsListView_transcriptMode,
5584f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                TRANSCRIPT_MODE_DISABLED);
5594f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        setTranscriptMode(transcriptMode);
5604f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
5614f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        int color = a.getColor(R.styleable.AbsListView_cacheColorHint, 0);
5624f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        setCacheColorHint(color);
5634f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
5644f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        boolean enableFastScroll = a.getBoolean(R.styleable.AbsListView_fastScrollEnabled, false);
5654f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        setFastScrollEnabled(enableFastScroll);
5664f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
5674f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        boolean smoothScrollbar = a.getBoolean(R.styleable.AbsListView_smoothScrollbar, true);
5684f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        setSmoothScrollbarEnabled(smoothScrollbar);
5694f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
5704f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        final int adapterId = a.getResourceId(R.styleable.AbsListView_adapter, 0);
5714f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        if (adapterId != 0) {
5724f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            final Context c = context;
5734f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            post(new Runnable() {
5744f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                public void run() {
5754f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                    setAdapter(Adapters.loadAdapter(c, adapterId));
5764f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                }
5774f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            });
5784f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        }
5794f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
5804f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        a.recycle();
5814f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    }
58296dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell
5834f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    private void initAbsListView() {
5844f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        // Setting focusable in touch mode will set the focusable property to true
5854f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        setClickable(true);
5864f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        setFocusableInTouchMode(true);
5874f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        setWillNotDraw(false);
5884f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        setAlwaysDrawnWithCacheEnabled(false);
5894f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        setScrollingCacheEnabled(true);
5904f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
5914f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        final ViewConfiguration configuration = ViewConfiguration.get(mContext);
5924f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        mTouchSlop = configuration.getScaledTouchSlop();
5934f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
5944f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
5954f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        mDensityScale = getContext().getResources().getDisplayMetrics().density;
5964f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    }
5974f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
5984f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
5994f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Enables fast scrolling by letting the user quickly scroll through lists by
6004f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * dragging the fast scroll thumb. The adapter attached to the list may want
6014f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * to implement {@link SectionIndexer} if it wishes to display alphabet preview and
6024f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * jump between sections of the list.
6034f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * @see SectionIndexer
6044f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * @see #isFastScrollEnabled()
6052bc2b792782b304b15d8c48b54916a9b3fa3a7acPaul Lind     * @param enabled whether or not to enable fast scrolling
6062bc2b792782b304b15d8c48b54916a9b3fa3a7acPaul Lind     */
6072bc2b792782b304b15d8c48b54916a9b3fa3a7acPaul Lind    public void setFastScrollEnabled(boolean enabled) {
6082bc2b792782b304b15d8c48b54916a9b3fa3a7acPaul Lind        mFastScrollEnabled = enabled;
6094f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        if (enabled) {
6104f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            if (mFastScroller == null) {
6114f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                mFastScroller = new FastScroller(getContext(), this);
6124f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            }
6134f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        } else {
6144f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            if (mFastScroller != null) {
6154f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                mFastScroller.stop();
6164f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                mFastScroller = null;
6174f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            }
6184f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        }
6194f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    }
6204f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
6214f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
6224f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Returns the current state of the fast scroll feature.
6234f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * @see #setFastScrollEnabled(boolean)
6244f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * @return true if fast scroll is enabled, false otherwise
6254f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
6264f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    @ViewDebug.ExportedProperty
6272bc2b792782b304b15d8c48b54916a9b3fa3a7acPaul Lind    public boolean isFastScrollEnabled() {
6282bc2b792782b304b15d8c48b54916a9b3fa3a7acPaul Lind        return mFastScrollEnabled;
6292bc2b792782b304b15d8c48b54916a9b3fa3a7acPaul Lind    }
6302bc2b792782b304b15d8c48b54916a9b3fa3a7acPaul Lind
6314f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
6324f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * If fast scroll is visible, then don't draw the vertical scrollbar.
6334f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * @hide
6344f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
6354f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    @Override
6364f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    protected boolean isVerticalScrollBarHidden() {
6374f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        return mFastScroller != null && mFastScroller.isVisible();
6384f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    }
6394f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
6404f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
6414f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * When smooth scrollbar is enabled, the position and size of the scrollbar thumb
6424f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * is computed based on the number of visible pixels in the visible items. This
6434f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * however assumes that all list items have the same height. If you use a list in
6444f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * which items have different heights, the scrollbar will change appearance as the
6454f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * user scrolls through the list. To avoid this issue, you need to disable this
6464f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * property.
6474f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     *
6484f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * When smooth scrollbar is disabled, the position and size of the scrollbar thumb
6494f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * is based solely on the number of items in the adapter and the position of the
6504f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * visible items inside the adapter. This provides a stable scrollbar as the user
6514f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * navigates through a list of items with varying heights.
6524f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     *
6534f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * @param enabled Whether or not to enable smooth scrollbar.
6544f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     *
6554f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * @see #setSmoothScrollbarEnabled(boolean)
6564f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * @attr ref android.R.styleable#AbsListView_smoothScrollbar
6574f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
6584f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    public void setSmoothScrollbarEnabled(boolean enabled) {
6594f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        mSmoothScrollbarEnabled = enabled;
6604f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    }
6614f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
6624f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
6634f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Returns the current state of the fast scroll feature.
6644f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     *
6654f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * @return True if smooth scrollbar is enabled is enabled, false otherwise.
6664f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     *
6674f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * @see #setSmoothScrollbarEnabled(boolean)
6684f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
6694f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    @ViewDebug.ExportedProperty
6704f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    public boolean isSmoothScrollbarEnabled() {
6714f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        return mSmoothScrollbarEnabled;
6724f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    }
6734f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
6744f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
6754f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Set the listener that will receive notifications every time the list scrolls.
6764f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     *
6774f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * @param l the scroll listener
6784f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
6794f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    public void setOnScrollListener(OnScrollListener l) {
6804f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        mOnScrollListener = l;
6814f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        invokeOnItemScrollListener();
6824f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    }
6834f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
6844f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
6854f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Notify our scroll listener (if there is one) of a change in scroll state
6864f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
6874f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    void invokeOnItemScrollListener() {
6884f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        if (mFastScroller != null) {
6894f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            mFastScroller.onScroll(this, mFirstPosition, getChildCount(), mItemCount);
6904f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        }
6914f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        if (mOnScrollListener != null) {
6924f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            mOnScrollListener.onScroll(this, mFirstPosition, getChildCount(), mItemCount);
6934f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        }
6944f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    }
6954f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
6964f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
6974f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Indicates whether the children's drawing cache is used during a scroll.
6984f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * By default, the drawing cache is enabled but this will consume more memory.
6994f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     *
7004f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * @return true if the scrolling cache is enabled, false otherwise
7014f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     *
7024f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * @see #setScrollingCacheEnabled(boolean)
7034f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * @see View#setDrawingCacheEnabled(boolean)
7044f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
7054f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    @ViewDebug.ExportedProperty
7064f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    public boolean isScrollingCacheEnabled() {
7074f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        return mScrollingCacheEnabled;
7084f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    }
7094f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
7104f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
7114f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Enables or disables the children's drawing cache during a scroll.
7124f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * By default, the drawing cache is enabled but this will use more memory.
7134f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     *
7144f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * When the scrolling cache is enabled, the caches are kept after the
7154f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * first scrolling. You can manually clear the cache by calling
7164f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * {@link android.view.ViewGroup#setChildrenDrawingCacheEnabled(boolean)}.
7174f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     *
7184f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * @param enabled true to enable the scroll cache, false otherwise
7194f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     *
7204f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * @see #isScrollingCacheEnabled()
7214f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * @see View#setDrawingCacheEnabled(boolean)
7224f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
7234f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    public void setScrollingCacheEnabled(boolean enabled) {
7244f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        if (mScrollingCacheEnabled && !enabled) {
7254f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            clearScrollingCache();
7264f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        }
7274f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        mScrollingCacheEnabled = enabled;
7284f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    }
7294f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
7304f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
7314f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Enables or disables the type filter window. If enabled, typing when
7324f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * this view has focus will filter the children to match the users input.
7334f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Note that the {@link Adapter} used by this view must implement the
7344f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * {@link Filterable} interface.
7354f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     *
7364f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * @param textFilterEnabled true to enable type filtering, false otherwise
7374f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     *
7384f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * @see Filterable
7394f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
7404f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    public void setTextFilterEnabled(boolean textFilterEnabled) {
7414f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        mTextFilterEnabled = textFilterEnabled;
7424f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    }
7434f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
7444f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
7454f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Indicates whether type filtering is enabled for this view
7464f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     *
7474f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * @return true if type filtering is enabled, false otherwise
7484f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     *
7494f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * @see #setTextFilterEnabled(boolean)
7504f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * @see Filterable
7514f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
7524f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    @ViewDebug.ExportedProperty
7534f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    public boolean isTextFilterEnabled() {
7544f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        return mTextFilterEnabled;
7554f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    }
7564f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
7574f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    @Override
7584f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    public void getFocusedRect(Rect r) {
7594f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        View view = getSelectedView();
7604f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        if (view != null && view.getParent() == this) {
7614f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            // the focused rectangle of the selected view offset into the
7624f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            // coordinate space of this view.
7634f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            view.getFocusedRect(r);
7644f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            offsetDescendantRectToMyCoords(view, r);
7654f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        } else {
7664f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            // otherwise, just the norm
7674f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            super.getFocusedRect(r);
7684f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        }
7694f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    }
7704f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
7714f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    private void useDefaultSelector() {
7724f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        setSelector(getResources().getDrawable(
7734f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                com.android.internal.R.drawable.list_selector_background));
7744f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    }
7754f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
7764f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
7774f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Indicates whether the content of this view is pinned to, or stacked from,
7784f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * the bottom edge.
7794f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     *
7804f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * @return true if the content is stacked from the bottom edge, false otherwise
7814f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
7824f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    @ViewDebug.ExportedProperty
7834f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    public boolean isStackFromBottom() {
7844f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        return mStackFromBottom;
7854f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    }
7864f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
7874f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
7884f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * When stack from bottom is set to true, the list fills its content starting from
7894f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * the bottom of the view.
7904f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     *
7914f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * @param stackFromBottom true to pin the view's content to the bottom edge,
7924f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     *        false to pin the view's content to the top edge
7934f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
7944f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    public void setStackFromBottom(boolean stackFromBottom) {
7954f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        if (mStackFromBottom != stackFromBottom) {
7964f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            mStackFromBottom = stackFromBottom;
7974f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            requestLayoutIfNecessary();
79801dda204cd28fe181691b4a44a51be7e5666d0c8Steve Block        }
7994f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    }
8004f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
8014f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    void requestLayoutIfNecessary() {
8024f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        if (getChildCount() > 0) {
8034f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            resetList();
8044f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            requestLayout();
8054f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            invalidate();
8064f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        }
8074f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    }
8084f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
8094f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    static class SavedState extends BaseSavedState {
8104f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        long selectedId;
8114f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        long firstId;
8124f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        int viewTop;
8134f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        int position;
8144f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        int height;
8154f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        String filter;
8164f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
8174f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        /**
8184f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project         * Constructor called from {@link AbsListView#onSaveInstanceState()}
8194f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project         */
8204f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        SavedState(Parcelable superState) {
8214f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            super(superState);
8224f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        }
8234f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
8244f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        /**
8254f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project         * Constructor called from {@link #CREATOR}
8264f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project         */
8274f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        private SavedState(Parcel in) {
8284f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            super(in);
8294f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            selectedId = in.readLong();
8304f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            firstId = in.readLong();
8314f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            viewTop = in.readInt();
8324f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            position = in.readInt();
8334f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            height = in.readInt();
8344f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            filter = in.readString();
8354f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        }
8364f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
8374f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        @Override
8384f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        public void writeToParcel(Parcel out, int flags) {
8394f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            super.writeToParcel(out, flags);
8404f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            out.writeLong(selectedId);
8414f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            out.writeLong(firstId);
8424f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            out.writeInt(viewTop);
8434f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            out.writeInt(position);
8444f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            out.writeInt(height);
8454f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            out.writeString(filter);
8464f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        }
8474f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
8484f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        @Override
8494f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        public String toString() {
8504f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            return "AbsListView.SavedState{"
8514f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                    + Integer.toHexString(System.identityHashCode(this))
8524f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                    + " selectedId=" + selectedId
8534f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                    + " firstId=" + firstId
8544f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                    + " viewTop=" + viewTop
8554f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                    + " position=" + position
8564f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                    + " height=" + height
8574f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                    + " filter=" + filter + "}";
8584f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        }
8594f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
8604f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        public static final Parcelable.Creator<SavedState> CREATOR
8614f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                = new Parcelable.Creator<SavedState>() {
8624f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            public SavedState createFromParcel(Parcel in) {
8634f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                return new SavedState(in);
8644f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            }
8654f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
8664f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            public SavedState[] newArray(int size) {
8674f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                return new SavedState[size];
8684f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            }
8694f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        };
8704f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    }
8714f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
8724f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    @Override
8734f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    public Parcelable onSaveInstanceState() {
8744f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        /*
8754f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project         * This doesn't really make sense as the place to dismiss the
8764f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project         * popups, but there don't seem to be any other useful hooks
8774f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project         * that happen early enough to keep from getting complaints
8784f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project         * about having leaked the window.
8794f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project         */
8804f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        dismissPopup();
8814f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
8824f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        Parcelable superState = super.onSaveInstanceState();
8834f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
8844f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        SavedState ss = new SavedState(superState);
8854f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
8864f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        boolean haveChildren = getChildCount() > 0;
8874f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        long selectedId = getSelectedItemId();
8884f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        ss.selectedId = selectedId;
8894f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        ss.height = getHeight();
8904f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
89196dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell        if (selectedId >= 0) {
89296dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell            // Remember the selection
89396dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell            ss.viewTop = mSelectedTop;
89496dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell            ss.position = getSelectedItemPosition();
89596dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell            ss.firstId = INVALID_POSITION;
89696dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell        } else {
89796dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell            if (haveChildren) {
89896dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell                // Remember the position of the first child
89996dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell                View v = getChildAt(0);
90096dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell                ss.viewTop = v.getTop();
90196dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell                ss.position = mFirstPosition;
90296dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell                ss.firstId = mAdapter.getItemId(mFirstPosition);
90396dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell            } else {
90496dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell                ss.viewTop = 0;
90596dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell                ss.firstId = INVALID_POSITION;
90696dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell                ss.position = 0;
90796dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell            }
90896dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell        }
90996dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell
91096dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell        ss.filter = null;
91196dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell        if (mFiltered) {
91296dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell            final EditText textFilter = mTextFilter;
91396dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell            if (textFilter != null) {
91496dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell                Editable filterText = textFilter.getText();
91596dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell                if (filterText != null) {
91696dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell                    ss.filter = filterText.toString();
91796dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell                }
91896dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell            }
91996dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell        }
92096dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell
92196dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell        return ss;
92296dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell    }
92396dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell
92496dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell    @Override
92596dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell    public void onRestoreInstanceState(Parcelable state) {
92696dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell        SavedState ss = (SavedState) state;
92796dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell
92896dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell        super.onRestoreInstanceState(ss.getSuperState());
92996dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell        mDataChanged = true;
93096dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell
93196dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell        mSyncHeight = ss.height;
93296dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell
93396dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell        if (ss.selectedId >= 0) {
93496dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell            mNeedSync = true;
93596dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell            mSyncRowId = ss.selectedId;
93696dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell            mSyncPosition = ss.position;
93796dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell            mSpecificTop = ss.viewTop;
93896dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell            mSyncMode = SYNC_SELECTED_POSITION;
93996dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell        } else if (ss.firstId >= 0) {
94096dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell            setSelectedPositionInt(INVALID_POSITION);
94196dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell            // Do this before setting mNeedSync since setNextSelectedPosition looks at mNeedSync
94296dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell            setNextSelectedPositionInt(INVALID_POSITION);
94396dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell            mNeedSync = true;
94496dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell            mSyncRowId = ss.firstId;
94596dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell            mSyncPosition = ss.position;
94696dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell            mSpecificTop = ss.viewTop;
94796dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell            mSyncMode = SYNC_FIRST_POSITION;
94896dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell        }
94996dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell
95096dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell        setFilterText(ss.filter);
95196dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell
95296dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell        requestLayout();
95396dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell    }
95496dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell
95596dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell    private boolean acceptFilter() {
95696dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell        return mTextFilterEnabled && getAdapter() instanceof Filterable &&
95796dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell                ((Filterable) getAdapter()).getFilter() != null;
95896dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell    }
95996dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell
96096dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell    /**
96196dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell     * Sets the initial value for the text filter.
96296dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell     * @param filterText The text to use for the filter.
96396dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell     *
96496dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell     * @see #setTextFilterEnabled
96596dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell     */
96696dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell    public void setFilterText(String filterText) {
96796dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell        // TODO: Should we check for acceptFilter()?
96896dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell        if (mTextFilterEnabled && !TextUtils.isEmpty(filterText)) {
96996dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell            createTextFilter(false);
97096dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell            // This is going to call our listener onTextChanged, but we might not
97196dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell            // be ready to bring up a window yet
97296dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell            mTextFilter.setText(filterText);
97396dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell            mTextFilter.setSelection(filterText.length());
97496dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell            if (mAdapter instanceof Filterable) {
97596dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell                // if mPopup is non-null, then onTextChanged will do the filtering
97696dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell                if (mPopup == null) {
97796dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell                    Filter f = ((Filterable) mAdapter).getFilter();
97896dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell                    f.filter(filterText);
97996dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell                }
98096dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell                // Set filtered to true so we will display the filter window when our main
98196dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell                // window is ready
98296dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell                mFiltered = true;
98396dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell                mDataSetObserver.clearSavedState();
98496dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell            }
98596dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell        }
98696dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell    }
98796dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell
98896dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell    /**
98996dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell     * Returns the list's text filter, if available.
99096dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell     * @return the list's text filter or null if filtering isn't enabled
9914f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
9924f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    public CharSequence getTextFilter() {
9934f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        if (mTextFilterEnabled && mTextFilter != null) {
9944f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            return mTextFilter.getText();
9954f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        }
9964f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        return null;
9974f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    }
9984f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
9994f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    @Override
10004f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
10014f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
10024f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        if (gainFocus && mSelectedPosition < 0 && !isInTouchMode()) {
10034f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            resurrectSelection();
10044f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        }
10054f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    }
10064f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
10074f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    @Override
10084f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    public void requestLayout() {
10094f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        if (!mBlockLayoutRequests && !mInLayout) {
10104f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            super.requestLayout();
10114f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        }
10124f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    }
10134f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
10144f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
10154f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * The list is empty. Clear everything out.
10164f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
10174f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    void resetList() {
10184f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        removeAllViewsInLayout();
10194f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        mFirstPosition = 0;
10204f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        mDataChanged = false;
10214f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        mNeedSync = false;
10224f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        mOldSelectedPosition = INVALID_POSITION;
10234f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        mOldSelectedRowId = INVALID_ROW_ID;
10244f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        setSelectedPositionInt(INVALID_POSITION);
10254f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        setNextSelectedPositionInt(INVALID_POSITION);
10264f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        mSelectedTop = 0;
10274f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        mSelectorRect.setEmpty();
10284f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        invalidate();
10294f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    }
10304f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
10314f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    @Override
10324f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    protected int computeVerticalScrollExtent() {
10334f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        final int count = getChildCount();
10344f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        if (count > 0) {
10354f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            if (mSmoothScrollbarEnabled) {
10364f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                int extent = count * 100;
10374f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
10384f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                View view = getChildAt(0);
10394f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                final int top = view.getTop();
10404f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                int height = view.getHeight();
10414f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                if (height > 0) {
10424f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                    extent += (top * 100) / height;
10434f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                }
10444f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
10454f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                view = getChildAt(count - 1);
10464f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                final int bottom = view.getBottom();
10474f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                height = view.getHeight();
10484f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                if (height > 0) {
10494f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                    extent -= ((bottom - getHeight()) * 100) / height;
10504f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                }
10514f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
10524f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                return extent;
10534f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            } else {
10544f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                return 1;
10554f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            }
10564f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        }
10574f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        return 0;
10584f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    }
10594f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
10604f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    @Override
10614f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    protected int computeVerticalScrollOffset() {
10624f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        final int firstPosition = mFirstPosition;
10634f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        final int childCount = getChildCount();
10644f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        if (firstPosition >= 0 && childCount > 0) {
10654f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            if (mSmoothScrollbarEnabled) {
10664f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                final View view = getChildAt(0);
10674f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                final int top = view.getTop();
10684f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                int height = view.getHeight();
10694f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                if (height > 0) {
10704f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                    return Math.max(firstPosition * 100 - (top * 100) / height +
10714f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                            (int)((float)mScrollY / getHeight() * mItemCount * 100), 0);
10724f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                }
10734f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            } else {
10744f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                int index;
10754f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                final int count = mItemCount;
10764f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                if (firstPosition == 0) {
10774f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                    index = 0;
107896dbb4fc58fe2dcf4390e073dbb42cc77ef2f0b5Martyn Capewell                } else if (firstPosition + childCount == count) {
10794f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                    index = count;
10804f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                } else {
10814f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                    index = firstPosition + childCount / 2;
10824f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                }
10834f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                return (int) (firstPosition + childCount * (index / (float) count));
10844f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            }
10854f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        }
10864f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        return 0;
10874f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    }
10884f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
10894f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    @Override
10904f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    protected int computeVerticalScrollRange() {
10914f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        int result;
10924f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        if (mSmoothScrollbarEnabled) {
10934f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            result = Math.max(mItemCount * 100, 0);
10944f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        } else {
10954f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            result = mItemCount;
10964f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        }
10974f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        return result;
10982bc2b792782b304b15d8c48b54916a9b3fa3a7acPaul Lind    }
10994f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
11004f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    @Override
11014f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    protected float getTopFadingEdgeStrength() {
11024f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        final int count = getChildCount();
11034f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        final float fadeEdge = super.getTopFadingEdgeStrength();
11044f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        if (count == 0) {
11054f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            return fadeEdge;
11064f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        } else {
11074f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            if (mFirstPosition > 0) {
11084f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                return 1.0f;
11094f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            }
11104f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
11114f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            final int top = getChildAt(0).getTop();
11124f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            final float fadeLength = (float) getVerticalFadingEdgeLength();
11134f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            return top < mPaddingTop ? (float) -(top - mPaddingTop) / fadeLength : fadeEdge;
11144f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        }
11154f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    }
11164f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
11174f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    @Override
11184f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    protected float getBottomFadingEdgeStrength() {
11194f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        final int count = getChildCount();
11204f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        final float fadeEdge = super.getBottomFadingEdgeStrength();
11214f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        if (count == 0) {
11224f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            return fadeEdge;
11234f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        } else {
11244f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            if (mFirstPosition + count - 1 < mItemCount - 1) {
112535237d135807af84bf9b0e5b8d7f8633e58db6f5The Android Open Source Project                return 1.0f;
112635237d135807af84bf9b0e5b8d7f8633e58db6f5The Android Open Source Project            }
112735237d135807af84bf9b0e5b8d7f8633e58db6f5The Android Open Source Project
11284f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            final int bottom = getChildAt(count - 1).getBottom();
11294f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            final int height = getHeight();
11304f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            final float fadeLength = (float) getVerticalFadingEdgeLength();
11314f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            return bottom > height - mPaddingBottom ?
11324f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                    (float) (bottom - height + mPaddingBottom) / fadeLength : fadeEdge;
11334f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        }
11344f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    }
11354f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
11364f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    @Override
11374f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
11384f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        if (mSelector == null) {
11394f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            useDefaultSelector();
11404f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        }
11414f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        final Rect listPadding = mListPadding;
11424f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        listPadding.left = mSelectionLeftPadding + mPaddingLeft;
11434f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        listPadding.top = mSelectionTopPadding + mPaddingTop;
11444f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        listPadding.right = mSelectionRightPadding + mPaddingRight;
11454f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        listPadding.bottom = mSelectionBottomPadding + mPaddingBottom;
11464f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    }
11474f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
11484f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
11494f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Subclasses should NOT override this method but
11504f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     *  {@link #layoutChildren()} instead.
11514f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
11524f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    @Override
11534f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    protected void onLayout(boolean changed, int l, int t, int r, int b) {
11544f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        super.onLayout(changed, l, t, r, b);
11554f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        mInLayout = true;
11564f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        if (changed) {
11574f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            int childCount = getChildCount();
11584f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            for (int i = 0; i < childCount; i++) {
11594f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                getChildAt(i).forceLayout();
11604f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            }
11614f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            mRecycler.markChildrenDirty();
11624f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        }
11634f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
11644f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        layoutChildren();
11654f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        mInLayout = false;
11664f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    }
11674f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
11684f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
11694f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * @hide
11704f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
11714f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    @Override
11724f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    protected boolean setFrame(int left, int top, int right, int bottom) {
11734f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        final boolean changed = super.setFrame(left, top, right, bottom);
11744f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
11754f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        if (changed) {
11764f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            // Reposition the popup when the frame has changed. This includes
11774f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            // translating the widget, not just changing its dimension. The
11784f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            // filter popup needs to follow the widget.
11794f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            final boolean visible = getWindowVisibility() == View.VISIBLE;
11804f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            if (mFiltered && visible && mPopup != null && mPopup.isShowing()) {
11814f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                positionPopup();
11824f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            }
11834f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        }
11844f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
11854f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        return changed;
11864f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    }
11874f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
11884f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
11894f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Subclasses must override this method to layout their children.
11904f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
11914f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    protected void layoutChildren() {
11924f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    }
11934f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
11944f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    void updateScrollIndicators() {
11954f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        if (mScrollUp != null) {
11964f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            boolean canScrollUp;
11974f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            // 0th element is not visible
11984f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            canScrollUp = mFirstPosition > 0;
11994f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
12004f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            // ... Or top of 0th element is not visible
12014f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            if (!canScrollUp) {
12024f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                if (getChildCount() > 0) {
12034f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                    View child = getChildAt(0);
12044f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                    canScrollUp = child.getTop() < mListPadding.top;
12054f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                }
12064f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            }
12074f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
12084f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            mScrollUp.setVisibility(canScrollUp ? View.VISIBLE : View.INVISIBLE);
12094f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        }
12104f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
12114f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        if (mScrollDown != null) {
12124f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            boolean canScrollDown;
12134f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            int count = getChildCount();
12144f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
12154f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            // Last item is not visible
12164f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            canScrollDown = (mFirstPosition + count) < mItemCount;
12174f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
12184f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            // ... Or bottom of the last element is not visible
12194f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            if (!canScrollDown && count > 0) {
12204f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                View child = getChildAt(count - 1);
12214f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                canScrollDown = child.getBottom() > mBottom - mListPadding.bottom;
12224f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            }
12234f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
12244f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            mScrollDown.setVisibility(canScrollDown ? View.VISIBLE : View.INVISIBLE);
12254f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        }
12264f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    }
12274f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
12284f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    @Override
12294f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    @ViewDebug.ExportedProperty
12304f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    public View getSelectedView() {
12314f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        if (mItemCount > 0 && mSelectedPosition >= 0) {
12324f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            return getChildAt(mSelectedPosition - mFirstPosition);
12334f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        } else {
12344f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            return null;
12354f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        }
12364f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    }
12374f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
12384f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
12394f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * List padding is the maximum of the normal view's padding and the padding of the selector.
12404f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     *
12414f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * @see android.view.View#getPaddingTop()
12424f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * @see #getSelector()
12434f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     *
12444f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * @return The top list padding.
12454f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
12464f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    public int getListPaddingTop() {
12474f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        return mListPadding.top;
12484f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    }
12494f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
12504f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
12514f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * List padding is the maximum of the normal view's padding and the padding of the selector.
12524f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     *
12534f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * @see android.view.View#getPaddingBottom()
12544f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * @see #getSelector()
12554f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     *
12564f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * @return The bottom list padding.
12574f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
12584f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    public int getListPaddingBottom() {
12594f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        return mListPadding.bottom;
12604f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    }
12614f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
12624f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
12634f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * List padding is the maximum of the normal view's padding and the padding of the selector.
12644f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     *
12654f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * @see android.view.View#getPaddingLeft()
12664f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * @see #getSelector()
12674f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     *
12684f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * @return The left list padding.
12694f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
12704f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    public int getListPaddingLeft() {
12714f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        return mListPadding.left;
12724f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    }
12734f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
12744f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
12754f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * List padding is the maximum of the normal view's padding and the padding of the selector.
12764f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     *
12774f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * @see android.view.View#getPaddingRight()
12784f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * @see #getSelector()
12794f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     *
12804f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * @return The right list padding.
12814f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
12824f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    public int getListPaddingRight() {
12834f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        return mListPadding.right;
12844f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    }
12854f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
12864f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    /**
12874f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * Get a view and have it show the data associated with the specified
12884f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * position. This is called when we have already discovered that the view is
12894f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * not available for reuse in the recycle bin. The only choices left are
12904f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * converting an old view or making a new one.
12914f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     *
12924f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * @param position The position to display
12934f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * @param isScrap Array of at least 1 boolean, the first entry will become true if
12944f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     *                the returned view was taken from the scrap heap, false if otherwise.
12954f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     *
12964f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     * @return A view displaying the data associated with the specified position
12974f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project     */
12984f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project    View obtainView(int position, boolean[] isScrap) {
12994f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        isScrap[0] = false;
13004f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        View scrapView;
13014f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
13024f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        scrapView = mRecycler.getScrapView(position);
13034f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
13044f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        View child;
13054f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project        if (scrapView != null) {
13064f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            if (ViewDebug.TRACE_RECYCLER) {
13074f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                ViewDebug.trace(scrapView, ViewDebug.RecyclerTraceType.RECYCLE_FROM_SCRAP_HEAP,
13084f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                        position, -1);
13094f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            }
13104f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
13114f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            child = mAdapter.getView(position, scrapView, this);
13124f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
13134f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            if (ViewDebug.TRACE_RECYCLER) {
13144f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                ViewDebug.trace(child, ViewDebug.RecyclerTraceType.BIND_VIEW,
13154f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                        position, getChildCount());
13164f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            }
13174f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project
13184f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            if (child != scrapView) {
13194f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                mRecycler.addScrapView(scrapView);
13204f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                if (mCacheColorHint != 0) {
13214f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                    child.setDrawingCacheBackgroundColor(mCacheColorHint);
13224f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                }
13234f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                if (ViewDebug.TRACE_RECYCLER) {
13244f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                    ViewDebug.trace(scrapView, ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP,
13254f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                            position, -1);
13264f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                }
13274f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            } else {
13284f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                isScrap[0] = true;
13294f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                child.dispatchFinishTemporaryDetach();
133035237d135807af84bf9b0e5b8d7f8633e58db6f5The Android Open Source Project            }
133135237d135807af84bf9b0e5b8d7f8633e58db6f5The Android Open Source Project        } else {
133235237d135807af84bf9b0e5b8d7f8633e58db6f5The Android Open Source Project            child = mAdapter.getView(position, null, this);
133335237d135807af84bf9b0e5b8d7f8633e58db6f5The Android Open Source Project            if (mCacheColorHint != 0) {
133435237d135807af84bf9b0e5b8d7f8633e58db6f5The Android Open Source Project                child.setDrawingCacheBackgroundColor(mCacheColorHint);
133535237d135807af84bf9b0e5b8d7f8633e58db6f5The Android Open Source Project            }
133635237d135807af84bf9b0e5b8d7f8633e58db6f5The Android Open Source Project            if (ViewDebug.TRACE_RECYCLER) {
133735237d135807af84bf9b0e5b8d7f8633e58db6f5The Android Open Source Project                ViewDebug.trace(child, ViewDebug.RecyclerTraceType.NEW_VIEW,
133835237d135807af84bf9b0e5b8d7f8633e58db6f5The Android Open Source Project                        position, getChildCount());
133935237d135807af84bf9b0e5b8d7f8633e58db6f5The Android Open Source Project            }
134035237d135807af84bf9b0e5b8d7f8633e58db6f5The Android Open Source Project        }
134135237d135807af84bf9b0e5b8d7f8633e58db6f5The Android Open Source Project
134235237d135807af84bf9b0e5b8d7f8633e58db6f5The Android Open Source Project        return child;
134335237d135807af84bf9b0e5b8d7f8633e58db6f5The Android Open Source Project    }
134435237d135807af84bf9b0e5b8d7f8633e58db6f5The Android Open Source Project
134535237d135807af84bf9b0e5b8d7f8633e58db6f5The Android Open Source Project    void positionSelector(View sel) {
134635237d135807af84bf9b0e5b8d7f8633e58db6f5The Android Open Source Project        final Rect selectorRect = mSelectorRect;
134735237d135807af84bf9b0e5b8d7f8633e58db6f5The Android Open Source Project        selectorRect.set(sel.getLeft(), sel.getTop(), sel.getRight(), sel.getBottom());
134835237d135807af84bf9b0e5b8d7f8633e58db6f5The Android Open Source Project        positionSelector(selectorRect.left, selectorRect.top, selectorRect.right,
134935237d135807af84bf9b0e5b8d7f8633e58db6f5The Android Open Source Project                selectorRect.bottom);
135035237d135807af84bf9b0e5b8d7f8633e58db6f5The Android Open Source Project
135135237d135807af84bf9b0e5b8d7f8633e58db6f5The Android Open Source Project        final boolean isChildViewEnabled = mIsChildViewEnabled;
135235237d135807af84bf9b0e5b8d7f8633e58db6f5The Android Open Source Project        if (sel.isEnabled() != isChildViewEnabled) {
135335237d135807af84bf9b0e5b8d7f8633e58db6f5The Android Open Source Project            mIsChildViewEnabled = !isChildViewEnabled;
135435237d135807af84bf9b0e5b8d7f8633e58db6f5The Android Open Source Project            refreshDrawableState();
135535237d135807af84bf9b0e5b8d7f8633e58db6f5The Android Open Source Project        }
135635237d135807af84bf9b0e5b8d7f8633e58db6f5The Android Open Source Project    }
135735237d135807af84bf9b0e5b8d7f8633e58db6f5The Android Open Source Project
135835237d135807af84bf9b0e5b8d7f8633e58db6f5The Android Open Source Project    private void positionSelector(int l, int t, int r, int b) {
135935237d135807af84bf9b0e5b8d7f8633e58db6f5The Android Open Source Project        mSelectorRect.set(l - mSelectionLeftPadding, t - mSelectionTopPadding, r
136035237d135807af84bf9b0e5b8d7f8633e58db6f5The Android Open Source Project                + mSelectionRightPadding, b + mSelectionBottomPadding);
136135237d135807af84bf9b0e5b8d7f8633e58db6f5The Android Open Source Project    }
136235237d135807af84bf9b0e5b8d7f8633e58db6f5The Android Open Source Project
136335237d135807af84bf9b0e5b8d7f8633e58db6f5The Android Open Source Project    @Override
136435237d135807af84bf9b0e5b8d7f8633e58db6f5The Android Open Source Project    protected void dispatchDraw(Canvas canvas) {
136535237d135807af84bf9b0e5b8d7f8633e58db6f5The Android Open Source Project        int saveCount = 0;
136635237d135807af84bf9b0e5b8d7f8633e58db6f5The Android Open Source Project        final boolean clipToPadding = (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
136735237d135807af84bf9b0e5b8d7f8633e58db6f5The Android Open Source Project        if (clipToPadding) {
136835237d135807af84bf9b0e5b8d7f8633e58db6f5The Android Open Source Project            saveCount = canvas.save();
136935237d135807af84bf9b0e5b8d7f8633e58db6f5The Android Open Source Project            final int scrollX = mScrollX;
13704f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            final int scrollY = mScrollY;
13714f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project            canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,
13724f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                    scrollX + mRight - mLeft - mPaddingRight,
13734f6e8d7a00cbeda1e70cc15be9c4af1018bdad5The Android Open Source Project                    scrollY + mBottom - mTop - mPaddingBottom);
1374            mGroupFlags &= ~CLIP_TO_PADDING_MASK;
1375        }
1376
1377        final boolean drawSelectorOnTop = mDrawSelectorOnTop;
1378        if (!drawSelectorOnTop) {
1379            drawSelector(canvas);
1380        }
1381
1382        super.dispatchDraw(canvas);
1383
1384        if (drawSelectorOnTop) {
1385            drawSelector(canvas);
1386        }
1387
1388        if (clipToPadding) {
1389            canvas.restoreToCount(saveCount);
1390            mGroupFlags |= CLIP_TO_PADDING_MASK;
1391        }
1392    }
1393
1394    @Override
1395    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
1396        if (getChildCount() > 0) {
1397            mDataChanged = true;
1398            rememberSyncState();
1399        }
1400
1401        if (mFastScroller != null) {
1402            mFastScroller.onSizeChanged(w, h, oldw, oldh);
1403        }
1404    }
1405
1406    /**
1407     * @return True if the current touch mode requires that we draw the selector in the pressed
1408     *         state.
1409     */
1410    boolean touchModeDrawsInPressedState() {
1411        // FIXME use isPressed for this
1412        switch (mTouchMode) {
1413        case TOUCH_MODE_TAP:
1414        case TOUCH_MODE_DONE_WAITING:
1415            return true;
1416        default:
1417            return false;
1418        }
1419    }
1420
1421    /**
1422     * Indicates whether this view is in a state where the selector should be drawn. This will
1423     * happen if we have focus but are not in touch mode, or we are in the middle of displaying
1424     * the pressed state for an item.
1425     *
1426     * @return True if the selector should be shown
1427     */
1428    boolean shouldShowSelector() {
1429        return (hasFocus() && !isInTouchMode()) || touchModeDrawsInPressedState();
1430    }
1431
1432    private void drawSelector(Canvas canvas) {
1433        if (shouldShowSelector() && mSelectorRect != null && !mSelectorRect.isEmpty()) {
1434            final Drawable selector = mSelector;
1435            selector.setBounds(mSelectorRect);
1436            selector.draw(canvas);
1437        }
1438    }
1439
1440    /**
1441     * Controls whether the selection highlight drawable should be drawn on top of the item or
1442     * behind it.
1443     *
1444     * @param onTop If true, the selector will be drawn on the item it is highlighting. The default
1445     *        is false.
1446     *
1447     * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop
1448     */
1449    public void setDrawSelectorOnTop(boolean onTop) {
1450        mDrawSelectorOnTop = onTop;
1451    }
1452
1453    /**
1454     * Set a Drawable that should be used to highlight the currently selected item.
1455     *
1456     * @param resID A Drawable resource to use as the selection highlight.
1457     *
1458     * @attr ref android.R.styleable#AbsListView_listSelector
1459     */
1460    public void setSelector(int resID) {
1461        setSelector(getResources().getDrawable(resID));
1462    }
1463
1464    public void setSelector(Drawable sel) {
1465        if (mSelector != null) {
1466            mSelector.setCallback(null);
1467            unscheduleDrawable(mSelector);
1468        }
1469        mSelector = sel;
1470        Rect padding = new Rect();
1471        sel.getPadding(padding);
1472        mSelectionLeftPadding = padding.left;
1473        mSelectionTopPadding = padding.top;
1474        mSelectionRightPadding = padding.right;
1475        mSelectionBottomPadding = padding.bottom;
1476        sel.setCallback(this);
1477        sel.setState(getDrawableState());
1478    }
1479
1480    /**
1481     * Returns the selector {@link android.graphics.drawable.Drawable} that is used to draw the
1482     * selection in the list.
1483     *
1484     * @return the drawable used to display the selector
1485     */
1486    public Drawable getSelector() {
1487        return mSelector;
1488    }
1489
1490    /**
1491     * Sets the selector state to "pressed" and posts a CheckForKeyLongPress to see if
1492     * this is a long press.
1493     */
1494    void keyPressed() {
1495        if (!isEnabled() || !isClickable()) {
1496            return;
1497        }
1498
1499        Drawable selector = mSelector;
1500        Rect selectorRect = mSelectorRect;
1501        if (selector != null && (isFocused() || touchModeDrawsInPressedState())
1502                && selectorRect != null && !selectorRect.isEmpty()) {
1503
1504            final View v = getChildAt(mSelectedPosition - mFirstPosition);
1505
1506            if (v != null) {
1507                if (v.hasFocusable()) return;
1508                v.setPressed(true);
1509            }
1510            setPressed(true);
1511
1512            final boolean longClickable = isLongClickable();
1513            Drawable d = selector.getCurrent();
1514            if (d != null && d instanceof TransitionDrawable) {
1515                if (longClickable) {
1516                    ((TransitionDrawable) d).startTransition(
1517                            ViewConfiguration.getLongPressTimeout());
1518                } else {
1519                    ((TransitionDrawable) d).resetTransition();
1520                }
1521            }
1522            if (longClickable && !mDataChanged) {
1523                if (mPendingCheckForKeyLongPress == null) {
1524                    mPendingCheckForKeyLongPress = new CheckForKeyLongPress();
1525                }
1526                mPendingCheckForKeyLongPress.rememberWindowAttachCount();
1527                postDelayed(mPendingCheckForKeyLongPress, ViewConfiguration.getLongPressTimeout());
1528            }
1529        }
1530    }
1531
1532    public void setScrollIndicators(View up, View down) {
1533        mScrollUp = up;
1534        mScrollDown = down;
1535    }
1536
1537    @Override
1538    protected void drawableStateChanged() {
1539        super.drawableStateChanged();
1540        if (mSelector != null) {
1541            mSelector.setState(getDrawableState());
1542        }
1543    }
1544
1545    @Override
1546    protected int[] onCreateDrawableState(int extraSpace) {
1547        // If the child view is enabled then do the default behavior.
1548        if (mIsChildViewEnabled) {
1549            // Common case
1550            return super.onCreateDrawableState(extraSpace);
1551        }
1552
1553        // The selector uses this View's drawable state. The selected child view
1554        // is disabled, so we need to remove the enabled state from the drawable
1555        // states.
1556        final int enabledState = ENABLED_STATE_SET[0];
1557
1558        // If we don't have any extra space, it will return one of the static state arrays,
1559        // and clearing the enabled state on those arrays is a bad thing!  If we specify
1560        // we need extra space, it will create+copy into a new array that safely mutable.
1561        int[] state = super.onCreateDrawableState(extraSpace + 1);
1562        int enabledPos = -1;
1563        for (int i = state.length - 1; i >= 0; i--) {
1564            if (state[i] == enabledState) {
1565                enabledPos = i;
1566                break;
1567            }
1568        }
1569
1570        // Remove the enabled state
1571        if (enabledPos >= 0) {
1572            System.arraycopy(state, enabledPos + 1, state, enabledPos,
1573                    state.length - enabledPos - 1);
1574        }
1575
1576        return state;
1577    }
1578
1579    @Override
1580    public boolean verifyDrawable(Drawable dr) {
1581        return mSelector == dr || super.verifyDrawable(dr);
1582    }
1583
1584    @Override
1585    protected void onAttachedToWindow() {
1586        super.onAttachedToWindow();
1587
1588        final ViewTreeObserver treeObserver = getViewTreeObserver();
1589        if (treeObserver != null) {
1590            treeObserver.addOnTouchModeChangeListener(this);
1591            if (mTextFilterEnabled && mPopup != null && !mGlobalLayoutListenerAddedFilter) {
1592                treeObserver.addOnGlobalLayoutListener(this);
1593            }
1594        }
1595
1596        if (mAdapter != null && mDataSetObserver == null) {
1597            mDataSetObserver = new AdapterDataSetObserver();
1598            mAdapter.registerDataSetObserver(mDataSetObserver);
1599        }
1600    }
1601
1602    @Override
1603    protected void onDetachedFromWindow() {
1604        super.onDetachedFromWindow();
1605
1606        // Dismiss the popup in case onSaveInstanceState() was not invoked
1607        dismissPopup();
1608
1609        // Detach any view left in the scrap heap
1610        mRecycler.clear();
1611
1612        final ViewTreeObserver treeObserver = getViewTreeObserver();
1613        if (treeObserver != null) {
1614            treeObserver.removeOnTouchModeChangeListener(this);
1615            if (mTextFilterEnabled && mPopup != null) {
1616                treeObserver.removeGlobalOnLayoutListener(this);
1617                mGlobalLayoutListenerAddedFilter = false;
1618            }
1619        }
1620
1621        if (mAdapter != null) {
1622            mAdapter.unregisterDataSetObserver(mDataSetObserver);
1623            mDataSetObserver = null;
1624        }
1625    }
1626
1627    @Override
1628    public void onWindowFocusChanged(boolean hasWindowFocus) {
1629        super.onWindowFocusChanged(hasWindowFocus);
1630
1631        final int touchMode = isInTouchMode() ? TOUCH_MODE_ON : TOUCH_MODE_OFF;
1632
1633        if (!hasWindowFocus) {
1634            setChildrenDrawingCacheEnabled(false);
1635            if (mFlingRunnable != null) {
1636                removeCallbacks(mFlingRunnable);
1637                // let the fling runnable report it's new state which
1638                // should be idle
1639                mFlingRunnable.endFling();
1640                if (mScrollY != 0) {
1641                    mScrollY = 0;
1642                    invalidate();
1643                }
1644            }
1645            // Always hide the type filter
1646            dismissPopup();
1647
1648            if (touchMode == TOUCH_MODE_OFF) {
1649                // Remember the last selected element
1650                mResurrectToPosition = mSelectedPosition;
1651            }
1652        } else {
1653            if (mFiltered && !mPopupHidden) {
1654                // Show the type filter only if a filter is in effect
1655                showPopup();
1656            }
1657
1658            // If we changed touch mode since the last time we had focus
1659            if (touchMode != mLastTouchMode && mLastTouchMode != TOUCH_MODE_UNKNOWN) {
1660                // If we come back in trackball mode, we bring the selection back
1661                if (touchMode == TOUCH_MODE_OFF) {
1662                    // This will trigger a layout
1663                    resurrectSelection();
1664
1665                // If we come back in touch mode, then we want to hide the selector
1666                } else {
1667                    hideSelector();
1668                    mLayoutMode = LAYOUT_NORMAL;
1669                    layoutChildren();
1670                }
1671            }
1672        }
1673
1674        mLastTouchMode = touchMode;
1675    }
1676
1677    /**
1678     * Creates the ContextMenuInfo returned from {@link #getContextMenuInfo()}. This
1679     * methods knows the view, position and ID of the item that received the
1680     * long press.
1681     *
1682     * @param view The view that received the long press.
1683     * @param position The position of the item that received the long press.
1684     * @param id The ID of the item that received the long press.
1685     * @return The extra information that should be returned by
1686     *         {@link #getContextMenuInfo()}.
1687     */
1688    ContextMenuInfo createContextMenuInfo(View view, int position, long id) {
1689        return new AdapterContextMenuInfo(view, position, id);
1690    }
1691
1692    /**
1693     * A base class for Runnables that will check that their view is still attached to
1694     * the original window as when the Runnable was created.
1695     *
1696     */
1697    private class WindowRunnnable {
1698        private int mOriginalAttachCount;
1699
1700        public void rememberWindowAttachCount() {
1701            mOriginalAttachCount = getWindowAttachCount();
1702        }
1703
1704        public boolean sameWindow() {
1705            return hasWindowFocus() && getWindowAttachCount() == mOriginalAttachCount;
1706        }
1707    }
1708
1709    private class PerformClick extends WindowRunnnable implements Runnable {
1710        View mChild;
1711        int mClickMotionPosition;
1712
1713        public void run() {
1714            // The data has changed since we posted this action in the event queue,
1715            // bail out before bad things happen
1716            if (mDataChanged) return;
1717
1718            final ListAdapter adapter = mAdapter;
1719            final int motionPosition = mClickMotionPosition;
1720            if (adapter != null && mItemCount > 0 &&
1721                    motionPosition != INVALID_POSITION &&
1722                    motionPosition < adapter.getCount() && sameWindow()) {
1723                performItemClick(mChild, motionPosition, adapter.getItemId(motionPosition));
1724            }
1725        }
1726    }
1727
1728    private class CheckForLongPress extends WindowRunnnable implements Runnable {
1729        public void run() {
1730            final int motionPosition = mMotionPosition;
1731            final View child = getChildAt(motionPosition - mFirstPosition);
1732            if (child != null) {
1733                final int longPressPosition = mMotionPosition;
1734                final long longPressId = mAdapter.getItemId(mMotionPosition);
1735
1736                boolean handled = false;
1737                if (sameWindow() && !mDataChanged) {
1738                    handled = performLongPress(child, longPressPosition, longPressId);
1739                }
1740                if (handled) {
1741                    mTouchMode = TOUCH_MODE_REST;
1742                    setPressed(false);
1743                    child.setPressed(false);
1744                } else {
1745                    mTouchMode = TOUCH_MODE_DONE_WAITING;
1746                }
1747
1748            }
1749        }
1750    }
1751
1752    private class CheckForKeyLongPress extends WindowRunnnable implements Runnable {
1753        public void run() {
1754            if (isPressed() && mSelectedPosition >= 0) {
1755                int index = mSelectedPosition - mFirstPosition;
1756                View v = getChildAt(index);
1757
1758                if (!mDataChanged) {
1759                    boolean handled = false;
1760                    if (sameWindow()) {
1761                        handled = performLongPress(v, mSelectedPosition, mSelectedRowId);
1762                    }
1763                    if (handled) {
1764                        setPressed(false);
1765                        v.setPressed(false);
1766                    }
1767                } else {
1768                    setPressed(false);
1769                    if (v != null) v.setPressed(false);
1770                }
1771            }
1772        }
1773    }
1774
1775    private boolean performLongPress(final View child,
1776            final int longPressPosition, final long longPressId) {
1777        boolean handled = false;
1778
1779        if (mOnItemLongClickListener != null) {
1780            handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, child,
1781                    longPressPosition, longPressId);
1782        }
1783        if (!handled) {
1784            mContextMenuInfo = createContextMenuInfo(child, longPressPosition, longPressId);
1785            handled = super.showContextMenuForChild(AbsListView.this);
1786        }
1787        if (handled) {
1788            performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
1789        }
1790        return handled;
1791    }
1792
1793    @Override
1794    protected ContextMenuInfo getContextMenuInfo() {
1795        return mContextMenuInfo;
1796    }
1797
1798    @Override
1799    public boolean showContextMenuForChild(View originalView) {
1800        final int longPressPosition = getPositionForView(originalView);
1801        if (longPressPosition >= 0) {
1802            final long longPressId = mAdapter.getItemId(longPressPosition);
1803            boolean handled = false;
1804
1805            if (mOnItemLongClickListener != null) {
1806                handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, originalView,
1807                        longPressPosition, longPressId);
1808            }
1809            if (!handled) {
1810                mContextMenuInfo = createContextMenuInfo(
1811                        getChildAt(longPressPosition - mFirstPosition),
1812                        longPressPosition, longPressId);
1813                handled = super.showContextMenuForChild(originalView);
1814            }
1815
1816            return handled;
1817        }
1818        return false;
1819    }
1820
1821    @Override
1822    public boolean onKeyDown(int keyCode, KeyEvent event) {
1823        return false;
1824    }
1825
1826    @Override
1827    public boolean onKeyUp(int keyCode, KeyEvent event) {
1828        switch (keyCode) {
1829        case KeyEvent.KEYCODE_DPAD_CENTER:
1830        case KeyEvent.KEYCODE_ENTER:
1831            if (!isEnabled()) {
1832                return true;
1833            }
1834            if (isClickable() && isPressed() &&
1835                    mSelectedPosition >= 0 && mAdapter != null &&
1836                    mSelectedPosition < mAdapter.getCount()) {
1837
1838                final View view = getChildAt(mSelectedPosition - mFirstPosition);
1839                if (view != null) {
1840                    performItemClick(view, mSelectedPosition, mSelectedRowId);
1841                    view.setPressed(false);
1842                }
1843                setPressed(false);
1844                return true;
1845            }
1846            break;
1847        }
1848        return super.onKeyUp(keyCode, event);
1849    }
1850
1851    @Override
1852    protected void dispatchSetPressed(boolean pressed) {
1853        // Don't dispatch setPressed to our children. We call setPressed on ourselves to
1854        // get the selector in the right state, but we don't want to press each child.
1855    }
1856
1857    /**
1858     * Maps a point to a position in the list.
1859     *
1860     * @param x X in local coordinate
1861     * @param y Y in local coordinate
1862     * @return The position of the item which contains the specified point, or
1863     *         {@link #INVALID_POSITION} if the point does not intersect an item.
1864     */
1865    public int pointToPosition(int x, int y) {
1866        Rect frame = mTouchFrame;
1867        if (frame == null) {
1868            mTouchFrame = new Rect();
1869            frame = mTouchFrame;
1870        }
1871
1872        final int count = getChildCount();
1873        for (int i = count - 1; i >= 0; i--) {
1874            final View child = getChildAt(i);
1875            if (child.getVisibility() == View.VISIBLE) {
1876                child.getHitRect(frame);
1877                if (frame.contains(x, y)) {
1878                    return mFirstPosition + i;
1879                }
1880            }
1881        }
1882        return INVALID_POSITION;
1883    }
1884
1885
1886    /**
1887     * Maps a point to a the rowId of the item which intersects that point.
1888     *
1889     * @param x X in local coordinate
1890     * @param y Y in local coordinate
1891     * @return The rowId of the item which contains the specified point, or {@link #INVALID_ROW_ID}
1892     *         if the point does not intersect an item.
1893     */
1894    public long pointToRowId(int x, int y) {
1895        int position = pointToPosition(x, y);
1896        if (position >= 0) {
1897            return mAdapter.getItemId(position);
1898        }
1899        return INVALID_ROW_ID;
1900    }
1901
1902    final class CheckForTap implements Runnable {
1903        public void run() {
1904            if (mTouchMode == TOUCH_MODE_DOWN) {
1905                mTouchMode = TOUCH_MODE_TAP;
1906                final View child = getChildAt(mMotionPosition - mFirstPosition);
1907                if (child != null && !child.hasFocusable()) {
1908                    mLayoutMode = LAYOUT_NORMAL;
1909
1910                    if (!mDataChanged) {
1911                        layoutChildren();
1912                        child.setPressed(true);
1913                        positionSelector(child);
1914                        setPressed(true);
1915
1916                        final int longPressTimeout = ViewConfiguration.getLongPressTimeout();
1917                        final boolean longClickable = isLongClickable();
1918
1919                        if (mSelector != null) {
1920                            Drawable d = mSelector.getCurrent();
1921                            if (d != null && d instanceof TransitionDrawable) {
1922                                if (longClickable) {
1923                                    ((TransitionDrawable) d).startTransition(longPressTimeout);
1924                                } else {
1925                                    ((TransitionDrawable) d).resetTransition();
1926                                }
1927                            }
1928                        }
1929
1930                        if (longClickable) {
1931                            if (mPendingCheckForLongPress == null) {
1932                                mPendingCheckForLongPress = new CheckForLongPress();
1933                            }
1934                            mPendingCheckForLongPress.rememberWindowAttachCount();
1935                            postDelayed(mPendingCheckForLongPress, longPressTimeout);
1936                        } else {
1937                            mTouchMode = TOUCH_MODE_DONE_WAITING;
1938                        }
1939                    } else {
1940                        mTouchMode = TOUCH_MODE_DONE_WAITING;
1941                    }
1942                }
1943            }
1944        }
1945    }
1946
1947    private boolean startScrollIfNeeded(int deltaY) {
1948        // Check if we have moved far enough that it looks more like a
1949        // scroll than a tap
1950        final int distance = Math.abs(deltaY);
1951        if (distance > mTouchSlop) {
1952            createScrollingCache();
1953            mTouchMode = TOUCH_MODE_SCROLL;
1954            mMotionCorrection = deltaY;
1955            final Handler handler = getHandler();
1956            // Handler should not be null unless the AbsListView is not attached to a
1957            // window, which would make it very hard to scroll it... but the monkeys
1958            // say it's possible.
1959            if (handler != null) {
1960                handler.removeCallbacks(mPendingCheckForLongPress);
1961            }
1962            setPressed(false);
1963            View motionView = getChildAt(mMotionPosition - mFirstPosition);
1964            if (motionView != null) {
1965                motionView.setPressed(false);
1966            }
1967            reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
1968            // Time to start stealing events! Once we've stolen them, don't let anyone
1969            // steal from us
1970            requestDisallowInterceptTouchEvent(true);
1971            return true;
1972        }
1973
1974        return false;
1975    }
1976
1977    public void onTouchModeChanged(boolean isInTouchMode) {
1978        if (isInTouchMode) {
1979            // Get rid of the selection when we enter touch mode
1980            hideSelector();
1981            // Layout, but only if we already have done so previously.
1982            // (Otherwise may clobber a LAYOUT_SYNC layout that was requested to restore
1983            // state.)
1984            if (getHeight() > 0 && getChildCount() > 0) {
1985                // We do not lose focus initiating a touch (since AbsListView is focusable in
1986                // touch mode). Force an initial layout to get rid of the selection.
1987                layoutChildren();
1988            }
1989        }
1990    }
1991
1992    @Override
1993    public boolean onTouchEvent(MotionEvent ev) {
1994        if (!isEnabled()) {
1995            // A disabled view that is clickable still consumes the touch
1996            // events, it just doesn't respond to them.
1997            return isClickable() || isLongClickable();
1998        }
1999
2000        if (mFastScroller != null) {
2001            boolean intercepted = mFastScroller.onTouchEvent(ev);
2002            if (intercepted) {
2003                return true;
2004            }
2005        }
2006
2007        final int action = ev.getAction();
2008
2009        View v;
2010        int deltaY;
2011
2012        if (mVelocityTracker == null) {
2013            mVelocityTracker = VelocityTracker.obtain();
2014        }
2015        mVelocityTracker.addMovement(ev);
2016
2017        switch (action & MotionEvent.ACTION_MASK) {
2018        case MotionEvent.ACTION_DOWN: {
2019            mActivePointerId = ev.getPointerId(0);
2020            final int x = (int) ev.getX();
2021            final int y = (int) ev.getY();
2022            int motionPosition = pointToPosition(x, y);
2023            if (!mDataChanged) {
2024                if ((mTouchMode != TOUCH_MODE_FLING) && (motionPosition >= 0)
2025                        && (getAdapter().isEnabled(motionPosition))) {
2026                    // User clicked on an actual view (and was not stopping a fling). It might be a
2027                    // click or a scroll. Assume it is a click until proven otherwise
2028                    mTouchMode = TOUCH_MODE_DOWN;
2029                    // FIXME Debounce
2030                    if (mPendingCheckForTap == null) {
2031                        mPendingCheckForTap = new CheckForTap();
2032                    }
2033                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
2034                } else {
2035                    if (ev.getEdgeFlags() != 0 && motionPosition < 0) {
2036                        // If we couldn't find a view to click on, but the down event was touching
2037                        // the edge, we will bail out and try again. This allows the edge correcting
2038                        // code in ViewRoot to try to find a nearby view to select
2039                        return false;
2040                    }
2041
2042                    if (mTouchMode == TOUCH_MODE_FLING) {
2043                        // Stopped a fling. It is a scroll.
2044                        createScrollingCache();
2045                        mTouchMode = TOUCH_MODE_SCROLL;
2046                        mMotionCorrection = 0;
2047                        motionPosition = findMotionRow(y);
2048                        reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
2049                    }
2050                }
2051            }
2052
2053            if (motionPosition >= 0) {
2054                // Remember where the motion event started
2055                v = getChildAt(motionPosition - mFirstPosition);
2056                mMotionViewOriginalTop = v.getTop();
2057            }
2058            mMotionX = x;
2059            mMotionY = y;
2060            mMotionPosition = motionPosition;
2061            mLastY = Integer.MIN_VALUE;
2062            break;
2063        }
2064
2065        case MotionEvent.ACTION_MOVE: {
2066            final int pointerIndex = ev.findPointerIndex(mActivePointerId);
2067            final int y = (int) ev.getY(pointerIndex);
2068            deltaY = y - mMotionY;
2069            switch (mTouchMode) {
2070            case TOUCH_MODE_DOWN:
2071            case TOUCH_MODE_TAP:
2072            case TOUCH_MODE_DONE_WAITING:
2073                // Check if we have moved far enough that it looks more like a
2074                // scroll than a tap
2075                startScrollIfNeeded(deltaY);
2076                break;
2077            case TOUCH_MODE_SCROLL:
2078                if (PROFILE_SCROLLING) {
2079                    if (!mScrollProfilingStarted) {
2080                        Debug.startMethodTracing("AbsListViewScroll");
2081                        mScrollProfilingStarted = true;
2082                    }
2083                }
2084
2085                if (y != mLastY) {
2086                    // We may be here after stopping a fling and continuing to scroll.
2087                    // If so, we haven't disallowed intercepting touch events yet.
2088                    // Make sure that we do so in case we're in a parent that can intercept.
2089                    if ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) == 0 &&
2090                            Math.abs(deltaY) > mTouchSlop) {
2091                        requestDisallowInterceptTouchEvent(true);
2092                    }
2093
2094                    deltaY -= mMotionCorrection;
2095                    int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY;
2096
2097                    // No need to do all this work if we're not going to move anyway
2098                    boolean atEdge = false;
2099                    if (incrementalDeltaY != 0) {
2100                        atEdge = trackMotionScroll(deltaY, incrementalDeltaY);
2101                    }
2102
2103                    // Check to see if we have bumped into the scroll limit
2104                    if (atEdge && getChildCount() > 0) {
2105                        // Treat this like we're starting a new scroll from the current
2106                        // position. This will let the user start scrolling back into
2107                        // content immediately rather than needing to scroll back to the
2108                        // point where they hit the limit first.
2109                        int motionPosition = findMotionRow(y);
2110                        if (motionPosition >= 0) {
2111                            final View motionView = getChildAt(motionPosition - mFirstPosition);
2112                            mMotionViewOriginalTop = motionView.getTop();
2113                        }
2114                        mMotionY = y;
2115                        mMotionPosition = motionPosition;
2116                        invalidate();
2117                    }
2118                    mLastY = y;
2119                }
2120                break;
2121            }
2122
2123            break;
2124        }
2125
2126        case MotionEvent.ACTION_UP: {
2127            switch (mTouchMode) {
2128            case TOUCH_MODE_DOWN:
2129            case TOUCH_MODE_TAP:
2130            case TOUCH_MODE_DONE_WAITING:
2131                final int motionPosition = mMotionPosition;
2132                final View child = getChildAt(motionPosition - mFirstPosition);
2133                if (child != null && !child.hasFocusable()) {
2134                    if (mTouchMode != TOUCH_MODE_DOWN) {
2135                        child.setPressed(false);
2136                    }
2137
2138                    if (mPerformClick == null) {
2139                        mPerformClick = new PerformClick();
2140                    }
2141
2142                    final AbsListView.PerformClick performClick = mPerformClick;
2143                    performClick.mChild = child;
2144                    performClick.mClickMotionPosition = motionPosition;
2145                    performClick.rememberWindowAttachCount();
2146
2147                    mResurrectToPosition = motionPosition;
2148
2149                    if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) {
2150                        final Handler handler = getHandler();
2151                        if (handler != null) {
2152                            handler.removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
2153                                    mPendingCheckForTap : mPendingCheckForLongPress);
2154                        }
2155                        mLayoutMode = LAYOUT_NORMAL;
2156                        if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
2157                            mTouchMode = TOUCH_MODE_TAP;
2158                            setSelectedPositionInt(mMotionPosition);
2159                            layoutChildren();
2160                            child.setPressed(true);
2161                            positionSelector(child);
2162                            setPressed(true);
2163                            if (mSelector != null) {
2164                                Drawable d = mSelector.getCurrent();
2165                                if (d != null && d instanceof TransitionDrawable) {
2166                                    ((TransitionDrawable) d).resetTransition();
2167                                }
2168                            }
2169                            postDelayed(new Runnable() {
2170                                public void run() {
2171                                    child.setPressed(false);
2172                                    setPressed(false);
2173                                    if (!mDataChanged) {
2174                                        post(performClick);
2175                                    }
2176                                    mTouchMode = TOUCH_MODE_REST;
2177                                }
2178                            }, ViewConfiguration.getPressedStateDuration());
2179                        } else {
2180                            mTouchMode = TOUCH_MODE_REST;
2181                        }
2182                        return true;
2183                    } else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
2184                        post(performClick);
2185                    }
2186                }
2187                mTouchMode = TOUCH_MODE_REST;
2188                break;
2189            case TOUCH_MODE_SCROLL:
2190                final int childCount = getChildCount();
2191                if (childCount > 0) {
2192                    if (mFirstPosition == 0 && getChildAt(0).getTop() >= mListPadding.top &&
2193                            mFirstPosition + childCount < mItemCount &&
2194                            getChildAt(childCount - 1).getBottom() <=
2195                                    getHeight() - mListPadding.bottom) {
2196                        mTouchMode = TOUCH_MODE_REST;
2197                        reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
2198                    } else {
2199                        final VelocityTracker velocityTracker = mVelocityTracker;
2200                        velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
2201                        final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
2202
2203                        if (Math.abs(initialVelocity) > mMinimumVelocity) {
2204                            if (mFlingRunnable == null) {
2205                                mFlingRunnable = new FlingRunnable();
2206                            }
2207                            reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
2208
2209                            mFlingRunnable.start(-initialVelocity);
2210                        } else {
2211                            mTouchMode = TOUCH_MODE_REST;
2212                            reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
2213                        }
2214                    }
2215                } else {
2216                    mTouchMode = TOUCH_MODE_REST;
2217                    reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
2218                }
2219                break;
2220            }
2221
2222            setPressed(false);
2223
2224            // Need to redraw since we probably aren't drawing the selector anymore
2225            invalidate();
2226
2227            final Handler handler = getHandler();
2228            if (handler != null) {
2229                handler.removeCallbacks(mPendingCheckForLongPress);
2230            }
2231
2232            if (mVelocityTracker != null) {
2233                mVelocityTracker.recycle();
2234                mVelocityTracker = null;
2235            }
2236
2237            mActivePointerId = INVALID_POINTER;
2238
2239            if (PROFILE_SCROLLING) {
2240                if (mScrollProfilingStarted) {
2241                    Debug.stopMethodTracing();
2242                    mScrollProfilingStarted = false;
2243                }
2244            }
2245            break;
2246        }
2247
2248        case MotionEvent.ACTION_CANCEL: {
2249            mTouchMode = TOUCH_MODE_REST;
2250            setPressed(false);
2251            View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
2252            if (motionView != null) {
2253                motionView.setPressed(false);
2254            }
2255            clearScrollingCache();
2256
2257            final Handler handler = getHandler();
2258            if (handler != null) {
2259                handler.removeCallbacks(mPendingCheckForLongPress);
2260            }
2261
2262            if (mVelocityTracker != null) {
2263                mVelocityTracker.recycle();
2264                mVelocityTracker = null;
2265            }
2266
2267            mActivePointerId = INVALID_POINTER;
2268            break;
2269        }
2270
2271        case MotionEvent.ACTION_POINTER_UP: {
2272            onSecondaryPointerUp(ev);
2273            final int x = mMotionX;
2274            final int y = mMotionY;
2275            final int motionPosition = pointToPosition(x, y);
2276            if (motionPosition >= 0) {
2277                // Remember where the motion event started
2278                v = getChildAt(motionPosition - mFirstPosition);
2279                mMotionViewOriginalTop = v.getTop();
2280                mMotionPosition = motionPosition;
2281            }
2282            mLastY = y;
2283            break;
2284        }
2285        }
2286
2287        return true;
2288    }
2289
2290    @Override
2291    public void draw(Canvas canvas) {
2292        super.draw(canvas);
2293        if (mFastScroller != null) {
2294            mFastScroller.draw(canvas);
2295        }
2296    }
2297
2298    @Override
2299    public boolean onInterceptTouchEvent(MotionEvent ev) {
2300        int action = ev.getAction();
2301        View v;
2302
2303        if (mFastScroller != null) {
2304            boolean intercepted = mFastScroller.onInterceptTouchEvent(ev);
2305            if (intercepted) {
2306                return true;
2307            }
2308        }
2309
2310        switch (action & MotionEvent.ACTION_MASK) {
2311        case MotionEvent.ACTION_DOWN: {
2312            int touchMode = mTouchMode;
2313
2314            final int x = (int) ev.getX();
2315            final int y = (int) ev.getY();
2316            mActivePointerId = ev.getPointerId(0);
2317
2318            int motionPosition = findMotionRow(y);
2319            if (touchMode != TOUCH_MODE_FLING && motionPosition >= 0) {
2320                // User clicked on an actual view (and was not stopping a fling).
2321                // Remember where the motion event started
2322                v = getChildAt(motionPosition - mFirstPosition);
2323                mMotionViewOriginalTop = v.getTop();
2324                mMotionX = x;
2325                mMotionY = y;
2326                mMotionPosition = motionPosition;
2327                mTouchMode = TOUCH_MODE_DOWN;
2328                clearScrollingCache();
2329            }
2330            mLastY = Integer.MIN_VALUE;
2331            if (touchMode == TOUCH_MODE_FLING) {
2332                return true;
2333            }
2334            break;
2335        }
2336
2337        case MotionEvent.ACTION_MOVE: {
2338            switch (mTouchMode) {
2339            case TOUCH_MODE_DOWN:
2340                final int pointerIndex = ev.findPointerIndex(mActivePointerId);
2341                final int y = (int) ev.getY(pointerIndex);
2342                if (startScrollIfNeeded(y - mMotionY)) {
2343                    return true;
2344                }
2345                break;
2346            }
2347            break;
2348        }
2349
2350        case MotionEvent.ACTION_UP: {
2351            mTouchMode = TOUCH_MODE_REST;
2352            mActivePointerId = INVALID_POINTER;
2353            reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
2354            break;
2355        }
2356
2357        case MotionEvent.ACTION_POINTER_UP: {
2358            onSecondaryPointerUp(ev);
2359            break;
2360        }
2361        }
2362
2363        return false;
2364    }
2365
2366    private void onSecondaryPointerUp(MotionEvent ev) {
2367        final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
2368                MotionEvent.ACTION_POINTER_INDEX_SHIFT;
2369        final int pointerId = ev.getPointerId(pointerIndex);
2370        if (pointerId == mActivePointerId) {
2371            // This was our active pointer going up. Choose a new
2372            // active pointer and adjust accordingly.
2373            // TODO: Make this decision more intelligent.
2374            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
2375            mMotionX = (int) ev.getX(newPointerIndex);
2376            mMotionY = (int) ev.getY(newPointerIndex);
2377            mActivePointerId = ev.getPointerId(newPointerIndex);
2378            if (mVelocityTracker != null) {
2379                mVelocityTracker.clear();
2380            }
2381        }
2382    }
2383
2384    /**
2385     * {@inheritDoc}
2386     */
2387    @Override
2388    public void addTouchables(ArrayList<View> views) {
2389        final int count = getChildCount();
2390        final int firstPosition = mFirstPosition;
2391        final ListAdapter adapter = mAdapter;
2392
2393        if (adapter == null) {
2394            return;
2395        }
2396
2397        for (int i = 0; i < count; i++) {
2398            final View child = getChildAt(i);
2399            if (adapter.isEnabled(firstPosition + i)) {
2400                views.add(child);
2401            }
2402            child.addTouchables(views);
2403        }
2404    }
2405
2406    /**
2407     * Fires an "on scroll state changed" event to the registered
2408     * {@link android.widget.AbsListView.OnScrollListener}, if any. The state change
2409     * is fired only if the specified state is different from the previously known state.
2410     *
2411     * @param newState The new scroll state.
2412     */
2413    void reportScrollStateChange(int newState) {
2414        if (newState != mLastScrollState) {
2415            if (mOnScrollListener != null) {
2416                mOnScrollListener.onScrollStateChanged(this, newState);
2417                mLastScrollState = newState;
2418            }
2419        }
2420    }
2421
2422    /**
2423     * Responsible for fling behavior. Use {@link #start(int)} to
2424     * initiate a fling. Each frame of the fling is handled in {@link #run()}.
2425     * A FlingRunnable will keep re-posting itself until the fling is done.
2426     *
2427     */
2428    private class FlingRunnable implements Runnable {
2429        /**
2430         * Tracks the decay of a fling scroll
2431         */
2432        private final Scroller mScroller;
2433
2434        /**
2435         * Y value reported by mScroller on the previous fling
2436         */
2437        private int mLastFlingY;
2438
2439        FlingRunnable() {
2440            mScroller = new Scroller(getContext());
2441        }
2442
2443        void start(int initialVelocity) {
2444            int initialY = initialVelocity < 0 ? Integer.MAX_VALUE : 0;
2445            mLastFlingY = initialY;
2446            mScroller.fling(0, initialY, 0, initialVelocity,
2447                    0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE);
2448            mTouchMode = TOUCH_MODE_FLING;
2449            post(this);
2450
2451            if (PROFILE_FLINGING) {
2452                if (!mFlingProfilingStarted) {
2453                    Debug.startMethodTracing("AbsListViewFling");
2454                    mFlingProfilingStarted = true;
2455                }
2456            }
2457        }
2458
2459        void startScroll(int distance, int duration) {
2460            int initialY = distance < 0 ? Integer.MAX_VALUE : 0;
2461            mLastFlingY = initialY;
2462            mScroller.startScroll(0, initialY, 0, distance, duration);
2463            mTouchMode = TOUCH_MODE_FLING;
2464            post(this);
2465        }
2466
2467        private void endFling() {
2468            mTouchMode = TOUCH_MODE_REST;
2469
2470            reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
2471            clearScrollingCache();
2472
2473            removeCallbacks(this);
2474
2475            if (mPositionScroller != null) {
2476                removeCallbacks(mPositionScroller);
2477            }
2478        }
2479
2480        public void run() {
2481            switch (mTouchMode) {
2482            default:
2483                return;
2484
2485            case TOUCH_MODE_FLING: {
2486                if (mItemCount == 0 || getChildCount() == 0) {
2487                    endFling();
2488                    return;
2489                }
2490
2491                final Scroller scroller = mScroller;
2492                boolean more = scroller.computeScrollOffset();
2493                final int y = scroller.getCurrY();
2494
2495                // Flip sign to convert finger direction to list items direction
2496                // (e.g. finger moving down means list is moving towards the top)
2497                int delta = mLastFlingY - y;
2498
2499                // Pretend that each frame of a fling scroll is a touch scroll
2500                if (delta > 0) {
2501                    // List is moving towards the top. Use first view as mMotionPosition
2502                    mMotionPosition = mFirstPosition;
2503                    final View firstView = getChildAt(0);
2504                    mMotionViewOriginalTop = firstView.getTop();
2505
2506                    // Don't fling more than 1 screen
2507                    delta = Math.min(getHeight() - mPaddingBottom - mPaddingTop - 1, delta);
2508                } else {
2509                    // List is moving towards the bottom. Use last view as mMotionPosition
2510                    int offsetToLast = getChildCount() - 1;
2511                    mMotionPosition = mFirstPosition + offsetToLast;
2512
2513                    final View lastView = getChildAt(offsetToLast);
2514                    mMotionViewOriginalTop = lastView.getTop();
2515
2516                    // Don't fling more than 1 screen
2517                    delta = Math.max(-(getHeight() - mPaddingBottom - mPaddingTop - 1), delta);
2518                }
2519
2520                final boolean atEnd = trackMotionScroll(delta, delta);
2521
2522                if (more && !atEnd) {
2523                    invalidate();
2524                    mLastFlingY = y;
2525                    post(this);
2526                } else {
2527                    endFling();
2528
2529                    if (PROFILE_FLINGING) {
2530                        if (mFlingProfilingStarted) {
2531                            Debug.stopMethodTracing();
2532                            mFlingProfilingStarted = false;
2533                        }
2534                    }
2535                }
2536                break;
2537            }
2538            }
2539
2540        }
2541    }
2542
2543
2544    class PositionScroller implements Runnable {
2545        private static final int SCROLL_DURATION = 400;
2546
2547        private static final int MOVE_DOWN_POS = 1;
2548        private static final int MOVE_UP_POS = 2;
2549        private static final int MOVE_DOWN_BOUND = 3;
2550        private static final int MOVE_UP_BOUND = 4;
2551        private static final int MOVE_OFFSET = 5;
2552
2553        private int mMode;
2554        private int mTargetPos;
2555        private int mBoundPos;
2556        private int mLastSeenPos;
2557        private int mScrollDuration;
2558        private final int mExtraScroll;
2559
2560        private int mOffsetFromTop;
2561
2562        PositionScroller() {
2563            mExtraScroll = ViewConfiguration.get(mContext).getScaledFadingEdgeLength();
2564        }
2565
2566        void start(int position) {
2567            final int firstPos = mFirstPosition;
2568            final int lastPos = firstPos + getChildCount() - 1;
2569
2570            int viewTravelCount = 0;
2571            if (position <= firstPos) {
2572                viewTravelCount = firstPos - position + 1;
2573                mMode = MOVE_UP_POS;
2574            } else if (position >= lastPos) {
2575                viewTravelCount = position - lastPos + 1;
2576                mMode = MOVE_DOWN_POS;
2577            } else {
2578                // Already on screen, nothing to do
2579                return;
2580            }
2581
2582            if (viewTravelCount > 0) {
2583                mScrollDuration = SCROLL_DURATION / viewTravelCount;
2584            } else {
2585                mScrollDuration = SCROLL_DURATION;
2586            }
2587            mTargetPos = position;
2588            mBoundPos = INVALID_POSITION;
2589            mLastSeenPos = INVALID_POSITION;
2590
2591            post(this);
2592        }
2593
2594        void start(int position, int boundPosition) {
2595            if (boundPosition == INVALID_POSITION) {
2596                start(position);
2597                return;
2598            }
2599
2600            final int firstPos = mFirstPosition;
2601            final int lastPos = firstPos + getChildCount() - 1;
2602
2603            int viewTravelCount = 0;
2604            if (position <= firstPos) {
2605                final int boundPosFromLast = lastPos - boundPosition;
2606                if (boundPosFromLast < 1) {
2607                    // Moving would shift our bound position off the screen. Abort.
2608                    return;
2609                }
2610
2611                final int posTravel = firstPos - position + 1;
2612                final int boundTravel = boundPosFromLast - 1;
2613                if (boundTravel < posTravel) {
2614                    viewTravelCount = boundTravel;
2615                    mMode = MOVE_UP_BOUND;
2616                } else {
2617                    viewTravelCount = posTravel;
2618                    mMode = MOVE_UP_POS;
2619                }
2620            } else if (position >= lastPos) {
2621                final int boundPosFromFirst = boundPosition - firstPos;
2622                if (boundPosFromFirst < 1) {
2623                    // Moving would shift our bound position off the screen. Abort.
2624                    return;
2625                }
2626
2627                final int posTravel = position - lastPos + 1;
2628                final int boundTravel = boundPosFromFirst - 1;
2629                if (boundTravel < posTravel) {
2630                    viewTravelCount = boundTravel;
2631                    mMode = MOVE_DOWN_BOUND;
2632                } else {
2633                    viewTravelCount = posTravel;
2634                    mMode = MOVE_DOWN_POS;
2635                }
2636            } else {
2637                // Already on screen, nothing to do
2638                return;
2639            }
2640
2641            if (viewTravelCount > 0) {
2642                mScrollDuration = SCROLL_DURATION / viewTravelCount;
2643            } else {
2644                mScrollDuration = SCROLL_DURATION;
2645            }
2646            mTargetPos = position;
2647            mBoundPos = boundPosition;
2648            mLastSeenPos = INVALID_POSITION;
2649
2650            post(this);
2651        }
2652
2653        void startWithOffset(int position, int offset) {
2654            mTargetPos = position;
2655            mOffsetFromTop = offset;
2656            mBoundPos = INVALID_POSITION;
2657            mLastSeenPos = INVALID_POSITION;
2658            mMode = MOVE_OFFSET;
2659
2660            final int firstPos = mFirstPosition;
2661            final int childCount = getChildCount();
2662            final int lastPos = firstPos + childCount - 1;
2663
2664            int viewTravelCount = 0;
2665            if (position < firstPos) {
2666                viewTravelCount = firstPos - position;
2667            } else if (position > lastPos) {
2668                viewTravelCount = position - lastPos;
2669            } else {
2670                // On-screen, just scroll.
2671                final int targetTop = getChildAt(position - firstPos).getTop();
2672                smoothScrollBy(targetTop - offset, SCROLL_DURATION);
2673                return;
2674            }
2675
2676            // Estimate how many screens we should travel
2677            final float screenTravelCount = viewTravelCount / childCount;
2678            mScrollDuration = (int) (SCROLL_DURATION / screenTravelCount);
2679            mLastSeenPos = INVALID_POSITION;
2680            post(this);
2681        }
2682
2683        void stop() {
2684            removeCallbacks(this);
2685        }
2686
2687        public void run() {
2688            if (mTouchMode != TOUCH_MODE_FLING && mLastSeenPos != INVALID_POSITION) {
2689                return;
2690            }
2691
2692            final int listHeight = getHeight();
2693            final int firstPos = mFirstPosition;
2694
2695            switch (mMode) {
2696            case MOVE_DOWN_POS: {
2697                final int lastViewIndex = getChildCount() - 1;
2698                final int lastPos = firstPos + lastViewIndex;
2699
2700                if (lastViewIndex < 0) {
2701                    return;
2702                }
2703
2704                if (lastPos == mLastSeenPos) {
2705                    // No new views, let things keep going.
2706                    post(this);
2707                    return;
2708                }
2709
2710                final View lastView = getChildAt(lastViewIndex);
2711                final int lastViewHeight = lastView.getHeight();
2712                final int lastViewTop = lastView.getTop();
2713                final int lastViewPixelsShowing = listHeight - lastViewTop;
2714                final int extraScroll = lastPos < mItemCount - 1 ? mExtraScroll : mListPadding.bottom;
2715
2716                smoothScrollBy(lastViewHeight - lastViewPixelsShowing + extraScroll,
2717                        mScrollDuration);
2718
2719                mLastSeenPos = lastPos;
2720                if (lastPos < mTargetPos) {
2721                    post(this);
2722                }
2723                break;
2724            }
2725
2726            case MOVE_DOWN_BOUND: {
2727                final int nextViewIndex = 1;
2728                final int childCount = getChildCount();
2729
2730                if (firstPos == mBoundPos || childCount <= nextViewIndex
2731                        || firstPos + childCount >= mItemCount) {
2732                    return;
2733                }
2734                final int nextPos = firstPos + nextViewIndex;
2735
2736                if (nextPos == mLastSeenPos) {
2737                    // No new views, let things keep going.
2738                    post(this);
2739                    return;
2740                }
2741
2742                final View nextView = getChildAt(nextViewIndex);
2743                final int nextViewHeight = nextView.getHeight();
2744                final int nextViewTop = nextView.getTop();
2745                final int extraScroll = mExtraScroll;
2746                if (nextPos < mBoundPos) {
2747                    smoothScrollBy(Math.max(0, nextViewHeight + nextViewTop - extraScroll),
2748                            mScrollDuration);
2749
2750                    mLastSeenPos = nextPos;
2751
2752                    post(this);
2753                } else  {
2754                    if (nextViewTop > extraScroll) {
2755                        smoothScrollBy(nextViewTop - extraScroll, mScrollDuration);
2756                    }
2757                }
2758                break;
2759            }
2760
2761            case MOVE_UP_POS: {
2762                if (firstPos == mLastSeenPos) {
2763                    // No new views, let things keep going.
2764                    post(this);
2765                    return;
2766                }
2767
2768                final View firstView = getChildAt(0);
2769                if (firstView == null) {
2770                    return;
2771                }
2772                final int firstViewTop = firstView.getTop();
2773                final int extraScroll = firstPos > 0 ? mExtraScroll : mListPadding.top;
2774
2775                smoothScrollBy(firstViewTop - extraScroll, mScrollDuration);
2776
2777                mLastSeenPos = firstPos;
2778
2779                if (firstPos > mTargetPos) {
2780                    post(this);
2781                }
2782                break;
2783            }
2784
2785            case MOVE_UP_BOUND: {
2786                final int lastViewIndex = getChildCount() - 2;
2787                if (lastViewIndex < 0) {
2788                    return;
2789                }
2790                final int lastPos = firstPos + lastViewIndex;
2791
2792                if (lastPos == mLastSeenPos) {
2793                    // No new views, let things keep going.
2794                    post(this);
2795                    return;
2796                }
2797
2798                final View lastView = getChildAt(lastViewIndex);
2799                final int lastViewHeight = lastView.getHeight();
2800                final int lastViewTop = lastView.getTop();
2801                final int lastViewPixelsShowing = listHeight - lastViewTop;
2802                mLastSeenPos = lastPos;
2803                if (lastPos > mBoundPos) {
2804                    smoothScrollBy(-(lastViewPixelsShowing - mExtraScroll), mScrollDuration);
2805                    post(this);
2806                } else {
2807                    final int bottom = listHeight - mExtraScroll;
2808                    final int lastViewBottom = lastViewTop + lastViewHeight;
2809                    if (bottom > lastViewBottom) {
2810                        smoothScrollBy(-(bottom - lastViewBottom), mScrollDuration);
2811                    }
2812                }
2813                break;
2814            }
2815
2816            case MOVE_OFFSET: {
2817                final int childCount = getChildCount();
2818
2819                mLastSeenPos = firstPos;
2820                final int position = mTargetPos;
2821                final int lastPos = firstPos + childCount - 1;
2822
2823                if (position < firstPos) {
2824                    smoothScrollBy(-getHeight(), mScrollDuration);
2825                    post(this);
2826                } else if (position > lastPos) {
2827                    smoothScrollBy(getHeight(), mScrollDuration);
2828                    post(this);
2829                } else {
2830                    // On-screen, just scroll.
2831                    final int targetTop = getChildAt(position - firstPos).getTop();
2832                    smoothScrollBy(targetTop - mOffsetFromTop, mScrollDuration);
2833                }
2834                break;
2835            }
2836
2837            default:
2838                break;
2839            }
2840        }
2841    }
2842
2843    /**
2844     * Smoothly scroll to the specified adapter position. The view will
2845     * scroll such that the indicated position is displayed.
2846     * @param position Scroll to this adapter position.
2847     */
2848    public void smoothScrollToPosition(int position) {
2849        if (mPositionScroller == null) {
2850            mPositionScroller = new PositionScroller();
2851        }
2852        mPositionScroller.start(position);
2853    }
2854
2855    /**
2856     * Smoothly scroll to the specified adapter position. The view will scroll
2857     * such that the indicated position is displayed <code>offset</code> pixels from
2858     * the top edge of the view. If this is impossible, (e.g. the offset would scroll
2859     * the first or last item beyond the boundaries of the list) it will get as close
2860     * as possible.
2861     *
2862     * @param position Position to scroll to
2863     * @param offset Desired distance in pixels of <code>position</code> from the top
2864     *               of the view when scrolling is finished
2865     */
2866    public void smoothScrollToPositionFromTop(int position, int offset) {
2867        if (mPositionScroller == null) {
2868            mPositionScroller = new PositionScroller();
2869        }
2870        mPositionScroller.startWithOffset(position, offset);
2871    }
2872
2873    /**
2874     * Smoothly scroll to the specified adapter position. The view will
2875     * scroll such that the indicated position is displayed, but it will
2876     * stop early if scrolling further would scroll boundPosition out of
2877     * view.
2878     * @param position Scroll to this adapter position.
2879     * @param boundPosition Do not scroll if it would move this adapter
2880     *          position out of view.
2881     */
2882    public void smoothScrollToPosition(int position, int boundPosition) {
2883        if (mPositionScroller == null) {
2884            mPositionScroller = new PositionScroller();
2885        }
2886        mPositionScroller.start(position, boundPosition);
2887    }
2888
2889    /**
2890     * Smoothly scroll by distance pixels over duration milliseconds.
2891     * @param distance Distance to scroll in pixels.
2892     * @param duration Duration of the scroll animation in milliseconds.
2893     */
2894    public void smoothScrollBy(int distance, int duration) {
2895        if (mFlingRunnable == null) {
2896            mFlingRunnable = new FlingRunnable();
2897        } else {
2898            mFlingRunnable.endFling();
2899        }
2900        mFlingRunnable.startScroll(distance, duration);
2901    }
2902
2903    /**
2904     * Allows RemoteViews to scroll relatively to a position.
2905     */
2906    void smoothScrollByOffset(int position) {
2907        int index = -1;
2908        if (position < 0) {
2909            index = getFirstVisiblePosition();
2910        } else if (position > 0) {
2911            index = getLastVisiblePosition();
2912        }
2913
2914        if (index > -1) {
2915            View child = getChildAt(index - getFirstVisiblePosition());
2916            if (child != null) {
2917                Rect visibleRect = new Rect();
2918                if (child.getGlobalVisibleRect(visibleRect)) {
2919                    // the child is partially visible
2920                    int childRectArea = child.getWidth() * child.getHeight();
2921                    int visibleRectArea = visibleRect.width() * visibleRect.height();
2922                    float visibleArea = (visibleRectArea / (float) childRectArea);
2923                    final float visibleThreshold = 0.75f;
2924                    if ((position < 0) && (visibleArea < visibleThreshold)) {
2925                        // the top index is not perceivably visible so offset
2926                        // to account for showing that top index as well
2927                        ++index;
2928                    } else if ((position > 0) && (visibleArea < visibleThreshold)) {
2929                        // the bottom index is not perceivably visible so offset
2930                        // to account for showing that bottom index as well
2931                        --index;
2932                    }
2933                }
2934                smoothScrollToPosition(Math.max(0, Math.min(getCount(), index + position)));
2935            }
2936        }
2937    }
2938
2939    private void createScrollingCache() {
2940        if (mScrollingCacheEnabled && !mCachingStarted) {
2941            setChildrenDrawnWithCacheEnabled(true);
2942            setChildrenDrawingCacheEnabled(true);
2943            mCachingStarted = true;
2944        }
2945    }
2946
2947    private void clearScrollingCache() {
2948        if (mClearScrollingCache == null) {
2949            mClearScrollingCache = new Runnable() {
2950                public void run() {
2951                    if (mCachingStarted) {
2952                        mCachingStarted = false;
2953                        setChildrenDrawnWithCacheEnabled(false);
2954                        if ((mPersistentDrawingCache & PERSISTENT_SCROLLING_CACHE) == 0) {
2955                            setChildrenDrawingCacheEnabled(false);
2956                        }
2957                        if (!isAlwaysDrawnWithCacheEnabled()) {
2958                            invalidate();
2959                        }
2960                    }
2961                }
2962            };
2963        }
2964        post(mClearScrollingCache);
2965    }
2966
2967    /**
2968     * Track a motion scroll
2969     *
2970     * @param deltaY Amount to offset mMotionView. This is the accumulated delta since the motion
2971     *        began. Positive numbers mean the user's finger is moving down the screen.
2972     * @param incrementalDeltaY Change in deltaY from the previous event.
2973     * @return true if we're already at the beginning/end of the list and have nothing to do.
2974     */
2975    boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
2976        final int childCount = getChildCount();
2977        if (childCount == 0) {
2978            return true;
2979        }
2980
2981        final int firstTop = getChildAt(0).getTop();
2982        final int lastBottom = getChildAt(childCount - 1).getBottom();
2983
2984        final Rect listPadding = mListPadding;
2985
2986         // FIXME account for grid vertical spacing too?
2987        final int spaceAbove = listPadding.top - firstTop;
2988        final int end = getHeight() - listPadding.bottom;
2989        final int spaceBelow = lastBottom - end;
2990
2991        final int height = getHeight() - mPaddingBottom - mPaddingTop;
2992        if (deltaY < 0) {
2993            deltaY = Math.max(-(height - 1), deltaY);
2994        } else {
2995            deltaY = Math.min(height - 1, deltaY);
2996        }
2997
2998        if (incrementalDeltaY < 0) {
2999            incrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY);
3000        } else {
3001            incrementalDeltaY = Math.min(height - 1, incrementalDeltaY);
3002        }
3003
3004        final int firstPosition = mFirstPosition;
3005
3006        if (firstPosition == 0 && firstTop >= listPadding.top && deltaY >= 0) {
3007            // Don't need to move views down if the top of the first position
3008            // is already visible
3009            return true;
3010        }
3011
3012        if (firstPosition + childCount == mItemCount && lastBottom <= end && deltaY <= 0) {
3013            // Don't need to move views up if the bottom of the last position
3014            // is already visible
3015            return true;
3016        }
3017
3018        final boolean down = incrementalDeltaY < 0;
3019
3020        final boolean inTouchMode = isInTouchMode();
3021        if (inTouchMode) {
3022            hideSelector();
3023        }
3024
3025        final int headerViewsCount = getHeaderViewsCount();
3026        final int footerViewsStart = mItemCount - getFooterViewsCount();
3027
3028        int start = 0;
3029        int count = 0;
3030
3031        if (down) {
3032            final int top = listPadding.top - incrementalDeltaY;
3033            for (int i = 0; i < childCount; i++) {
3034                final View child = getChildAt(i);
3035                if (child.getBottom() >= top) {
3036                    break;
3037                } else {
3038                    count++;
3039                    int position = firstPosition + i;
3040                    if (position >= headerViewsCount && position < footerViewsStart) {
3041                        mRecycler.addScrapView(child);
3042
3043                        if (ViewDebug.TRACE_RECYCLER) {
3044                            ViewDebug.trace(child,
3045                                    ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP,
3046                                    firstPosition + i, -1);
3047                        }
3048                    }
3049                }
3050            }
3051        } else {
3052            final int bottom = getHeight() - listPadding.bottom - incrementalDeltaY;
3053            for (int i = childCount - 1; i >= 0; i--) {
3054                final View child = getChildAt(i);
3055                if (child.getTop() <= bottom) {
3056                    break;
3057                } else {
3058                    start = i;
3059                    count++;
3060                    int position = firstPosition + i;
3061                    if (position >= headerViewsCount && position < footerViewsStart) {
3062                        mRecycler.addScrapView(child);
3063
3064                        if (ViewDebug.TRACE_RECYCLER) {
3065                            ViewDebug.trace(child,
3066                                    ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP,
3067                                    firstPosition + i, -1);
3068                        }
3069                    }
3070                }
3071            }
3072        }
3073
3074        mMotionViewNewTop = mMotionViewOriginalTop + deltaY;
3075
3076        mBlockLayoutRequests = true;
3077
3078        if (count > 0) {
3079            detachViewsFromParent(start, count);
3080        }
3081        offsetChildrenTopAndBottom(incrementalDeltaY);
3082
3083        if (down) {
3084            mFirstPosition += count;
3085        }
3086
3087        invalidate();
3088
3089        final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
3090        if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
3091            fillGap(down);
3092        }
3093
3094        if (!inTouchMode && mSelectedPosition != INVALID_POSITION) {
3095            final int childIndex = mSelectedPosition - mFirstPosition;
3096            if (childIndex >= 0 && childIndex < getChildCount()) {
3097                positionSelector(getChildAt(childIndex));
3098            }
3099        }
3100
3101        mBlockLayoutRequests = false;
3102
3103        invokeOnItemScrollListener();
3104        awakenScrollBars();
3105
3106        return false;
3107    }
3108
3109    /**
3110     * Returns the number of header views in the list. Header views are special views
3111     * at the top of the list that should not be recycled during a layout.
3112     *
3113     * @return The number of header views, 0 in the default implementation.
3114     */
3115    int getHeaderViewsCount() {
3116        return 0;
3117    }
3118
3119    /**
3120     * Returns the number of footer views in the list. Footer views are special views
3121     * at the bottom of the list that should not be recycled during a layout.
3122     *
3123     * @return The number of footer views, 0 in the default implementation.
3124     */
3125    int getFooterViewsCount() {
3126        return 0;
3127    }
3128
3129    /**
3130     * Fills the gap left open by a touch-scroll. During a touch scroll, children that
3131     * remain on screen are shifted and the other ones are discarded. The role of this
3132     * method is to fill the gap thus created by performing a partial layout in the
3133     * empty space.
3134     *
3135     * @param down true if the scroll is going down, false if it is going up
3136     */
3137    abstract void fillGap(boolean down);
3138
3139    void hideSelector() {
3140        if (mSelectedPosition != INVALID_POSITION) {
3141            if (mLayoutMode != LAYOUT_SPECIFIC) {
3142                mResurrectToPosition = mSelectedPosition;
3143            }
3144            if (mNextSelectedPosition >= 0 && mNextSelectedPosition != mSelectedPosition) {
3145                mResurrectToPosition = mNextSelectedPosition;
3146            }
3147            setSelectedPositionInt(INVALID_POSITION);
3148            setNextSelectedPositionInt(INVALID_POSITION);
3149            mSelectedTop = 0;
3150            mSelectorRect.setEmpty();
3151        }
3152    }
3153
3154    /**
3155     * @return A position to select. First we try mSelectedPosition. If that has been clobbered by
3156     * entering touch mode, we then try mResurrectToPosition. Values are pinned to the range
3157     * of items available in the adapter
3158     */
3159    int reconcileSelectedPosition() {
3160        int position = mSelectedPosition;
3161        if (position < 0) {
3162            position = mResurrectToPosition;
3163        }
3164        position = Math.max(0, position);
3165        position = Math.min(position, mItemCount - 1);
3166        return position;
3167    }
3168
3169    /**
3170     * Find the row closest to y. This row will be used as the motion row when scrolling
3171     *
3172     * @param y Where the user touched
3173     * @return The position of the first (or only) item in the row containing y
3174     */
3175    abstract int findMotionRow(int y);
3176
3177    /**
3178     * Find the row closest to y. This row will be used as the motion row when scrolling.
3179     *
3180     * @param y Where the user touched
3181     * @return The position of the first (or only) item in the row closest to y
3182     */
3183    int findClosestMotionRow(int y) {
3184        final int childCount = getChildCount();
3185        if (childCount == 0) {
3186            return INVALID_POSITION;
3187        }
3188
3189        final int motionRow = findMotionRow(y);
3190        return motionRow != INVALID_POSITION ? motionRow : mFirstPosition + childCount - 1;
3191    }
3192
3193    /**
3194     * Causes all the views to be rebuilt and redrawn.
3195     */
3196    public void invalidateViews() {
3197        mDataChanged = true;
3198        rememberSyncState();
3199        requestLayout();
3200        invalidate();
3201    }
3202
3203    /**
3204     * Makes the item at the supplied position selected.
3205     *
3206     * @param position the position of the new selection
3207     */
3208    abstract void setSelectionInt(int position);
3209
3210    /**
3211     * Attempt to bring the selection back if the user is switching from touch
3212     * to trackball mode
3213     * @return Whether selection was set to something.
3214     */
3215    boolean resurrectSelection() {
3216        final int childCount = getChildCount();
3217
3218        if (childCount <= 0) {
3219            return false;
3220        }
3221
3222        int selectedTop = 0;
3223        int selectedPos;
3224        int childrenTop = mListPadding.top;
3225        int childrenBottom = mBottom - mTop - mListPadding.bottom;
3226        final int firstPosition = mFirstPosition;
3227        final int toPosition = mResurrectToPosition;
3228        boolean down = true;
3229
3230        if (toPosition >= firstPosition && toPosition < firstPosition + childCount) {
3231            selectedPos = toPosition;
3232
3233            final View selected = getChildAt(selectedPos - mFirstPosition);
3234            selectedTop = selected.getTop();
3235            int selectedBottom = selected.getBottom();
3236
3237            // We are scrolled, don't get in the fade
3238            if (selectedTop < childrenTop) {
3239                selectedTop = childrenTop + getVerticalFadingEdgeLength();
3240            } else if (selectedBottom > childrenBottom) {
3241                selectedTop = childrenBottom - selected.getMeasuredHeight()
3242                        - getVerticalFadingEdgeLength();
3243            }
3244        } else {
3245            if (toPosition < firstPosition) {
3246                // Default to selecting whatever is first
3247                selectedPos = firstPosition;
3248                for (int i = 0; i < childCount; i++) {
3249                    final View v = getChildAt(i);
3250                    final int top = v.getTop();
3251
3252                    if (i == 0) {
3253                        // Remember the position of the first item
3254                        selectedTop = top;
3255                        // See if we are scrolled at all
3256                        if (firstPosition > 0 || top < childrenTop) {
3257                            // If we are scrolled, don't select anything that is
3258                            // in the fade region
3259                            childrenTop += getVerticalFadingEdgeLength();
3260                        }
3261                    }
3262                    if (top >= childrenTop) {
3263                        // Found a view whose top is fully visisble
3264                        selectedPos = firstPosition + i;
3265                        selectedTop = top;
3266                        break;
3267                    }
3268                }
3269            } else {
3270                final int itemCount = mItemCount;
3271                down = false;
3272                selectedPos = firstPosition + childCount - 1;
3273
3274                for (int i = childCount - 1; i >= 0; i--) {
3275                    final View v = getChildAt(i);
3276                    final int top = v.getTop();
3277                    final int bottom = v.getBottom();
3278
3279                    if (i == childCount - 1) {
3280                        selectedTop = top;
3281                        if (firstPosition + childCount < itemCount || bottom > childrenBottom) {
3282                            childrenBottom -= getVerticalFadingEdgeLength();
3283                        }
3284                    }
3285
3286                    if (bottom <= childrenBottom) {
3287                        selectedPos = firstPosition + i;
3288                        selectedTop = top;
3289                        break;
3290                    }
3291                }
3292            }
3293        }
3294
3295        mResurrectToPosition = INVALID_POSITION;
3296        removeCallbacks(mFlingRunnable);
3297        removeCallbacks(mPositionScroller);
3298        mTouchMode = TOUCH_MODE_REST;
3299        clearScrollingCache();
3300        mSpecificTop = selectedTop;
3301        selectedPos = lookForSelectablePosition(selectedPos, down);
3302        if (selectedPos >= firstPosition && selectedPos <= getLastVisiblePosition()) {
3303            mLayoutMode = LAYOUT_SPECIFIC;
3304            setSelectionInt(selectedPos);
3305            invokeOnItemScrollListener();
3306        } else {
3307            selectedPos = INVALID_POSITION;
3308        }
3309        reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
3310
3311        return selectedPos >= 0;
3312    }
3313
3314    @Override
3315    protected void handleDataChanged() {
3316        int count = mItemCount;
3317        if (count > 0) {
3318
3319            int newPos;
3320
3321            int selectablePos;
3322
3323            // Find the row we are supposed to sync to
3324            if (mNeedSync) {
3325                // Update this first, since setNextSelectedPositionInt inspects it
3326                mNeedSync = false;
3327
3328                if (mTranscriptMode == TRANSCRIPT_MODE_ALWAYS_SCROLL ||
3329                        (mTranscriptMode == TRANSCRIPT_MODE_NORMAL &&
3330                                mFirstPosition + getChildCount() >= mOldItemCount)) {
3331                    mLayoutMode = LAYOUT_FORCE_BOTTOM;
3332                    return;
3333                }
3334
3335                switch (mSyncMode) {
3336                case SYNC_SELECTED_POSITION:
3337                    if (isInTouchMode()) {
3338                        // We saved our state when not in touch mode. (We know this because
3339                        // mSyncMode is SYNC_SELECTED_POSITION.) Now we are trying to
3340                        // restore in touch mode. Just leave mSyncPosition as it is (possibly
3341                        // adjusting if the available range changed) and return.
3342                        mLayoutMode = LAYOUT_SYNC;
3343                        mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
3344
3345                        return;
3346                    } else {
3347                        // See if we can find a position in the new data with the same
3348                        // id as the old selection. This will change mSyncPosition.
3349                        newPos = findSyncPosition();
3350                        if (newPos >= 0) {
3351                            // Found it. Now verify that new selection is still selectable
3352                            selectablePos = lookForSelectablePosition(newPos, true);
3353                            if (selectablePos == newPos) {
3354                                // Same row id is selected
3355                                mSyncPosition = newPos;
3356
3357                                if (mSyncHeight == getHeight()) {
3358                                    // If we are at the same height as when we saved state, try
3359                                    // to restore the scroll position too.
3360                                    mLayoutMode = LAYOUT_SYNC;
3361                                } else {
3362                                    // We are not the same height as when the selection was saved, so
3363                                    // don't try to restore the exact position
3364                                    mLayoutMode = LAYOUT_SET_SELECTION;
3365                                }
3366
3367                                // Restore selection
3368                                setNextSelectedPositionInt(newPos);
3369                                return;
3370                            }
3371                        }
3372                    }
3373                    break;
3374                case SYNC_FIRST_POSITION:
3375                    // Leave mSyncPosition as it is -- just pin to available range
3376                    mLayoutMode = LAYOUT_SYNC;
3377                    mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
3378
3379                    return;
3380                }
3381            }
3382
3383            if (!isInTouchMode()) {
3384                // We couldn't find matching data -- try to use the same position
3385                newPos = getSelectedItemPosition();
3386
3387                // Pin position to the available range
3388                if (newPos >= count) {
3389                    newPos = count - 1;
3390                }
3391                if (newPos < 0) {
3392                    newPos = 0;
3393                }
3394
3395                // Make sure we select something selectable -- first look down
3396                selectablePos = lookForSelectablePosition(newPos, true);
3397
3398                if (selectablePos >= 0) {
3399                    setNextSelectedPositionInt(selectablePos);
3400                    return;
3401                } else {
3402                    // Looking down didn't work -- try looking up
3403                    selectablePos = lookForSelectablePosition(newPos, false);
3404                    if (selectablePos >= 0) {
3405                        setNextSelectedPositionInt(selectablePos);
3406                        return;
3407                    }
3408                }
3409            } else {
3410
3411                // We already know where we want to resurrect the selection
3412                if (mResurrectToPosition >= 0) {
3413                    return;
3414                }
3415            }
3416
3417        }
3418
3419        // Nothing is selected. Give up and reset everything.
3420        mLayoutMode = mStackFromBottom ? LAYOUT_FORCE_BOTTOM : LAYOUT_FORCE_TOP;
3421        mSelectedPosition = INVALID_POSITION;
3422        mSelectedRowId = INVALID_ROW_ID;
3423        mNextSelectedPosition = INVALID_POSITION;
3424        mNextSelectedRowId = INVALID_ROW_ID;
3425        mNeedSync = false;
3426        checkSelectionChanged();
3427    }
3428
3429    @Override
3430    protected void onDisplayHint(int hint) {
3431        super.onDisplayHint(hint);
3432        switch (hint) {
3433            case INVISIBLE:
3434                if (mPopup != null && mPopup.isShowing()) {
3435                    dismissPopup();
3436                }
3437                break;
3438            case VISIBLE:
3439                if (mFiltered && mPopup != null && !mPopup.isShowing()) {
3440                    showPopup();
3441                }
3442                break;
3443        }
3444        mPopupHidden = hint == INVISIBLE;
3445    }
3446
3447    /**
3448     * Removes the filter window
3449     */
3450    private void dismissPopup() {
3451        if (mPopup != null) {
3452            mPopup.dismiss();
3453        }
3454    }
3455
3456    /**
3457     * Shows the filter window
3458     */
3459    private void showPopup() {
3460        // Make sure we have a window before showing the popup
3461        if (getWindowVisibility() == View.VISIBLE) {
3462            createTextFilter(true);
3463            positionPopup();
3464            // Make sure we get focus if we are showing the popup
3465            checkFocus();
3466        }
3467    }
3468
3469    private void positionPopup() {
3470        int screenHeight = getResources().getDisplayMetrics().heightPixels;
3471        final int[] xy = new int[2];
3472        getLocationOnScreen(xy);
3473        // TODO: The 20 below should come from the theme
3474        // TODO: And the gravity should be defined in the theme as well
3475        final int bottomGap = screenHeight - xy[1] - getHeight() + (int) (mDensityScale * 20);
3476        if (!mPopup.isShowing()) {
3477            mPopup.showAtLocation(this, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL,
3478                    xy[0], bottomGap);
3479        } else {
3480            mPopup.update(xy[0], bottomGap, -1, -1);
3481        }
3482    }
3483
3484    /**
3485     * What is the distance between the source and destination rectangles given the direction of
3486     * focus navigation between them? The direction basically helps figure out more quickly what is
3487     * self evident by the relationship between the rects...
3488     *
3489     * @param source the source rectangle
3490     * @param dest the destination rectangle
3491     * @param direction the direction
3492     * @return the distance between the rectangles
3493     */
3494    static int getDistance(Rect source, Rect dest, int direction) {
3495        int sX, sY; // source x, y
3496        int dX, dY; // dest x, y
3497        switch (direction) {
3498        case View.FOCUS_RIGHT:
3499            sX = source.right;
3500            sY = source.top + source.height() / 2;
3501            dX = dest.left;
3502            dY = dest.top + dest.height() / 2;
3503            break;
3504        case View.FOCUS_DOWN:
3505            sX = source.left + source.width() / 2;
3506            sY = source.bottom;
3507            dX = dest.left + dest.width() / 2;
3508            dY = dest.top;
3509            break;
3510        case View.FOCUS_LEFT:
3511            sX = source.left;
3512            sY = source.top + source.height() / 2;
3513            dX = dest.right;
3514            dY = dest.top + dest.height() / 2;
3515            break;
3516        case View.FOCUS_UP:
3517            sX = source.left + source.width() / 2;
3518            sY = source.top;
3519            dX = dest.left + dest.width() / 2;
3520            dY = dest.bottom;
3521            break;
3522        default:
3523            throw new IllegalArgumentException("direction must be one of "
3524                    + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
3525        }
3526        int deltaX = dX - sX;
3527        int deltaY = dY - sY;
3528        return deltaY * deltaY + deltaX * deltaX;
3529    }
3530
3531    @Override
3532    protected boolean isInFilterMode() {
3533        return mFiltered;
3534    }
3535
3536    /**
3537     * Sends a key to the text filter window
3538     *
3539     * @param keyCode The keycode for the event
3540     * @param event The actual key event
3541     *
3542     * @return True if the text filter handled the event, false otherwise.
3543     */
3544    boolean sendToTextFilter(int keyCode, int count, KeyEvent event) {
3545        if (!acceptFilter()) {
3546            return false;
3547        }
3548
3549        boolean handled = false;
3550        boolean okToSend = true;
3551        switch (keyCode) {
3552        case KeyEvent.KEYCODE_DPAD_UP:
3553        case KeyEvent.KEYCODE_DPAD_DOWN:
3554        case KeyEvent.KEYCODE_DPAD_LEFT:
3555        case KeyEvent.KEYCODE_DPAD_RIGHT:
3556        case KeyEvent.KEYCODE_DPAD_CENTER:
3557        case KeyEvent.KEYCODE_ENTER:
3558            okToSend = false;
3559            break;
3560        case KeyEvent.KEYCODE_BACK:
3561            if (mFiltered && mPopup != null && mPopup.isShowing()) {
3562                if (event.getAction() == KeyEvent.ACTION_DOWN
3563                        && event.getRepeatCount() == 0) {
3564                    getKeyDispatcherState().startTracking(event, this);
3565                    handled = true;
3566                } else if (event.getAction() == KeyEvent.ACTION_UP
3567                        && event.isTracking() && !event.isCanceled()) {
3568                    handled = true;
3569                    mTextFilter.setText("");
3570                }
3571            }
3572            okToSend = false;
3573            break;
3574        case KeyEvent.KEYCODE_SPACE:
3575            // Only send spaces once we are filtered
3576            okToSend = mFiltered;
3577            break;
3578        }
3579
3580        if (okToSend) {
3581            createTextFilter(true);
3582
3583            KeyEvent forwardEvent = event;
3584            if (forwardEvent.getRepeatCount() > 0) {
3585                forwardEvent = KeyEvent.changeTimeRepeat(event, event.getEventTime(), 0);
3586            }
3587
3588            int action = event.getAction();
3589            switch (action) {
3590                case KeyEvent.ACTION_DOWN:
3591                    handled = mTextFilter.onKeyDown(keyCode, forwardEvent);
3592                    break;
3593
3594                case KeyEvent.ACTION_UP:
3595                    handled = mTextFilter.onKeyUp(keyCode, forwardEvent);
3596                    break;
3597
3598                case KeyEvent.ACTION_MULTIPLE:
3599                    handled = mTextFilter.onKeyMultiple(keyCode, count, event);
3600                    break;
3601            }
3602        }
3603        return handled;
3604    }
3605
3606    /**
3607     * Return an InputConnection for editing of the filter text.
3608     */
3609    @Override
3610    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
3611        if (isTextFilterEnabled()) {
3612            // XXX we need to have the text filter created, so we can get an
3613            // InputConnection to proxy to.  Unfortunately this means we pretty
3614            // much need to make it as soon as a list view gets focus.
3615            createTextFilter(false);
3616            if (mPublicInputConnection == null) {
3617                mDefInputConnection = new BaseInputConnection(this, false);
3618                mPublicInputConnection = new InputConnectionWrapper(
3619                        mTextFilter.onCreateInputConnection(outAttrs), true) {
3620                    @Override
3621                    public boolean reportFullscreenMode(boolean enabled) {
3622                        // Use our own input connection, since it is
3623                        // the "real" one the IME is talking with.
3624                        return mDefInputConnection.reportFullscreenMode(enabled);
3625                    }
3626
3627                    @Override
3628                    public boolean performEditorAction(int editorAction) {
3629                        // The editor is off in its own window; we need to be
3630                        // the one that does this.
3631                        if (editorAction == EditorInfo.IME_ACTION_DONE) {
3632                            InputMethodManager imm = (InputMethodManager)
3633                                    getContext().getSystemService(
3634                                            Context.INPUT_METHOD_SERVICE);
3635                            if (imm != null) {
3636                                imm.hideSoftInputFromWindow(getWindowToken(), 0);
3637                            }
3638                            return true;
3639                        }
3640                        return false;
3641                    }
3642
3643                    @Override
3644                    public boolean sendKeyEvent(KeyEvent event) {
3645                        // Use our own input connection, since the filter
3646                        // text view may not be shown in a window so has
3647                        // no ViewRoot to dispatch events with.
3648                        return mDefInputConnection.sendKeyEvent(event);
3649                    }
3650                };
3651            }
3652            outAttrs.inputType = EditorInfo.TYPE_CLASS_TEXT
3653                    | EditorInfo.TYPE_TEXT_VARIATION_FILTER;
3654            outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE;
3655            return mPublicInputConnection;
3656        }
3657        return null;
3658    }
3659
3660    /**
3661     * For filtering we proxy an input connection to an internal text editor,
3662     * and this allows the proxying to happen.
3663     */
3664    @Override
3665    public boolean checkInputConnectionProxy(View view) {
3666        return view == mTextFilter;
3667    }
3668
3669    /**
3670     * Creates the window for the text filter and populates it with an EditText field;
3671     *
3672     * @param animateEntrance true if the window should appear with an animation
3673     */
3674    private void createTextFilter(boolean animateEntrance) {
3675        if (mPopup == null) {
3676            Context c = getContext();
3677            PopupWindow p = new PopupWindow(c);
3678            LayoutInflater layoutInflater = (LayoutInflater)
3679                    c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
3680            mTextFilter = (EditText) layoutInflater.inflate(
3681                    com.android.internal.R.layout.typing_filter, null);
3682            // For some reason setting this as the "real" input type changes
3683            // the text view in some way that it doesn't work, and I don't
3684            // want to figure out why this is.
3685            mTextFilter.setRawInputType(EditorInfo.TYPE_CLASS_TEXT
3686                    | EditorInfo.TYPE_TEXT_VARIATION_FILTER);
3687            mTextFilter.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI);
3688            mTextFilter.addTextChangedListener(this);
3689            p.setFocusable(false);
3690            p.setTouchable(false);
3691            p.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
3692            p.setContentView(mTextFilter);
3693            p.setWidth(LayoutParams.WRAP_CONTENT);
3694            p.setHeight(LayoutParams.WRAP_CONTENT);
3695            p.setBackgroundDrawable(null);
3696            mPopup = p;
3697            getViewTreeObserver().addOnGlobalLayoutListener(this);
3698            mGlobalLayoutListenerAddedFilter = true;
3699        }
3700        if (animateEntrance) {
3701            mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilter);
3702        } else {
3703            mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilterRestore);
3704        }
3705    }
3706
3707    /**
3708     * Clear the text filter.
3709     */
3710    public void clearTextFilter() {
3711        if (mFiltered) {
3712            mTextFilter.setText("");
3713            mFiltered = false;
3714            if (mPopup != null && mPopup.isShowing()) {
3715                dismissPopup();
3716            }
3717        }
3718    }
3719
3720    /**
3721     * Returns if the ListView currently has a text filter.
3722     */
3723    public boolean hasTextFilter() {
3724        return mFiltered;
3725    }
3726
3727    public void onGlobalLayout() {
3728        if (isShown()) {
3729            // Show the popup if we are filtered
3730            if (mFiltered && mPopup != null && !mPopup.isShowing() && !mPopupHidden) {
3731                showPopup();
3732            }
3733        } else {
3734            // Hide the popup when we are no longer visible
3735            if (mPopup != null && mPopup.isShowing()) {
3736                dismissPopup();
3737            }
3738        }
3739
3740    }
3741
3742    /**
3743     * For our text watcher that is associated with the text filter.  Does
3744     * nothing.
3745     */
3746    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
3747    }
3748
3749    /**
3750     * For our text watcher that is associated with the text filter. Performs
3751     * the actual filtering as the text changes, and takes care of hiding and
3752     * showing the popup displaying the currently entered filter text.
3753     */
3754    public void onTextChanged(CharSequence s, int start, int before, int count) {
3755        if (mPopup != null && isTextFilterEnabled()) {
3756            int length = s.length();
3757            boolean showing = mPopup.isShowing();
3758            if (!showing && length > 0) {
3759                // Show the filter popup if necessary
3760                showPopup();
3761                mFiltered = true;
3762            } else if (showing && length == 0) {
3763                // Remove the filter popup if the user has cleared all text
3764                dismissPopup();
3765                mFiltered = false;
3766            }
3767            if (mAdapter instanceof Filterable) {
3768                Filter f = ((Filterable) mAdapter).getFilter();
3769                // Filter should not be null when we reach this part
3770                if (f != null) {
3771                    f.filter(s, this);
3772                } else {
3773                    throw new IllegalStateException("You cannot call onTextChanged with a non "
3774                            + "filterable adapter");
3775                }
3776            }
3777        }
3778    }
3779
3780    /**
3781     * For our text watcher that is associated with the text filter.  Does
3782     * nothing.
3783     */
3784    public void afterTextChanged(Editable s) {
3785    }
3786
3787    public void onFilterComplete(int count) {
3788        if (mSelectedPosition < 0 && count > 0) {
3789            mResurrectToPosition = INVALID_POSITION;
3790            resurrectSelection();
3791        }
3792    }
3793
3794    @Override
3795    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
3796        return new LayoutParams(p);
3797    }
3798
3799    @Override
3800    public LayoutParams generateLayoutParams(AttributeSet attrs) {
3801        return new AbsListView.LayoutParams(getContext(), attrs);
3802    }
3803
3804    @Override
3805    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
3806        return p instanceof AbsListView.LayoutParams;
3807    }
3808
3809    /**
3810     * Puts the list or grid into transcript mode. In this mode the list or grid will always scroll
3811     * to the bottom to show new items.
3812     *
3813     * @param mode the transcript mode to set
3814     *
3815     * @see #TRANSCRIPT_MODE_DISABLED
3816     * @see #TRANSCRIPT_MODE_NORMAL
3817     * @see #TRANSCRIPT_MODE_ALWAYS_SCROLL
3818     */
3819    public void setTranscriptMode(int mode) {
3820        mTranscriptMode = mode;
3821    }
3822
3823    /**
3824     * Returns the current transcript mode.
3825     *
3826     * @return {@link #TRANSCRIPT_MODE_DISABLED}, {@link #TRANSCRIPT_MODE_NORMAL} or
3827     *         {@link #TRANSCRIPT_MODE_ALWAYS_SCROLL}
3828     */
3829    public int getTranscriptMode() {
3830        return mTranscriptMode;
3831    }
3832
3833    @Override
3834    public int getSolidColor() {
3835        return mCacheColorHint;
3836    }
3837
3838    /**
3839     * When set to a non-zero value, the cache color hint indicates that this list is always drawn
3840     * on top of a solid, single-color, opaque background
3841     *
3842     * @param color The background color
3843     */
3844    public void setCacheColorHint(int color) {
3845        if (color != mCacheColorHint) {
3846            mCacheColorHint = color;
3847            int count = getChildCount();
3848            for (int i = 0; i < count; i++) {
3849                getChildAt(i).setDrawingCacheBackgroundColor(color);
3850            }
3851            mRecycler.setCacheColorHint(color);
3852        }
3853    }
3854
3855    /**
3856     * When set to a non-zero value, the cache color hint indicates that this list is always drawn
3857     * on top of a solid, single-color, opaque background
3858     *
3859     * @return The cache color hint
3860     */
3861    public int getCacheColorHint() {
3862        return mCacheColorHint;
3863    }
3864
3865    /**
3866     * Move all views (excluding headers and footers) held by this AbsListView into the supplied
3867     * List. This includes views displayed on the screen as well as views stored in AbsListView's
3868     * internal view recycler.
3869     *
3870     * @param views A list into which to put the reclaimed views
3871     */
3872    public void reclaimViews(List<View> views) {
3873        int childCount = getChildCount();
3874        RecyclerListener listener = mRecycler.mRecyclerListener;
3875
3876        // Reclaim views on screen
3877        for (int i = 0; i < childCount; i++) {
3878            View child = getChildAt(i);
3879            AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
3880            // Don't reclaim header or footer views, or views that should be ignored
3881            if (lp != null && mRecycler.shouldRecycleViewType(lp.viewType)) {
3882                views.add(child);
3883                if (listener != null) {
3884                    // Pretend they went through the scrap heap
3885                    listener.onMovedToScrapHeap(child);
3886                }
3887            }
3888        }
3889        mRecycler.reclaimScrapViews(views);
3890        removeAllViewsInLayout();
3891    }
3892
3893    /**
3894     * @hide
3895     */
3896    @Override
3897    protected boolean onConsistencyCheck(int consistency) {
3898        boolean result = super.onConsistencyCheck(consistency);
3899
3900        final boolean checkLayout = (consistency & ViewDebug.CONSISTENCY_LAYOUT) != 0;
3901
3902        if (checkLayout) {
3903            // The active recycler must be empty
3904            final View[] activeViews = mRecycler.mActiveViews;
3905            int count = activeViews.length;
3906            for (int i = 0; i < count; i++) {
3907                if (activeViews[i] != null) {
3908                    result = false;
3909                    Log.d(ViewDebug.CONSISTENCY_LOG_TAG,
3910                            "AbsListView " + this + " has a view in its active recycler: " +
3911                                    activeViews[i]);
3912                }
3913            }
3914
3915            // All views in the recycler must NOT be on screen and must NOT have a parent
3916            final ArrayList<View> scrap = mRecycler.mCurrentScrap;
3917            if (!checkScrap(scrap)) result = false;
3918            final ArrayList<View>[] scraps = mRecycler.mScrapViews;
3919            count = scraps.length;
3920            for (int i = 0; i < count; i++) {
3921                if (!checkScrap(scraps[i])) result = false;
3922            }
3923        }
3924
3925        return result;
3926    }
3927
3928    private boolean checkScrap(ArrayList<View> scrap) {
3929        if (scrap == null) return true;
3930        boolean result = true;
3931
3932        final int count = scrap.size();
3933        for (int i = 0; i < count; i++) {
3934            final View view = scrap.get(i);
3935            if (view.getParent() != null) {
3936                result = false;
3937                Log.d(ViewDebug.CONSISTENCY_LOG_TAG, "AbsListView " + this +
3938                        " has a view in its scrap heap still attached to a parent: " + view);
3939            }
3940            if (indexOfChild(view) >= 0) {
3941                result = false;
3942                Log.d(ViewDebug.CONSISTENCY_LOG_TAG, "AbsListView " + this +
3943                        " has a view in its scrap heap that is also a direct child: " + view);
3944            }
3945        }
3946
3947        return result;
3948    }
3949
3950    /**
3951     * Sets up this AbsListView to use a remote views adapter which connects to a RemoteViewsService
3952     * through the specified intent.
3953     * @param intent the intent used to identify the RemoteViewsService for the adapter to connect to.
3954     */
3955    public void setRemoteViewsAdapter(Intent intent) {
3956        mRemoteAdapter = new RemoteViewsAdapter(getContext(), intent, this);
3957    }
3958
3959    /**
3960     * Called back when the adapter connects to the RemoteViewsService.
3961     */
3962    public void onRemoteAdapterConnected() {
3963        if (mRemoteAdapter != mAdapter) {
3964            setAdapter(mRemoteAdapter);
3965        }
3966    }
3967
3968    /**
3969     * Called back when the adapter disconnects from the RemoteViewsService.
3970     */
3971    public void onRemoteAdapterDisconnected() {
3972        if (mRemoteAdapter == mAdapter) {
3973            mRemoteAdapter = null;
3974            setAdapter(null);
3975        }
3976    }
3977
3978    /**
3979     * Sets the recycler listener to be notified whenever a View is set aside in
3980     * the recycler for later reuse. This listener can be used to free resources
3981     * associated to the View.
3982     *
3983     * @param listener The recycler listener to be notified of views set aside
3984     *        in the recycler.
3985     *
3986     * @see android.widget.AbsListView.RecycleBin
3987     * @see android.widget.AbsListView.RecyclerListener
3988     */
3989    public void setRecyclerListener(RecyclerListener listener) {
3990        mRecycler.mRecyclerListener = listener;
3991    }
3992
3993    /**
3994     * AbsListView extends LayoutParams to provide a place to hold the view type.
3995     */
3996    public static class LayoutParams extends ViewGroup.LayoutParams {
3997        /**
3998         * View type for this view, as returned by
3999         * {@link android.widget.Adapter#getItemViewType(int) }
4000         */
4001        @ViewDebug.ExportedProperty(mapping = {
4002            @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_IGNORE, to = "ITEM_VIEW_TYPE_IGNORE"),
4003            @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_HEADER_OR_FOOTER, to = "ITEM_VIEW_TYPE_HEADER_OR_FOOTER")
4004        })
4005        int viewType;
4006
4007        /**
4008         * When this boolean is set, the view has been added to the AbsListView
4009         * at least once. It is used to know whether headers/footers have already
4010         * been added to the list view and whether they should be treated as
4011         * recycled views or not.
4012         */
4013        @ViewDebug.ExportedProperty
4014        boolean recycledHeaderFooter;
4015
4016        /**
4017         * When an AbsListView is measured with an AT_MOST measure spec, it needs
4018         * to obtain children views to measure itself. When doing so, the children
4019         * are not attached to the window, but put in the recycler which assumes
4020         * they've been attached before. Setting this flag will force the reused
4021         * view to be attached to the window rather than just attached to the
4022         * parent.
4023         */
4024        @ViewDebug.ExportedProperty
4025        boolean forceAdd;
4026
4027        public LayoutParams(Context c, AttributeSet attrs) {
4028            super(c, attrs);
4029        }
4030
4031        public LayoutParams(int w, int h) {
4032            super(w, h);
4033        }
4034
4035        public LayoutParams(int w, int h, int viewType) {
4036            super(w, h);
4037            this.viewType = viewType;
4038        }
4039
4040        public LayoutParams(ViewGroup.LayoutParams source) {
4041            super(source);
4042        }
4043    }
4044
4045    /**
4046     * A RecyclerListener is used to receive a notification whenever a View is placed
4047     * inside the RecycleBin's scrap heap. This listener is used to free resources
4048     * associated to Views placed in the RecycleBin.
4049     *
4050     * @see android.widget.AbsListView.RecycleBin
4051     * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
4052     */
4053    public static interface RecyclerListener {
4054        /**
4055         * Indicates that the specified View was moved into the recycler's scrap heap.
4056         * The view is not displayed on screen any more and any expensive resource
4057         * associated with the view should be discarded.
4058         *
4059         * @param view
4060         */
4061        void onMovedToScrapHeap(View view);
4062    }
4063
4064    /**
4065     * The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of
4066     * storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the
4067     * start of a layout. By construction, they are displaying current information. At the end of
4068     * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that
4069     * could potentially be used by the adapter to avoid allocating views unnecessarily.
4070     *
4071     * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
4072     * @see android.widget.AbsListView.RecyclerListener
4073     */
4074    class RecycleBin {
4075        private RecyclerListener mRecyclerListener;
4076
4077        /**
4078         * The position of the first view stored in mActiveViews.
4079         */
4080        private int mFirstActivePosition;
4081
4082        /**
4083         * Views that were on screen at the start of layout. This array is populated at the start of
4084         * layout, and at the end of layout all view in mActiveViews are moved to mScrapViews.
4085         * Views in mActiveViews represent a contiguous range of Views, with position of the first
4086         * view store in mFirstActivePosition.
4087         */
4088        private View[] mActiveViews = new View[0];
4089
4090        /**
4091         * Unsorted views that can be used by the adapter as a convert view.
4092         */
4093        private ArrayList<View>[] mScrapViews;
4094
4095        private int mViewTypeCount;
4096
4097        private ArrayList<View> mCurrentScrap;
4098
4099        public void setViewTypeCount(int viewTypeCount) {
4100            if (viewTypeCount < 1) {
4101                throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
4102            }
4103            //noinspection unchecked
4104            ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
4105            for (int i = 0; i < viewTypeCount; i++) {
4106                scrapViews[i] = new ArrayList<View>();
4107            }
4108            mViewTypeCount = viewTypeCount;
4109            mCurrentScrap = scrapViews[0];
4110            mScrapViews = scrapViews;
4111        }
4112
4113        public void markChildrenDirty() {
4114            if (mViewTypeCount == 1) {
4115                final ArrayList<View> scrap = mCurrentScrap;
4116                final int scrapCount = scrap.size();
4117                for (int i = 0; i < scrapCount; i++) {
4118                    scrap.get(i).forceLayout();
4119                }
4120            } else {
4121                final int typeCount = mViewTypeCount;
4122                for (int i = 0; i < typeCount; i++) {
4123                    final ArrayList<View> scrap = mScrapViews[i];
4124                    final int scrapCount = scrap.size();
4125                    for (int j = 0; j < scrapCount; j++) {
4126                        scrap.get(j).forceLayout();
4127                    }
4128                }
4129            }
4130        }
4131
4132        public boolean shouldRecycleViewType(int viewType) {
4133            return viewType >= 0;
4134        }
4135
4136        /**
4137         * Clears the scrap heap.
4138         */
4139        void clear() {
4140            if (mViewTypeCount == 1) {
4141                final ArrayList<View> scrap = mCurrentScrap;
4142                final int scrapCount = scrap.size();
4143                for (int i = 0; i < scrapCount; i++) {
4144                    removeDetachedView(scrap.remove(scrapCount - 1 - i), false);
4145                }
4146            } else {
4147                final int typeCount = mViewTypeCount;
4148                for (int i = 0; i < typeCount; i++) {
4149                    final ArrayList<View> scrap = mScrapViews[i];
4150                    final int scrapCount = scrap.size();
4151                    for (int j = 0; j < scrapCount; j++) {
4152                        removeDetachedView(scrap.remove(scrapCount - 1 - j), false);
4153                    }
4154                }
4155            }
4156        }
4157
4158        /**
4159         * Fill ActiveViews with all of the children of the AbsListView.
4160         *
4161         * @param childCount The minimum number of views mActiveViews should hold
4162         * @param firstActivePosition The position of the first view that will be stored in
4163         *        mActiveViews
4164         */
4165        void fillActiveViews(int childCount, int firstActivePosition) {
4166            if (mActiveViews.length < childCount) {
4167                mActiveViews = new View[childCount];
4168            }
4169            mFirstActivePosition = firstActivePosition;
4170
4171            final View[] activeViews = mActiveViews;
4172            for (int i = 0; i < childCount; i++) {
4173                View child = getChildAt(i);
4174                AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
4175                // Don't put header or footer views into the scrap heap
4176                if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
4177                    // Note:  We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views.
4178                    //        However, we will NOT place them into scrap views.
4179                    activeViews[i] = child;
4180                }
4181            }
4182        }
4183
4184        /**
4185         * Get the view corresponding to the specified position. The view will be removed from
4186         * mActiveViews if it is found.
4187         *
4188         * @param position The position to look up in mActiveViews
4189         * @return The view if it is found, null otherwise
4190         */
4191        View getActiveView(int position) {
4192            int index = position - mFirstActivePosition;
4193            final View[] activeViews = mActiveViews;
4194            if (index >=0 && index < activeViews.length) {
4195                final View match = activeViews[index];
4196                activeViews[index] = null;
4197                return match;
4198            }
4199            return null;
4200        }
4201
4202        /**
4203         * @return A view from the ScrapViews collection. These are unordered.
4204         */
4205        View getScrapView(int position) {
4206            ArrayList<View> scrapViews;
4207            if (mViewTypeCount == 1) {
4208                scrapViews = mCurrentScrap;
4209                int size = scrapViews.size();
4210                if (size > 0) {
4211                    return scrapViews.remove(size - 1);
4212                } else {
4213                    return null;
4214                }
4215            } else {
4216                int whichScrap = mAdapter.getItemViewType(position);
4217                if (whichScrap >= 0 && whichScrap < mScrapViews.length) {
4218                    scrapViews = mScrapViews[whichScrap];
4219                    int size = scrapViews.size();
4220                    if (size > 0) {
4221                        return scrapViews.remove(size - 1);
4222                    }
4223                }
4224            }
4225            return null;
4226        }
4227
4228        /**
4229         * Put a view into the ScapViews list. These views are unordered.
4230         *
4231         * @param scrap The view to add
4232         */
4233        void addScrapView(View scrap) {
4234            AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
4235            if (lp == null) {
4236                return;
4237            }
4238
4239            // Don't put header or footer views or views that should be ignored
4240            // into the scrap heap
4241            int viewType = lp.viewType;
4242            if (!shouldRecycleViewType(viewType)) {
4243                if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
4244                    removeDetachedView(scrap, false);
4245                }
4246                return;
4247            }
4248
4249            if (mViewTypeCount == 1) {
4250                scrap.dispatchStartTemporaryDetach();
4251                mCurrentScrap.add(scrap);
4252            } else {
4253                scrap.dispatchStartTemporaryDetach();
4254                mScrapViews[viewType].add(scrap);
4255            }
4256
4257            if (mRecyclerListener != null) {
4258                mRecyclerListener.onMovedToScrapHeap(scrap);
4259            }
4260        }
4261
4262        /**
4263         * Move all views remaining in mActiveViews to mScrapViews.
4264         */
4265        void scrapActiveViews() {
4266            final View[] activeViews = mActiveViews;
4267            final boolean hasListener = mRecyclerListener != null;
4268            final boolean multipleScraps = mViewTypeCount > 1;
4269
4270            ArrayList<View> scrapViews = mCurrentScrap;
4271            final int count = activeViews.length;
4272            for (int i = count - 1; i >= 0; i--) {
4273                final View victim = activeViews[i];
4274                if (victim != null) {
4275                    int whichScrap = ((AbsListView.LayoutParams) victim.getLayoutParams()).viewType;
4276
4277                    activeViews[i] = null;
4278
4279                    if (!shouldRecycleViewType(whichScrap)) {
4280                        // Do not move views that should be ignored
4281                        if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
4282                            removeDetachedView(victim, false);
4283                        }
4284                        continue;
4285                    }
4286
4287                    if (multipleScraps) {
4288                        scrapViews = mScrapViews[whichScrap];
4289                    }
4290                    victim.dispatchStartTemporaryDetach();
4291                    scrapViews.add(victim);
4292
4293                    if (hasListener) {
4294                        mRecyclerListener.onMovedToScrapHeap(victim);
4295                    }
4296
4297                    if (ViewDebug.TRACE_RECYCLER) {
4298                        ViewDebug.trace(victim,
4299                                ViewDebug.RecyclerTraceType.MOVE_FROM_ACTIVE_TO_SCRAP_HEAP,
4300                                mFirstActivePosition + i, -1);
4301                    }
4302                }
4303            }
4304
4305            pruneScrapViews();
4306        }
4307
4308        /**
4309         * Makes sure that the size of mScrapViews does not exceed the size of mActiveViews.
4310         * (This can happen if an adapter does not recycle its views).
4311         */
4312        private void pruneScrapViews() {
4313            final int maxViews = mActiveViews.length;
4314            final int viewTypeCount = mViewTypeCount;
4315            final ArrayList<View>[] scrapViews = mScrapViews;
4316            for (int i = 0; i < viewTypeCount; ++i) {
4317                final ArrayList<View> scrapPile = scrapViews[i];
4318                int size = scrapPile.size();
4319                final int extras = size - maxViews;
4320                size--;
4321                for (int j = 0; j < extras; j++) {
4322                    removeDetachedView(scrapPile.remove(size--), false);
4323                }
4324            }
4325        }
4326
4327        /**
4328         * Puts all views in the scrap heap into the supplied list.
4329         */
4330        void reclaimScrapViews(List<View> views) {
4331            if (mViewTypeCount == 1) {
4332                views.addAll(mCurrentScrap);
4333            } else {
4334                final int viewTypeCount = mViewTypeCount;
4335                final ArrayList<View>[] scrapViews = mScrapViews;
4336                for (int i = 0; i < viewTypeCount; ++i) {
4337                    final ArrayList<View> scrapPile = scrapViews[i];
4338                    views.addAll(scrapPile);
4339                }
4340            }
4341        }
4342
4343        /**
4344         * Updates the cache color hint of all known views.
4345         *
4346         * @param color The new cache color hint.
4347         */
4348        void setCacheColorHint(int color) {
4349            if (mViewTypeCount == 1) {
4350                final ArrayList<View> scrap = mCurrentScrap;
4351                final int scrapCount = scrap.size();
4352                for (int i = 0; i < scrapCount; i++) {
4353                    scrap.get(i).setDrawingCacheBackgroundColor(color);
4354                }
4355            } else {
4356                final int typeCount = mViewTypeCount;
4357                for (int i = 0; i < typeCount; i++) {
4358                    final ArrayList<View> scrap = mScrapViews[i];
4359                    final int scrapCount = scrap.size();
4360                    for (int j = 0; j < scrapCount; j++) {
4361                        scrap.get(j).setDrawingCacheBackgroundColor(color);
4362                    }
4363                }
4364            }
4365            // Just in case this is called during a layout pass
4366            final View[] activeViews = mActiveViews;
4367            final int count = activeViews.length;
4368            for (int i = 0; i < count; ++i) {
4369                final View victim = activeViews[i];
4370                if (victim != null) {
4371                    victim.setDrawingCacheBackgroundColor(color);
4372                }
4373            }
4374        }
4375    }
4376}
4377