1f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang/*
2f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang * Copyright (C) 2012 Google Inc.
3f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang * Licensed to The Android Open Source Project.
4f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang *
5f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang * Licensed under the Apache License, Version 2.0 (the "License");
6f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang * you may not use this file except in compliance with the License.
7f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang * You may obtain a copy of the License at
8f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang *
9f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang *      http://www.apache.org/licenses/LICENSE-2.0
10f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang *
11f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang * Unless required by applicable law or agreed to in writing, software
12f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang * distributed under the License is distributed on an "AS IS" BASIS,
13f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang * See the License for the specific language governing permissions and
15f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang * limitations under the License.
16f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang */
17f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
185ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huangpackage com.android.mail.browse;
19f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
20f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport android.content.Context;
218f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huangimport android.content.res.Configuration;
2246dfba6160b55a582b344328067e3dafeb881dd9Andy Huangimport android.database.DataSetObserver;
23c966a8a65a75559f677574c2a53cb9d43490f04eJin Caoimport android.graphics.Rect;
242fd167d6131482da984768b5ee75cefa32ed3e32Andrew Sappersteinimport android.support.v4.view.ViewCompat;
25f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport android.util.AttributeSet;
2665fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huangimport android.util.SparseArray;
27adbf3e8cadb66666f307352b72537fbac57b916fAndy Huangimport android.view.Gravity;
28bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huangimport android.view.MotionEvent;
29f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport android.view.View;
30bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huangimport android.view.ViewConfiguration;
31f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport android.view.ViewGroup;
32f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport android.webkit.WebView;
33b5078b287b1cec38817e342ff054ea901d199329Andy Huangimport android.widget.ListView;
34bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huangimport android.widget.ScrollView;
35f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
36b5078b287b1cec38817e342ff054ea901d199329Andy Huangimport com.android.mail.R;
375ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huangimport com.android.mail.browse.ScrollNotifier.ScrollListener;
388f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huangimport com.android.mail.providers.UIProvider;
39632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huangimport com.android.mail.ui.ConversationViewFragment;
407bdc3750454efe59617b7df945eadd7e59bee954Andy Huangimport com.android.mail.utils.DequeMap;
4131c38a8247b4583ac1cc506acf8454d8922ee491Andy Huangimport com.android.mail.utils.InputSmoother;
42f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport com.android.mail.utils.LogUtils;
4347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huangimport com.google.common.collect.Lists;
44c966a8a65a75559f677574c2a53cb9d43490f04eJin Caoimport com.google.common.collect.Sets;
4547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
4647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huangimport java.util.List;
47c966a8a65a75559f677574c2a53cb9d43490f04eJin Caoimport java.util.Set;
48b5078b287b1cec38817e342ff054ea901d199329Andy Huang
49f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang/**
50b5078b287b1cec38817e342ff054ea901d199329Andy Huang * A specialized ViewGroup container for conversation view. It is designed to contain a single
51b5078b287b1cec38817e342ff054ea901d199329Andy Huang * {@link WebView} and a number of overlay views that draw on top of the WebView. In the Mail app,
52b5078b287b1cec38817e342ff054ea901d199329Andy Huang * the WebView contains all HTML message bodies in a conversation, and the overlay views are the
53b5078b287b1cec38817e342ff054ea901d199329Andy Huang * subject view, message headers, and attachment views. The WebView does all scroll handling, and
54b5078b287b1cec38817e342ff054ea901d199329Andy Huang * this container manages scrolling of the overlay views so that they move in tandem.
55b5078b287b1cec38817e342ff054ea901d199329Andy Huang *
56b5078b287b1cec38817e342ff054ea901d199329Andy Huang * <h5>INPUT HANDLING</h5>
57b5078b287b1cec38817e342ff054ea901d199329Andy Huang * Placing the WebView in the same container as the overlay views means we don't have to do a lot of
58b5078b287b1cec38817e342ff054ea901d199329Andy Huang * manual manipulation of touch events. We do have a
59b5078b287b1cec38817e342ff054ea901d199329Andy Huang * {@link #forwardFakeMotionEvent(MotionEvent, int)} method that deals with one WebView
60b5078b287b1cec38817e342ff054ea901d199329Andy Huang * idiosyncrasy: it doesn't react well when touch MOVE events stream in without a preceding DOWN.
61b5078b287b1cec38817e342ff054ea901d199329Andy Huang *
62b5078b287b1cec38817e342ff054ea901d199329Andy Huang * <h5>VIEW RECYCLING</h5>
63b5078b287b1cec38817e342ff054ea901d199329Andy Huang * Normally, it would make sense to put all overlay views into a {@link ListView}. But this view
64b5078b287b1cec38817e342ff054ea901d199329Andy Huang * sandwich has unique characteristics: the list items are scrolled based on an external controller,
65b5078b287b1cec38817e342ff054ea901d199329Andy Huang * and we happen to know all of the overlay positions up front. So it didn't make sense to shoehorn
66b5078b287b1cec38817e342ff054ea901d199329Andy Huang * a ListView in and instead, we rolled our own view recycler by borrowing key details from
67bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein * ListView and AbsListView.<br/><br/>
68f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang *
69bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein * There is one additional constraint with the recycling: since scroll
70bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein * notifications happen during the WebView's draw, we do not remove and re-add views for recycling.
71bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein * Instead, we simply move the views off-screen and add them to our recycle cache. When the views
72bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein * are reused, they are simply moved back on screen instead of added. This practice
73bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein * circumvents the issues found when views are added or removed during draw (which results in
74bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein * elements not being drawn and other visual oddities). See b/10994303 for more details.
75f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang */
76f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangpublic class ConversationContainer extends ViewGroup implements ScrollListener {
77632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang    private static final String TAG = ConversationViewFragment.LAYOUT_TAG;
78bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang
7947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    private static final int[] BOTTOM_LAYER_VIEW_IDS = {
80e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein        R.id.conversation_webview
8147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    };
8247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
8347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    private static final int[] TOP_LAYER_VIEW_IDS = {
8447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        R.id.conversation_topmost_overlay
8547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    };
8647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
8731c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang    /**
8831c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang     * Maximum scroll speed (in dp/sec) at which the snap header animation will draw.
8931c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang     * Anything faster than that, and drawing it creates visual artifacting (wagon-wheel effect).
9031c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang     */
9131c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang    private static final float SNAP_HEADER_MAX_SCROLL_SPEED = 600f;
9231c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang
938f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang    private ConversationAccountController mAccountController;
947bdc3750454efe59617b7df945eadd7e59bee954Andy Huang    private ConversationViewAdapter mOverlayAdapter;
95adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang    private OverlayPosition[] mOverlayPositions;
96bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang    private ConversationWebView mWebView;
97a467d40eeb9c598fc6d7cb2dbafa2a331292d23eAndrew Sapperstein    private SnapHeader mSnapHeader;
98f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
9947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    private final List<View> mNonScrollingChildren = Lists.newArrayList();
10047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
101bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang    /**
10223014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang     * Current document zoom scale per {@link WebView#getScale()}. This is the ratio of actual
10323014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang     * screen pixels to logical WebView HTML pixels. We use it to convert from one to the other.
104bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang     */
105bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang    private float mScale;
106120ea66184c31bdeb729274054ec913afe3872c1Andy Huang    /**
107120ea66184c31bdeb729274054ec913afe3872c1Andy Huang     * Set to true upon receiving the first touch event. Used to help reject invalid WebView scale
108120ea66184c31bdeb729274054ec913afe3872c1Andy Huang     * values.
109120ea66184c31bdeb729274054ec913afe3872c1Andy Huang     */
110120ea66184c31bdeb729274054ec913afe3872c1Andy Huang    private boolean mTouchInitialized;
111f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
112bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang    /**
113bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang     * System touch-slop distance per {@link ViewConfiguration#getScaledTouchSlop()}.
114bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang     */
115bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang    private final int mTouchSlop;
116bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang    /**
117bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang     * Current scroll position, as dictated by the background {@link WebView}.
118bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang     */
119f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    private int mOffsetY;
120bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang    /**
121bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang     * Original pointer Y for slop calculation.
122bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang     */
123bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang    private float mLastMotionY;
124bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang    /**
125bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang     * Original pointer ID for slop calculation.
126bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang     */
127bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang    private int mActivePointerId;
128bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang    /**
129bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang     * Track pointer up/down state to know whether to send a make-up DOWN event to WebView.
130bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang     * WebView internal logic requires that a stream of {@link MotionEvent#ACTION_MOVE} events be
131bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang     * preceded by a {@link MotionEvent#ACTION_DOWN} event.
132bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang     */
133bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang    private boolean mTouchIsDown = false;
134bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang    /**
135bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang     * Remember if touch interception was triggered on a {@link MotionEvent#ACTION_POINTER_DOWN},
136bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang     * so we can send a make-up event in {@link #onTouchEvent(MotionEvent)}.
137bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang     */
138bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang    private boolean mMissedPointerDown;
139f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
1407bdc3750454efe59617b7df945eadd7e59bee954Andy Huang    /**
141c69cee309fb61e071ecae11e72bf2b8621eeb872Andy Huang     * A recycler that holds removed scrap views, organized by integer item view type. All views
142040b52fe85a67eeb6d9a4e9dc20322ee36ad4aecAndrew Sapperstein     * in this data structure should be removed from their view parent prior to insertion.
1437bdc3750454efe59617b7df945eadd7e59bee954Andy Huang     */
1447bdc3750454efe59617b7df945eadd7e59bee954Andy Huang    private final DequeMap<Integer, View> mScrapViews = new DequeMap<Integer, View>();
145b5078b287b1cec38817e342ff054ea901d199329Andy Huang
146b5078b287b1cec38817e342ff054ea901d199329Andy Huang    /**
14765fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huang     * The current set of overlay views in the view hierarchy. Looking through this map is faster
14865fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huang     * than traversing the view hierarchy.
149b5078b287b1cec38817e342ff054ea901d199329Andy Huang     * <p>
150b5078b287b1cec38817e342ff054ea901d199329Andy Huang     * WebView sometimes notifies of scroll changes during a draw (or display list generation), when
151b5078b287b1cec38817e342ff054ea901d199329Andy Huang     * it's not safe to detach view children because ViewGroup is in the middle of iterating over
15265fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huang     * its child array. So we remove any child from this list immediately and queue up a task to
15365fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huang     * detach it later. Since nobody other than the detach task references that view in the
15465fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huang     * meantime, we don't need any further checks or synchronization.
15546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang     * <p>
15646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang     * We keep {@link OverlayView} wrappers instead of bare views so that when it's time to dispose
15746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang     * of all views (on data set or adapter change), we can at least recycle them into the typed
15846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang     * scrap piles for later reuse.
159b5078b287b1cec38817e342ff054ea901d199329Andy Huang     */
16046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    private final SparseArray<OverlayView> mOverlayViews;
161b5078b287b1cec38817e342ff054ea901d199329Andy Huang
162b5078b287b1cec38817e342ff054ea901d199329Andy Huang    private int mWidthMeasureSpec;
163b5078b287b1cec38817e342ff054ea901d199329Andy Huang
164c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang    private boolean mDisableLayoutTracing;
165c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang
16631c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang    private final InputSmoother mVelocityTracker;
16731c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang
16846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    private final DataSetObserver mAdapterObserver = new AdapterObserver();
16946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
170cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang    /**
17159e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang     * The adapter index of the lowest overlay item that is above the top of the screen and reports
17259e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang     * {@link ConversationOverlayItem#canPushSnapHeader()}. We calculate this after a pass through
173bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein     * {@link #positionOverlays}.
17459e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang     *
17559e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang     */
17659e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang    private int mSnapIndex;
17759e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang
1788f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang    private boolean mSnapEnabled;
1798f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang
180ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein    /**
181ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein     * A View that fills the remaining vertical space when the overlays do not take
182ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein     * up the entire container. Otherwise, a card-like bottom white space appears.
183ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein     */
184888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein    private View mAdditionalBottomBorder;
185ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein
186ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein    /**
187ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein     * A flag denoting whether the fake bottom border has been added to the container.
188ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein     */
189888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein    private boolean mAdditionalBottomBorderAdded;
190888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein
19159e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang    /**
192ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein     * An int containing the potential top value for the additional bottom border.
193ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein     * If this value is less than the height of the scroll container, the additional
194ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein     * bottom border will be drawn.
195ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein     */
196ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein    private int mAdditionalBottomBorderOverlayTop;
197ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein
198ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein    /**
199cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang     * Child views of this container should implement this interface to be notified when they are
200cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang     * being detached.
201cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang     */
202cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang    public interface DetachListener {
203cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang        /**
204cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang         * Called on a child view when it is removed from its parent as part of
205cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang         * {@link ConversationContainer} view recycling.
206cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang         */
207cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang        void onDetachedFromParent();
208cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang    }
209cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang
210adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang    public static class OverlayPosition {
211adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang        public final int top;
212adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang        public final int bottom;
213adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang
214adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang        public OverlayPosition(int top, int bottom) {
215adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang            this.top = top;
216adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang            this.bottom = bottom;
217adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang        }
218adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang    }
219adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang
22046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    private static class OverlayView {
22146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        public View view;
22246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        int itemType;
22346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
22446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        public OverlayView(View view, int itemType) {
22546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            this.view = view;
22646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            this.itemType = itemType;
22746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        }
22846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    }
22946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
230f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    public ConversationContainer(Context c) {
231f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        this(c, null);
232f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    }
233f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
234f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    public ConversationContainer(Context c, AttributeSet attrs) {
235f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        super(c, attrs);
236bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang
23746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        mOverlayViews = new SparseArray<OverlayView>();
238b5078b287b1cec38817e342ff054ea901d199329Andy Huang
23931c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang        mVelocityTracker = new InputSmoother(c);
24031c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang
241bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang        mTouchSlop = ViewConfiguration.get(c).getScaledTouchSlop();
242bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang
243bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang        // Disabling event splitting fixes pinch-zoom when the first pointer goes down on the
244bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang        // WebView and the second pointer goes down on an overlay view.
245bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang        // Intercepting ACTION_POINTER_DOWN events allows pinch-zoom to work when the first pointer
246bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang        // goes down on an overlay view.
247bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang        setMotionEventSplittingEnabled(false);
248bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang    }
249bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang
250bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang    @Override
251bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang    protected void onFinishInflate() {
252bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang        super.onFinishInflate();
253bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang
254e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein        mWebView = (ConversationWebView) findViewById(R.id.conversation_webview);
255bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang        mWebView.addScrollListener(this);
25647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
25747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        for (int id : BOTTOM_LAYER_VIEW_IDS) {
25847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang            mNonScrollingChildren.add(findViewById(id));
25947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        }
26047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        for (int id : TOP_LAYER_VIEW_IDS) {
26147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang            mNonScrollingChildren.add(findViewById(id));
26247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        }
263f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    }
264f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
26585ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein    public void setupSnapHeader() {
266a467d40eeb9c598fc6d7cb2dbafa2a331292d23eAndrew Sapperstein        mSnapHeader = (SnapHeader) findViewById(R.id.snap_header);
267a467d40eeb9c598fc6d7cb2dbafa2a331292d23eAndrew Sapperstein        mSnapHeader.setSnappy();
26885ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein    }
26985ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein
270a467d40eeb9c598fc6d7cb2dbafa2a331292d23eAndrew Sapperstein    public SnapHeader getSnapHeader() {
27159e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang        return mSnapHeader;
27259e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang    }
27359e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang
2747bdc3750454efe59617b7df945eadd7e59bee954Andy Huang    public void setOverlayAdapter(ConversationViewAdapter a) {
27546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        if (mOverlayAdapter != null) {
27646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            mOverlayAdapter.unregisterDataSetObserver(mAdapterObserver);
27746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            clearOverlays();
27846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        }
279f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        mOverlayAdapter = a;
28046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        if (mOverlayAdapter != null) {
28146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            mOverlayAdapter.registerDataSetObserver(mAdapterObserver);
28246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        }
28351067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang    }
28451067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang
2858f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang    public void setAccountController(ConversationAccountController controller) {
2868f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang        mAccountController = controller;
2878f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang
288f8e065aab2cbff4dee86cc931a4d9c88dec37345Andrew Sapperstein//        mSnapEnabled = isSnapEnabled();
289f8e065aab2cbff4dee86cc931a4d9c88dec37345Andrew Sapperstein        mSnapEnabled = false; // TODO - re-enable when dogfooders howl
2908f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang    }
2918f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang
2926b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang    /**
2936b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang     * Re-bind any existing views that correspond to the given adapter positions.
2946b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang     *
2956b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang     */
2966b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang    public void onOverlayModelUpdate(List<Integer> affectedAdapterPositions) {
2976b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang        for (Integer i : affectedAdapterPositions) {
2986b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang            final ConversationOverlayItem item = mOverlayAdapter.getItem(i);
2996b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang            final OverlayView overlay = mOverlayViews.get(i);
3006b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang            if (overlay != null && overlay.view != null && item != null) {
3016b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang                item.onModelUpdated(overlay.view);
3026b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang            }
3034baafcbb39e20a81a1585270fc270a560ec8824dAndy Huang            // update the snap header too, but only it's showing if the current item
3044baafcbb39e20a81a1585270fc270a560ec8824dAndy Huang            if (i == mSnapIndex && mSnapHeader.isBoundTo(item)) {
3056b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang                mSnapHeader.refresh();
3066b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang            }
3076b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang        }
3086b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang    }
3096b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang
310c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang    /**
311c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang     * Return an overlay view for the given adapter item, or null if no matching view is currently
312c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang     * visible. This can happen as you scroll away from an overlay view.
313c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang     *
314c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang     */
315c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang    public View getViewForItem(ConversationOverlayItem item) {
31607fe9320d65d33b0d99b580eb47c5859ee7a7be6Andrew Sapperstein        if (mOverlayAdapter == null) {
31707fe9320d65d33b0d99b580eb47c5859ee7a7be6Andrew Sapperstein            return null;
31807fe9320d65d33b0d99b580eb47c5859ee7a7be6Andrew Sapperstein        }
319c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang        View result = null;
320c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang        int adapterPos = -1;
321c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang        for (int i = 0, len = mOverlayAdapter.getCount(); i < len; i++) {
322c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang            if (mOverlayAdapter.getItem(i) == item) {
323c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang                adapterPos = i;
324c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang                break;
325c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang            }
326c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang        }
327c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang        if (adapterPos != -1) {
328c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang            final OverlayView overlay = mOverlayViews.get(adapterPos);
329c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang            if (overlay != null) {
330c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang                result = overlay.view;
331c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang            }
332c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang        }
333c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang        return result;
334c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang    }
335c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang
33646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    private void clearOverlays() {
33746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        for (int i = 0, len = mOverlayViews.size(); i < len; i++) {
338bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein            detachOverlay(mOverlayViews.valueAt(i), true /* removeFromContainer */);
33946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        }
34046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        mOverlayViews.clear();
34146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    }
34246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
34346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    private void onDataSetChanged() {
3442a1e8e30bdd02dc08bdf2f878201b279f60d5142Andy Huang        // Recycle all views and re-bind them according to the current set of spacer coordinates.
3452a1e8e30bdd02dc08bdf2f878201b279f60d5142Andy Huang        // This essentially resets the overlay views and re-renders them.
3462a1e8e30bdd02dc08bdf2f878201b279f60d5142Andy Huang        // It's fast enough that it's okay to re-do all views on any small change, as long as
3472a1e8e30bdd02dc08bdf2f878201b279f60d5142Andy Huang        // the change isn't too frequent (< ~1Hz).
3482a1e8e30bdd02dc08bdf2f878201b279f60d5142Andy Huang
34946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        clearOverlays();
3502a1e8e30bdd02dc08bdf2f878201b279f60d5142Andy Huang        // also unbind the snap header view, so this "reset" causes the snap header to re-create
3512a1e8e30bdd02dc08bdf2f878201b279f60d5142Andy Huang        // its view, just like all other headers
3522a1e8e30bdd02dc08bdf2f878201b279f60d5142Andy Huang        mSnapHeader.unbind();
353ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein
354ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein        // also clear out the additional bottom border
355ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein        removeViewInLayout(mAdditionalBottomBorder);
356ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein        mAdditionalBottomBorderAdded = false;
357ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein
35884872d07eb0247a14c1e883e12f5ad642488fc96Andrew Sapperstein//        mSnapEnabled = isSnapEnabled();
35984872d07eb0247a14c1e883e12f5ad642488fc96Andrew Sapperstein        mSnapEnabled = false; // TODO - re-enable when dogfooders howl
360bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein        positionOverlays(mOffsetY, false /* postAddView */);
36146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    }
36246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
363bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang    private void forwardFakeMotionEvent(MotionEvent original, int newAction) {
364bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang        MotionEvent newEvent = MotionEvent.obtain(original);
365bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang        newEvent.setAction(newAction);
366bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang        mWebView.onTouchEvent(newEvent);
367bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang        LogUtils.v(TAG, "in Container.OnTouch. fake: action=%d x/y=%f/%f pointers=%d",
368bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang                newEvent.getActionMasked(), newEvent.getX(), newEvent.getY(),
369bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang                newEvent.getPointerCount());
370bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang    }
371bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang
372bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang    /**
373bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang     * Touch slop code was copied from {@link ScrollView#onInterceptTouchEvent(MotionEvent)}.
374bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang     */
375bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang    @Override
376bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang    public boolean onInterceptTouchEvent(MotionEvent ev) {
377120ea66184c31bdeb729274054ec913afe3872c1Andy Huang
378120ea66184c31bdeb729274054ec913afe3872c1Andy Huang        if (!mTouchInitialized) {
379120ea66184c31bdeb729274054ec913afe3872c1Andy Huang            mTouchInitialized = true;
380120ea66184c31bdeb729274054ec913afe3872c1Andy Huang        }
381120ea66184c31bdeb729274054ec913afe3872c1Andy Huang
382632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang        // no interception when WebView handles the first DOWN
383632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang        if (mWebView.isHandlingTouch()) {
384632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang            return false;
385632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang        }
386632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang
387bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang        boolean intercept = false;
388bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang        switch (ev.getActionMasked()) {
389bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang            case MotionEvent.ACTION_POINTER_DOWN:
390632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang                LogUtils.d(TAG, "Container is intercepting non-primary touch!");
391bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang                intercept = true;
392bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang                mMissedPointerDown = true;
393632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang                requestDisallowInterceptTouchEvent(true);
394bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang                break;
395bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang
396bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang            case MotionEvent.ACTION_DOWN:
397bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang                mLastMotionY = ev.getY();
398bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang                mActivePointerId = ev.getPointerId(0);
399bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang                break;
400bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang
401bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang            case MotionEvent.ACTION_MOVE:
402bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang                final int pointerIndex = ev.findPointerIndex(mActivePointerId);
403bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang                final float y = ev.getY(pointerIndex);
404bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang                final int yDiff = (int) Math.abs(y - mLastMotionY);
405bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang                if (yDiff > mTouchSlop) {
406bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang                    mLastMotionY = y;
407bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang                    intercept = true;
408bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang                }
409bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang                break;
410bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang        }
411bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang
412632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang//        LogUtils.v(TAG, "in Container.InterceptTouch. action=%d x/y=%f/%f pointers=%d result=%s",
413632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang//                ev.getActionMasked(), ev.getX(), ev.getY(), ev.getPointerCount(), intercept);
414bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang        return intercept;
415bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang    }
416bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang
417bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang    @Override
418bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang    public boolean onTouchEvent(MotionEvent ev) {
419bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang        final int action = ev.getActionMasked();
420bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang
421632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
422bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang            mTouchIsDown = false;
423bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang        } else if (!mTouchIsDown &&
424bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang                (action == MotionEvent.ACTION_MOVE || action == MotionEvent.ACTION_POINTER_DOWN)) {
425bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang
426bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang            forwardFakeMotionEvent(ev, MotionEvent.ACTION_DOWN);
427bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang            if (mMissedPointerDown) {
428bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang                forwardFakeMotionEvent(ev, MotionEvent.ACTION_POINTER_DOWN);
429bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang                mMissedPointerDown = false;
430bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang            }
431bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang
432bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang            mTouchIsDown = true;
433bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang        }
434bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang
435bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang        final boolean webViewResult = mWebView.onTouchEvent(ev);
436bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang
437632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang//        LogUtils.v(TAG, "in Container.OnTouch. action=%d x/y=%f/%f pointers=%d",
438632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang//                ev.getActionMasked(), ev.getX(), ev.getY(), ev.getPointerCount());
439bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang        return webViewResult;
440f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    }
441f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
442f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    @Override
443bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein    public void onNotifierScroll(final int y) {
44431c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang        mVelocityTracker.onInput(y);
445c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang        mDisableLayoutTracing = true;
446bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein        positionOverlays(y, true /* postAddView */); // post the addView since we're in draw code
447c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang        mDisableLayoutTracing = false;
448b5078b287b1cec38817e342ff054ea901d199329Andy Huang    }
449b5078b287b1cec38817e342ff054ea901d199329Andy Huang
450bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein    /**
451bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein     * Positions the overlays given an updated y position for the container.
452bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein     * @param y the current top position on screen
453bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein     * @param postAddView If {@code true}, posts all calls to
454bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein     *                    {@link #addViewInLayoutWrapper(android.view.View, boolean)}
455bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein     *                    to the UI thread rather than adding it immediately. If {@code false},
456bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein     *                    calls {@link #addViewInLayoutWrapper(android.view.View, boolean)}
457bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein     *                    immediately.
458bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein     */
459bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein    private void positionOverlays(int y, boolean postAddView) {
460f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        mOffsetY = y;
46107f8732dcc0e411d0b26d9eb88de582f95aaebb6Andy Huang
46207f8732dcc0e411d0b26d9eb88de582f95aaebb6Andy Huang        /*
463120ea66184c31bdeb729274054ec913afe3872c1Andy Huang         * The scale value that WebView reports is inaccurate when measured during WebView
464120ea66184c31bdeb729274054ec913afe3872c1Andy Huang         * initialization. This bug is present in ICS, so to work around it, we ignore all
46523014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang         * reported values and use a calculated expected value from ConversationWebView instead.
46623014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang         * Only when the user actually begins to touch the view (to, say, begin a zoom) do we begin
46723014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang         * to pay attention to WebView-reported scale values.
46807f8732dcc0e411d0b26d9eb88de582f95aaebb6Andy Huang         */
469120ea66184c31bdeb729274054ec913afe3872c1Andy Huang        if (mTouchInitialized) {
47007f8732dcc0e411d0b26d9eb88de582f95aaebb6Andy Huang            mScale = mWebView.getScale();
47123014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang        } else if (mScale == 0) {
47223014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang            mScale = mWebView.getInitialScale();
47307f8732dcc0e411d0b26d9eb88de582f95aaebb6Andy Huang        }
474c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang        traceLayout("in positionOverlays, raw scale=%f, effective scale=%f", mWebView.getScale(),
475c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang                mScale);
476f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
477adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang        if (mOverlayPositions == null || mOverlayAdapter == null) {
47851067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang            return;
47951067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang        }
48051067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang
481b5078b287b1cec38817e342ff054ea901d199329Andy Huang        // recycle scrolled-off views and add newly visible views
482b5078b287b1cec38817e342ff054ea901d199329Andy Huang
4837bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        // we want consecutive spacers/overlays to stack towards the bottom
4847bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        // so iterate from the bottom of the conversation up
4857bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        // starting with the last spacer bottom and the last adapter item, position adapter views
4867bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        // in a single stack until you encounter a non-contiguous expanded message header,
4877bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        // then decrement to the next spacer.
488b5078b287b1cec38817e342ff054ea901d199329Andy Huang
489adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang        traceLayout("IN positionOverlays, spacerCount=%d overlayCount=%d", mOverlayPositions.length,
490c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang                mOverlayAdapter.getCount());
491c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang
49259e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang        mSnapIndex = -1;
493ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein        mAdditionalBottomBorderOverlayTop = 0;
49459e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang
495adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang        int adapterLoopIndex = mOverlayAdapter.getCount() - 1;
496adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang        int spacerIndex = mOverlayPositions.length - 1;
497adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang        while (spacerIndex >= 0 && adapterLoopIndex >= 0) {
498adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang
499adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang            final int spacerTop = getOverlayTop(spacerIndex);
500adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang            final int spacerBottom = getOverlayBottom(spacerIndex);
501adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang
502adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang            final boolean flip;
503adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang            final int flipOffset;
504adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang            final int forceGravity;
505adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang            // flip direction from bottom->top to top->bottom traversal on the very first spacer
506adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang            // to facilitate top-aligned headers at spacer index = 0
507adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang            if (spacerIndex == 0) {
508adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang                flip = true;
509adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang                flipOffset = adapterLoopIndex;
510adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang                forceGravity = Gravity.TOP;
511adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang            } else {
512adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang                flip = false;
513adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang                flipOffset = 0;
514adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang                forceGravity = Gravity.NO_GRAVITY;
515adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang            }
516c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang
517adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang            int adapterIndex = flip ? flipOffset - adapterLoopIndex : adapterLoopIndex;
5187bdc3750454efe59617b7df945eadd7e59bee954Andy Huang
5197bdc3750454efe59617b7df945eadd7e59bee954Andy Huang            // always place at least one overlay per spacer
52046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            ConversationOverlayItem adapterItem = mOverlayAdapter.getItem(adapterIndex);
5217bdc3750454efe59617b7df945eadd7e59bee954Andy Huang
522adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang            OverlayPosition itemPos = calculatePosition(adapterItem, spacerTop, spacerBottom,
523adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang                    forceGravity);
5247bdc3750454efe59617b7df945eadd7e59bee954Andy Huang
525c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang            traceLayout("in loop, spacer=%d overlay=%d t/b=%d/%d (%s)", spacerIndex, adapterIndex,
526adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang                    itemPos.top, itemPos.bottom, adapterItem);
527bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein            positionOverlay(adapterIndex, itemPos.top, itemPos.bottom, postAddView);
5287bdc3750454efe59617b7df945eadd7e59bee954Andy Huang
529adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang            // and keep stacking overlays unconditionally if we are on the first spacer, or as long
530adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang            // as overlays are contiguous
531adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang            while (--adapterLoopIndex >= 0) {
532adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang                adapterIndex = flip ? flipOffset - adapterLoopIndex : adapterLoopIndex;
5337bdc3750454efe59617b7df945eadd7e59bee954Andy Huang                adapterItem = mOverlayAdapter.getItem(adapterIndex);
534adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang                if (spacerIndex > 0 && !adapterItem.isContiguous()) {
5357bdc3750454efe59617b7df945eadd7e59bee954Andy Huang                    // advance to the next spacer, but stay on this adapter item
5367bdc3750454efe59617b7df945eadd7e59bee954Andy Huang                    break;
537b5078b287b1cec38817e342ff054ea901d199329Andy Huang                }
5387bdc3750454efe59617b7df945eadd7e59bee954Andy Huang
539adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang                // place this overlay in the region of the spacer above or below the last item,
540adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang                // depending on direction of iteration
541adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang                final int regionTop = flip ? itemPos.bottom : spacerTop;
542adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang                final int regionBottom = flip ? spacerBottom : itemPos.top;
543adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang                itemPos = calculatePosition(adapterItem, regionTop, regionBottom, forceGravity);
5447bdc3750454efe59617b7df945eadd7e59bee954Andy Huang
545c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang                traceLayout("in contig loop, spacer=%d overlay=%d t/b=%d/%d (%s)", spacerIndex,
546adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang                        adapterIndex, itemPos.top, itemPos.bottom, adapterItem);
547bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein                positionOverlay(adapterIndex, itemPos.top, itemPos.bottom, postAddView);
548b5078b287b1cec38817e342ff054ea901d199329Andy Huang            }
549c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang
550c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang            spacerIndex--;
551b5078b287b1cec38817e342ff054ea901d199329Andy Huang        }
55259e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang
55331c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang        positionSnapHeader(mSnapIndex);
554bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein        positionAdditionalBottomBorder(postAddView);
555b5078b287b1cec38817e342ff054ea901d199329Andy Huang    }
556b5078b287b1cec38817e342ff054ea901d199329Andy Huang
557ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein    /**
558ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein     * Adds an additional bottom border to the overlay views in case
559ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein     * the overlays do not fill the entire screen.
560ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein     */
561bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein    private void positionAdditionalBottomBorder(boolean postAddView) {
562ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein        final int lastBottom = mAdditionalBottomBorderOverlayTop;
56383b460bf42d48bd8ab18ee135fab181242d13b9aAndrew Sapperstein        final int containerHeight = webPxToScreenPx(mWebView.getContentHeight());
564888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein        final int speculativeHeight = containerHeight - lastBottom;
565888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein        if (speculativeHeight > 0) {
566888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein            if (mAdditionalBottomBorder == null) {
567888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein                mAdditionalBottomBorder = mOverlayAdapter.getLayoutInflater().inflate(
568888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein                        R.layout.fake_bottom_border, this, false);
569888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein            }
570888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein
571888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein            setAdditionalBottomBorderHeight(speculativeHeight);
572888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein
573888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein            if (!mAdditionalBottomBorderAdded) {
574bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein                addViewInLayoutWrapper(mAdditionalBottomBorder, postAddView);
575888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein                mAdditionalBottomBorderAdded = true;
576888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein            }
577888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein
578888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein            measureOverlayView(mAdditionalBottomBorder);
579888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein            layoutOverlay(mAdditionalBottomBorder, lastBottom, containerHeight);
580888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein        } else {
581888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein            if (mAdditionalBottomBorder != null && mAdditionalBottomBorderAdded) {
58224e052ad62145bfcb1189817e750e78b60c8645dAndrew Sapperstein                if (postAddView) {
5832c15529ceb22e7aaad6f75ef809cb908ea08b286Andrew Sapperstein                    post(mRemoveBorderRunnable);
58424e052ad62145bfcb1189817e750e78b60c8645dAndrew Sapperstein                } else {
5852c15529ceb22e7aaad6f75ef809cb908ea08b286Andrew Sapperstein                    mRemoveBorderRunnable.run();
58624e052ad62145bfcb1189817e750e78b60c8645dAndrew Sapperstein                }
587ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein                mAdditionalBottomBorderAdded = false;
588888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein            }
589888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein        }
590888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein    }
591888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein
5922c15529ceb22e7aaad6f75ef809cb908ea08b286Andrew Sapperstein    private final RemoveBorderRunnable mRemoveBorderRunnable = new RemoveBorderRunnable();
59324e052ad62145bfcb1189817e750e78b60c8645dAndrew Sapperstein
594888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein    private void setAdditionalBottomBorderHeight(int speculativeHeight) {
595888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein        LayoutParams params = mAdditionalBottomBorder.getLayoutParams();
596888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein        params.height = speculativeHeight;
597888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein        mAdditionalBottomBorder.setLayoutParams(params);
598888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein    }
599888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein
600ff8553f20964f4c31b0c503a9e1daff6ae08a9c7Scott Kennedy    private static OverlayPosition calculatePosition(final ConversationOverlayItem adapterItem,
601ff8553f20964f4c31b0c503a9e1daff6ae08a9c7Scott Kennedy            final int withinTop, final int withinBottom, final int forceGravity) {
602adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang        if (adapterItem.getHeight() == 0) {
603adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang            // "place" invisible items at the bottom of their region to stay consistent with the
604adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang            // stacking algorithm in positionOverlays(), unless gravity is forced to the top
605adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang            final int y = (forceGravity == Gravity.TOP) ? withinTop : withinBottom;
606adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang            return new OverlayPosition(y, y);
607adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang        }
608adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang
609adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang        final int v = ((forceGravity != Gravity.NO_GRAVITY) ?
610adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang                forceGravity : adapterItem.getGravity()) & Gravity.VERTICAL_GRAVITY_MASK;
611adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang        switch (v) {
612adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang            case Gravity.BOTTOM:
613adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang                return new OverlayPosition(withinBottom - adapterItem.getHeight(), withinBottom);
614adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang            case Gravity.TOP:
615adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang                return new OverlayPosition(withinTop, withinTop + adapterItem.getHeight());
616adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang            default:
617adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang                throw new UnsupportedOperationException("unsupported gravity: " + v);
618adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang        }
619adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang    }
620adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang
621b5078b287b1cec38817e342ff054ea901d199329Andy Huang    /**
6229875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang     * Executes a measure pass over the specified child overlay view and returns the measured
6239875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang     * height. The measurement uses whatever the current container's width measure spec is.
6249875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang     * This method ignores view visibility and returns the height that the view would be if visible.
6259875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang     *
6269875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang     * @param overlayView an overlay view to measure. does not actually have to be attached yet.
6279875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang     * @return height that the view would be if it was visible
628b5078b287b1cec38817e342ff054ea901d199329Andy Huang     */
6299875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang    public int measureOverlay(View overlayView) {
6309875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang        measureOverlayView(overlayView);
6319875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang        return overlayView.getMeasuredHeight();
6329875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang    }
633c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang
6349875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang    /**
6359875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang     * Copied/stolen from {@link ListView}.
6369875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang     */
6379875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang    private void measureOverlayView(View child) {
63847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        MarginLayoutParams p = (MarginLayoutParams) child.getLayoutParams();
639b5078b287b1cec38817e342ff054ea901d199329Andy Huang        if (p == null) {
64047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang            p = (MarginLayoutParams) generateDefaultLayoutParams();
641b5078b287b1cec38817e342ff054ea901d199329Andy Huang        }
642b5078b287b1cec38817e342ff054ea901d199329Andy Huang
643b5078b287b1cec38817e342ff054ea901d199329Andy Huang        int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
64447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang                getPaddingLeft() + getPaddingRight() + p.leftMargin + p.rightMargin, p.width);
645b5078b287b1cec38817e342ff054ea901d199329Andy Huang        int lpHeight = p.height;
646b5078b287b1cec38817e342ff054ea901d199329Andy Huang        int childHeightSpec;
647b5078b287b1cec38817e342ff054ea901d199329Andy Huang        if (lpHeight > 0) {
648b5078b287b1cec38817e342ff054ea901d199329Andy Huang            childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
649b5078b287b1cec38817e342ff054ea901d199329Andy Huang        } else {
650b5078b287b1cec38817e342ff054ea901d199329Andy Huang            childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
651b5078b287b1cec38817e342ff054ea901d199329Andy Huang        }
652b5078b287b1cec38817e342ff054ea901d199329Andy Huang        child.measure(childWidthSpec, childHeightSpec);
653b5078b287b1cec38817e342ff054ea901d199329Andy Huang    }
654b5078b287b1cec38817e342ff054ea901d199329Andy Huang
65546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    private void onOverlayScrolledOff(final int adapterIndex, final OverlayView overlay,
65646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            int overlayTop, int overlayBottom) {
657bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein        // immediately remove this view from the view set so future lookups don't find it
65865fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huang        mOverlayViews.remove(adapterIndex);
659b5078b287b1cec38817e342ff054ea901d199329Andy Huang
660bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein        // detach but don't actually remove from the view
661bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein        detachOverlay(overlay, false /* removeFromContainer */);
662b5078b287b1cec38817e342ff054ea901d199329Andy Huang
663b5078b287b1cec38817e342ff054ea901d199329Andy Huang        // push it out of view immediately
664b5078b287b1cec38817e342ff054ea901d199329Andy Huang        // otherwise this scrolled-off header will continue to draw until the runnable runs
66546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        layoutOverlay(overlay.view, overlayTop, overlayBottom);
6667bdc3750454efe59617b7df945eadd7e59bee954Andy Huang    }
6677bdc3750454efe59617b7df945eadd7e59bee954Andy Huang
6688fdce82be7c643375470cd6dc48ff531f9175dd0Andy Huang    /**
669c69cee309fb61e071ecae11e72bf2b8621eeb872Andy Huang     * Returns an existing scrap view, if available. The view will already be removed from the view
6708fdce82be7c643375470cd6dc48ff531f9175dd0Andy Huang     * hierarchy. This method will not remove the view from the scrap heap.
6718fdce82be7c643375470cd6dc48ff531f9175dd0Andy Huang     *
6728fdce82be7c643375470cd6dc48ff531f9175dd0Andy Huang     */
6737bdc3750454efe59617b7df945eadd7e59bee954Andy Huang    public View getScrapView(int type) {
6748fdce82be7c643375470cd6dc48ff531f9175dd0Andy Huang        return mScrapViews.peek(type);
6757bdc3750454efe59617b7df945eadd7e59bee954Andy Huang    }
6767bdc3750454efe59617b7df945eadd7e59bee954Andy Huang
6777bdc3750454efe59617b7df945eadd7e59bee954Andy Huang    public void addScrapView(int type, View v) {
6787bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        mScrapViews.add(type, v);
679bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein        addViewInLayoutWrapper(v, false /* postAddView */);
680b5078b287b1cec38817e342ff054ea901d199329Andy Huang    }
681b5078b287b1cec38817e342ff054ea901d199329Andy Huang
682bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein    private void detachOverlay(OverlayView overlay, boolean removeFromContainer) {
683c69cee309fb61e071ecae11e72bf2b8621eeb872Andy Huang        // Prefer removeViewInLayout over removeView. The typical followup layout pass is unneeded
684c69cee309fb61e071ecae11e72bf2b8621eeb872Andy Huang        // because removing overlay views doesn't affect overall layout.
685bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein        if (removeFromContainer) {
686bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein            removeViewInLayout(overlay.view);
687bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein        }
68846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        mScrapViews.add(overlay.itemType, overlay.view);
68946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        if (overlay.view instanceof DetachListener) {
69046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            ((DetachListener) overlay.view).onDetachedFromParent();
691cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang        }
692cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang    }
693cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang
694cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang    @Override
695f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
696f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
697632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang        if (LogUtils.isLoggable(TAG, LogUtils.DEBUG)) {
698632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang            LogUtils.d(TAG, "*** IN header container onMeasure spec for w/h=%s/%s",
699632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang                    MeasureSpec.toString(widthMeasureSpec),
700632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang                    MeasureSpec.toString(heightMeasureSpec));
701632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang        }
702f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
70347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        for (View nonScrollingChild : mNonScrollingChildren) {
70447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang            if (nonScrollingChild.getVisibility() != GONE) {
70547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang                measureChildWithMargins(nonScrollingChild, widthMeasureSpec, 0 /* widthUsed */,
70647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang                        heightMeasureSpec, 0 /* heightUsed */);
70747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang            }
7083233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang        }
709b5078b287b1cec38817e342ff054ea901d199329Andy Huang        mWidthMeasureSpec = widthMeasureSpec;
7107bdc3750454efe59617b7df945eadd7e59bee954Andy Huang
7119875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang        // onLayout will re-measure and re-position overlays for the new container size, but the
7129875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang        // spacer offsets would still need to be updated to have them draw at their new locations.
713f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    }
714f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
715f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    @Override
716f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    protected void onLayout(boolean changed, int l, int t, int r, int b) {
717632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang        LogUtils.d(TAG, "*** IN header container onLayout");
718f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
71947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        for (View nonScrollingChild : mNonScrollingChildren) {
72047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang            if (nonScrollingChild.getVisibility() != GONE) {
72147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang                final int w = nonScrollingChild.getMeasuredWidth();
72247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang                final int h = nonScrollingChild.getMeasuredHeight();
72347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
72447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang                final MarginLayoutParams lp =
72547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang                        (MarginLayoutParams) nonScrollingChild.getLayoutParams();
72647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
72747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang                final int childLeft = lp.leftMargin;
72847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang                final int childTop = lp.topMargin;
72947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang                nonScrollingChild.layout(childLeft, childTop, childLeft + w, childTop + h);
73047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang            }
73147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        }
7325ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang
733e20e1639ff0eca873e9986ec52dbaa497059e01eAndy Huang        if (mOverlayAdapter != null) {
734e20e1639ff0eca873e9986ec52dbaa497059e01eAndy Huang            // being in a layout pass means overlay children may require measurement,
735e20e1639ff0eca873e9986ec52dbaa497059e01eAndy Huang            // so invalidate them
736e20e1639ff0eca873e9986ec52dbaa497059e01eAndy Huang            for (int i = 0, len = mOverlayAdapter.getCount(); i < len; i++) {
737e20e1639ff0eca873e9986ec52dbaa497059e01eAndy Huang                mOverlayAdapter.getItem(i).invalidateMeasurement();
738e20e1639ff0eca873e9986ec52dbaa497059e01eAndy Huang            }
7398778a8717b21335ebc3127b7be1a2cddf6b64697Andy Huang        }
7408778a8717b21335ebc3127b7be1a2cddf6b64697Andy Huang
741bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein        positionOverlays(mOffsetY, false /* postAddView */);
74259e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang    }
74359e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang
74459e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang    @Override
74547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    protected LayoutParams generateDefaultLayoutParams() {
74647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        return new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
74747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    }
74847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
74947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    @Override
75047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    public LayoutParams generateLayoutParams(AttributeSet attrs) {
75147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        return new MarginLayoutParams(getContext(), attrs);
75247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    }
75347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
75447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    @Override
75547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
75647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        return new MarginLayoutParams(p);
75747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    }
75847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
75947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    @Override
76047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
76147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        return p instanceof MarginLayoutParams;
76247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    }
76347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
764adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang    private int getOverlayTop(int spacerIndex) {
765adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang        return webPxToScreenPx(mOverlayPositions[spacerIndex].top);
766adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang    }
767adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang
7687bdc3750454efe59617b7df945eadd7e59bee954Andy Huang    private int getOverlayBottom(int spacerIndex) {
769adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang        return webPxToScreenPx(mOverlayPositions[spacerIndex].bottom);
770adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang    }
771adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang
772adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang    private int webPxToScreenPx(int webPx) {
7737bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        // TODO: round or truncate?
774adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang        // TODO: refactor and unify with ConversationWebView.webPxToScreenPx()
775adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang        return (int) (webPx * mScale);
776b5078b287b1cec38817e342ff054ea901d199329Andy Huang    }
777b5078b287b1cec38817e342ff054ea901d199329Andy Huang
778bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein    private void positionOverlay(
779bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein            int adapterIndex, int overlayTopY, int overlayBottomY, boolean postAddView) {
78046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        final OverlayView overlay = mOverlayViews.get(adapterIndex);
78146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        final ConversationOverlayItem item = mOverlayAdapter.getItem(adapterIndex);
7829875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang
78331c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang        // save off the item's current top for later snap calculations
78431c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang        item.setTop(overlayTopY);
78531c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang
786c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang        // is the overlay visible and does it have non-zero height?
787c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang        if (overlayTopY != overlayBottomY && overlayBottomY > mOffsetY
788c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang                && overlayTopY < mOffsetY + getHeight()) {
78946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            View overlayView = overlay != null ? overlay.view : null;
7907bdc3750454efe59617b7df945eadd7e59bee954Andy Huang            // show and/or move overlay
7917bdc3750454efe59617b7df945eadd7e59bee954Andy Huang            if (overlayView == null) {
792bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein                overlayView = addOverlayView(adapterIndex, postAddView);
7932fd167d6131482da984768b5ee75cefa32ed3e32Andrew Sapperstein                ViewCompat.setLayoutDirection(overlayView, ViewCompat.getLayoutDirection(this));
7949875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang                measureOverlayView(overlayView);
7959875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang                item.markMeasurementValid();
7969875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang                traceLayout("show/measure overlay %d", adapterIndex);
797c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang            } else {
798c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang                traceLayout("move overlay %d", adapterIndex);
7999875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang                if (!item.isMeasurementValid()) {
800cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein                    item.rebindView(overlayView);
8019875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang                    measureOverlayView(overlayView);
8029875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang                    item.markMeasurementValid();
8039875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang                    traceLayout("and (re)measure overlay %d, old/new heights=%d/%d", adapterIndex,
8049875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang                            overlayView.getHeight(), overlayView.getMeasuredHeight());
8059875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang                }
8067bdc3750454efe59617b7df945eadd7e59bee954Andy Huang            }
8079875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang            traceLayout("laying out overlay %d with h=%d", adapterIndex,
8089875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang                    overlayView.getMeasuredHeight());
809ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein            final int childBottom = overlayTopY + overlayView.getMeasuredHeight();
810ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein            layoutOverlay(overlayView, overlayTopY, childBottom);
811ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein            mAdditionalBottomBorderOverlayTop = (childBottom > mAdditionalBottomBorderOverlayTop) ?
812ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein                    childBottom : mAdditionalBottomBorderOverlayTop;
8137bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        } else {
8147bdc3750454efe59617b7df945eadd7e59bee954Andy Huang            // hide overlay
81546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            if (overlay != null) {
816c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang                traceLayout("hide overlay %d", adapterIndex);
81746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                onOverlayScrolledOff(adapterIndex, overlay, overlayTopY, overlayBottomY);
818c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang            } else {
819c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang                traceLayout("ignore non-visible overlay %d", adapterIndex);
8207bdc3750454efe59617b7df945eadd7e59bee954Andy Huang            }
821ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein            mAdditionalBottomBorderOverlayTop = (overlayBottomY > mAdditionalBottomBorderOverlayTop)
822ef6367d9ec71fc5022a06c4f87504637a010a0b7Andrew Sapperstein                    ? overlayBottomY : mAdditionalBottomBorderOverlayTop;
8237bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        }
82459e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang
82559e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang        if (overlayTopY <= mOffsetY && item.canPushSnapHeader()) {
82659e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang            if (mSnapIndex == -1) {
82759e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang                mSnapIndex = adapterIndex;
82859e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang            } else if (adapterIndex > mSnapIndex) {
82959e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang                mSnapIndex = adapterIndex;
83059e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang            }
83159e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang        }
83259e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang
833b5078b287b1cec38817e342ff054ea901d199329Andy Huang    }
834b5078b287b1cec38817e342ff054ea901d199329Andy Huang
8357bdc3750454efe59617b7df945eadd7e59bee954Andy Huang    // layout an existing view
8367bdc3750454efe59617b7df945eadd7e59bee954Andy Huang    // need its top offset into the conversation, its height, and the scroll offset
837c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang    private void layoutOverlay(View child, int childTop, int childBottom) {
8387bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        final int top = childTop - mOffsetY;
839c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang        final int bottom = childBottom - mOffsetY;
84047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
84147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
84247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        final int childLeft = getPaddingLeft() + lp.leftMargin;
84347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
84447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        child.layout(childLeft, top, childLeft + child.getMeasuredWidth(), bottom);
845b5078b287b1cec38817e342ff054ea901d199329Andy Huang    }
846b5078b287b1cec38817e342ff054ea901d199329Andy Huang
847bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein    private View addOverlayView(int adapterIndex, boolean postAddView) {
8487bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        final int itemType = mOverlayAdapter.getItemViewType(adapterIndex);
8497bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        final View convertView = mScrapViews.poll(itemType);
8507bdc3750454efe59617b7df945eadd7e59bee954Andy Huang
851256b35c0a8287f48c28e0d1ba3fae65790063295Andy Huang        final View view = mOverlayAdapter.getView(adapterIndex, convertView, this);
85246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        mOverlayViews.put(adapterIndex, new OverlayView(view, itemType));
853f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
854c69cee309fb61e071ecae11e72bf2b8621eeb872Andy Huang        if (convertView == view) {
855c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang            LogUtils.d(TAG, "want to REUSE scrolled-in view: index=%d obj=%s", adapterIndex, view);
8567bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        } else {
857c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang            LogUtils.d(TAG, "want to CREATE scrolled-in view: index=%d obj=%s", adapterIndex, view);
858f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        }
8597bdc3750454efe59617b7df945eadd7e59bee954Andy Huang
860bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein        if (view.getParent() == null) {
861bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein            addViewInLayoutWrapper(view, postAddView);
862bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein        } else {
863040b52fe85a67eeb6d9a4e9dc20322ee36ad4aecAndrew Sapperstein            // Need to call postInvalidate since the view is being moved back on
864040b52fe85a67eeb6d9a4e9dc20322ee36ad4aecAndrew Sapperstein            // screen and we want to force it to draw the view. Without doing this,
865040b52fe85a67eeb6d9a4e9dc20322ee36ad4aecAndrew Sapperstein            // the view may not draw itself when it comes back on screen.
866040b52fe85a67eeb6d9a4e9dc20322ee36ad4aecAndrew Sapperstein            view.postInvalidate();
867bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein        }
86859e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang
8697bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        return view;
870f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    }
871f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
872bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein    private void addViewInLayoutWrapper(View view, boolean postAddView) {
873bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein        final AddViewRunnable addviewRunnable = new AddViewRunnable(view);
874bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein        if (postAddView) {
875bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein            post(addviewRunnable);
876bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein        } else {
877bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein            addviewRunnable.run();
878bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein        }
879888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein    }
880888388c1485fd8c694abacb638fb54273c5f411bAndrew Sapperstein
881bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein    private class AddViewRunnable implements Runnable {
88224e052ad62145bfcb1189817e750e78b60c8645dAndrew Sapperstein        private final View mView;
883bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein
884bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein        public AddViewRunnable(View view) {
885bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein            mView = view;
886bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein        }
887bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein
888bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein        @Override
889bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein        public void run() {
890bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein            final int index = BOTTOM_LAYER_VIEW_IDS.length;
891bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein            addViewInLayout(mView, index, mView.getLayoutParams(), true /* preventRequestLayout */);
892bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein        }
893bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein    };
894bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein
8952c15529ceb22e7aaad6f75ef809cb908ea08b286Andrew Sapperstein    private class RemoveBorderRunnable implements Runnable {
89624e052ad62145bfcb1189817e750e78b60c8645dAndrew Sapperstein        @Override
89724e052ad62145bfcb1189817e750e78b60c8645dAndrew Sapperstein        public void run() {
8982c15529ceb22e7aaad6f75ef809cb908ea08b286Andrew Sapperstein            removeViewInLayout(mAdditionalBottomBorder);
89924e052ad62145bfcb1189817e750e78b60c8645dAndrew Sapperstein        }
90024e052ad62145bfcb1189817e750e78b60c8645dAndrew Sapperstein    }
90124e052ad62145bfcb1189817e750e78b60c8645dAndrew Sapperstein
9028f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang    private boolean isSnapEnabled() {
9038f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang        if (mAccountController == null || mAccountController.getAccount() == null
9048f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang                || mAccountController.getAccount().settings == null) {
9058f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang            return true;
9068f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang        }
9078f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang        final int snap = mAccountController.getAccount().settings.snapHeaders;
9088f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang        return snap == UIProvider.SnapHeaderValue.ALWAYS ||
9098f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang                (snap == UIProvider.SnapHeaderValue.PORTRAIT_ONLY && getResources()
9108f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang                    .getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT);
9118f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang    }
9128f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang
91331c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang    // render and/or re-position snap header
91431c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang    private void positionSnapHeader(int snapIndex) {
91531c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang        ConversationOverlayItem snapItem = null;
9168f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang        if (mSnapEnabled && snapIndex != -1) {
91731c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang            final ConversationOverlayItem item = mOverlayAdapter.getItem(snapIndex);
91831c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang            if (item.canBecomeSnapHeader()) {
91931c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang                snapItem = item;
92031c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang            }
92131c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang        }
92231c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang        if (snapItem == null) {
92331c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang            mSnapHeader.setVisibility(GONE);
92431c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang            mSnapHeader.unbind();
92531c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang            return;
92631c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang        }
92731c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang
92831c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang        snapItem.bindView(mSnapHeader, false /* measureOnly */);
92931c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang        mSnapHeader.setVisibility(VISIBLE);
93031c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang
9310a648c9b8f9d02f89e8711e97215b0e30f2cf0b6Andy Huang        // overlap is negative or zero; bump the snap header upwards by that much
93231c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang        int overlap = 0;
93331c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang
93431c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang        final ConversationOverlayItem next = findNextPushingOverlay(snapIndex + 1);
93531c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang        if (next != null) {
93631c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang            overlap = Math.min(0, next.getTop() - mSnapHeader.getHeight() - mOffsetY);
93731c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang
93831c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang            // disable overlap drawing past a certain speed
93931c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang            if (overlap < 0) {
94031c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang                final Float v = mVelocityTracker.getSmoothedVelocity();
94131c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang                if (v != null && v > SNAP_HEADER_MAX_SCROLL_SPEED) {
94231c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang                    overlap = 0;
94331c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang                }
94431c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang            }
94531c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang        }
9460a648c9b8f9d02f89e8711e97215b0e30f2cf0b6Andy Huang
9470a648c9b8f9d02f89e8711e97215b0e30f2cf0b6Andy Huang        mSnapHeader.setTranslationY(overlap);
94831c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang    }
94931c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang
95031c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang    // find the next header that can push the snap header up
95131c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang    private ConversationOverlayItem findNextPushingOverlay(int start) {
95231c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang        for (int i = start, len = mOverlayAdapter.getCount(); i < len; i++) {
95331c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang            final ConversationOverlayItem next = mOverlayAdapter.getItem(i);
95431c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang            if (next.canPushSnapHeader()) {
95531c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang                return next;
95631c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang            }
95731c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang        }
95831c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang        return null;
95931c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang    }
96031c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang
961c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang    /**
962adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang     * Prevents any layouts from happening until the next time
963adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang     * {@link #onGeometryChange(OverlayPosition[])} is
964c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang     * called. Useful when you know the HTML spacer coordinates are inconsistent with adapter items.
965c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang     * <p>
966adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang     * If you call this, you must ensure that a followup call to
967adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang     * {@link #onGeometryChange(OverlayPosition[])}
968c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang     * is made later, when the HTML spacer coordinates are updated.
969c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang     *
970c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang     */
971c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang    public void invalidateSpacerGeometry() {
972adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang        mOverlayPositions = null;
973c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang    }
974c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang
975adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang    public void onGeometryChange(OverlayPosition[] overlayPositions) {
976adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang        traceLayout("*** got overlay spacer positions:");
977adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang        for (OverlayPosition pos : overlayPositions) {
978adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang            traceLayout("top=%d bottom=%d", pos.top, pos.bottom);
979f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        }
980f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
981adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang        mOverlayPositions = overlayPositions;
982bb6f0504c1607c89d9a3dd3e6023f36d61837016Andrew Sapperstein        positionOverlays(mOffsetY, false /* postAddView */);
983f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    }
984f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
985e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein    /**
986e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein     * Remove the view that corresponds to the item in the {@link ConversationViewAdapter}
987e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein     * at the specified index.<p/>
988e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein     *
989e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein     * <b>Note:</b> the view is actually pushed off-screen and recycled
990e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein     * as though it were scrolled off.
991e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein     * @param adapterIndex The index for the view in the adapter.
992e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein     */
993e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein    public void removeViewAtAdapterIndex(int adapterIndex) {
994e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein        // need to temporarily set the offset to 0 so that we can ensure we're pushing off-screen.
995e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein        final int offsetY = mOffsetY;
996e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein        mOffsetY = 0;
997e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein        final OverlayView overlay = mOverlayViews.get(adapterIndex);
998e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein        if (overlay != null) {
999e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein            final int height = getHeight();
1000e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein            onOverlayScrolledOff(adapterIndex, overlay, height, height + overlay.view.getHeight());
10011e191e34733699149a9e662fd2540ae4b6ec9b0dAndrew Sapperstein            LogUtils.i(TAG, "footer scrolled off. container height=%s, measuredHeight=%s",
10021e191e34733699149a9e662fd2540ae4b6ec9b0dAndrew Sapperstein                    height, getMeasuredHeight());
10031e191e34733699149a9e662fd2540ae4b6ec9b0dAndrew Sapperstein        } else {
10049e700c59ccd35fb243fbfa207b530d4184d7ad3aAndrew Sapperstein            LogUtils.i(TAG, "footer not found with adapterIndex=%s", adapterIndex);
10059e700c59ccd35fb243fbfa207b530d4184d7ad3aAndrew Sapperstein            for (int i = 0, size = mOverlayViews.size(); i < size; i++) {
10069e700c59ccd35fb243fbfa207b530d4184d7ad3aAndrew Sapperstein                final int index = mOverlayViews.keyAt(i);
10079e700c59ccd35fb243fbfa207b530d4184d7ad3aAndrew Sapperstein                final OverlayView overlayView = mOverlayViews.valueAt(i);
10089e700c59ccd35fb243fbfa207b530d4184d7ad3aAndrew Sapperstein                LogUtils.i(TAG, "OverlayView: adapterIndex=%s, itemType=%s, view=%s",
10099e700c59ccd35fb243fbfa207b530d4184d7ad3aAndrew Sapperstein                        index, overlayView.itemType, overlayView.view);
10109e700c59ccd35fb243fbfa207b530d4184d7ad3aAndrew Sapperstein            }
10119e700c59ccd35fb243fbfa207b530d4184d7ad3aAndrew Sapperstein            for (int i = 0, size = mOverlayAdapter.getCount(); i < size; i++) {
10129e700c59ccd35fb243fbfa207b530d4184d7ad3aAndrew Sapperstein                final ConversationOverlayItem item = mOverlayAdapter.getItem(i);
10139e700c59ccd35fb243fbfa207b530d4184d7ad3aAndrew Sapperstein                LogUtils.i(TAG, "adapter item: index=%s, item=%s", i, item);
10149e700c59ccd35fb243fbfa207b530d4184d7ad3aAndrew Sapperstein            }
1015e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein        }
1016e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein        // restore the offset to its original value after the view has been moved off-screen.
1017e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein        mOffsetY = offsetY;
1018e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein    }
1019e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein
1020c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang    private void traceLayout(String msg, Object... params) {
1021c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang        if (mDisableLayoutTracing) {
1022c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang            return;
1023c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang        }
10247f9ef60c5e42470814f6b4bb8ed771c17fdfea22Andy Huang        LogUtils.d(TAG, msg, params);
1025c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang    }
1026c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang
1027c966a8a65a75559f677574c2a53cb9d43490f04eJin Cao    @Override
1028c966a8a65a75559f677574c2a53cb9d43490f04eJin Cao    protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
1029eed2850edd2c0c3f339d5e84a09d1385c2cdd256Jin Cao        if (mOverlayAdapter != null) {
10302cb6c1ca2b508331ee29d16b71f5ec024cd22555Jin Cao            return mOverlayAdapter.focusFirstMessageHeader();
1031eed2850edd2c0c3f339d5e84a09d1385c2cdd256Jin Cao        }
1032eed2850edd2c0c3f339d5e84a09d1385c2cdd256Jin Cao        return false;
10330b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao    }
10340b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao
1035c966a8a65a75559f677574c2a53cb9d43490f04eJin Cao    public void focusFirstMessageHeader() {
1036c966a8a65a75559f677574c2a53cb9d43490f04eJin Cao        mOverlayAdapter.focusFirstMessageHeader();
10370b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao    }
10380b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao
1039c966a8a65a75559f677574c2a53cb9d43490f04eJin Cao    public View getNextOverlayView(View curr, boolean isDown) {
1040c966a8a65a75559f677574c2a53cb9d43490f04eJin Cao        // Find the scraps that we should avoid when fetching the next view.
1041c966a8a65a75559f677574c2a53cb9d43490f04eJin Cao        final Set<View> scraps = Sets.newHashSet();
1042c966a8a65a75559f677574c2a53cb9d43490f04eJin Cao        mScrapViews.visitAll(new DequeMap.Visitor<View>() {
1043c966a8a65a75559f677574c2a53cb9d43490f04eJin Cao            @Override
1044c966a8a65a75559f677574c2a53cb9d43490f04eJin Cao            public void visit(View item) {
1045c966a8a65a75559f677574c2a53cb9d43490f04eJin Cao                scraps.add(item);
1046c966a8a65a75559f677574c2a53cb9d43490f04eJin Cao            }
1047c966a8a65a75559f677574c2a53cb9d43490f04eJin Cao        });
1048c966a8a65a75559f677574c2a53cb9d43490f04eJin Cao        return mOverlayAdapter.getNextOverlayView(curr, isDown, scraps);
10490b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao    }
10500b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao
105146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    private class AdapterObserver extends DataSetObserver {
105246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        @Override
105346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        public void onChanged() {
105446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            onDataSetChanged();
105546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        }
105646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    }
1057f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang}
1058