ConversationContainer.java revision c69cee309fb61e071ecae11e72bf2b8621eeb872
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;
2146dfba6160b55a582b344328067e3dafeb881dd9Andy Huangimport android.database.DataSetObserver;
2259e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huangimport android.graphics.Canvas;
23f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport android.util.AttributeSet;
2465fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huangimport android.util.SparseArray;
25bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huangimport android.view.MotionEvent;
26f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport android.view.View;
27bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huangimport android.view.ViewConfiguration;
28f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport android.view.ViewGroup;
29f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport android.webkit.WebView;
30f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport android.widget.Adapter;
31b5078b287b1cec38817e342ff054ea901d199329Andy Huangimport android.widget.ListView;
32bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huangimport android.widget.ScrollView;
33f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
34b5078b287b1cec38817e342ff054ea901d199329Andy Huangimport com.android.mail.R;
355ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huangimport com.android.mail.browse.ScrollNotifier.ScrollListener;
363bcf180f8104bc27319086a9a6ece5a3c2917c37mindypimport com.android.mail.providers.Conversation;
37632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huangimport com.android.mail.ui.ConversationViewFragment;
387bdc3750454efe59617b7df945eadd7e59bee954Andy Huangimport com.android.mail.utils.DequeMap;
3931c38a8247b4583ac1cc506acf8454d8922ee491Andy Huangimport com.android.mail.utils.InputSmoother;
40f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport com.android.mail.utils.LogUtils;
417517e3b61b898a57f19be0671f70d58a82224643Andy Huangimport com.android.mail.utils.Utils;
4247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huangimport com.google.common.collect.Lists;
4347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
4447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huangimport java.util.List;
45b5078b287b1cec38817e342ff054ea901d199329Andy Huang
46f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang/**
47b5078b287b1cec38817e342ff054ea901d199329Andy Huang * A specialized ViewGroup container for conversation view. It is designed to contain a single
48b5078b287b1cec38817e342ff054ea901d199329Andy Huang * {@link WebView} and a number of overlay views that draw on top of the WebView. In the Mail app,
49b5078b287b1cec38817e342ff054ea901d199329Andy Huang * the WebView contains all HTML message bodies in a conversation, and the overlay views are the
50b5078b287b1cec38817e342ff054ea901d199329Andy Huang * subject view, message headers, and attachment views. The WebView does all scroll handling, and
51b5078b287b1cec38817e342ff054ea901d199329Andy Huang * this container manages scrolling of the overlay views so that they move in tandem.
52b5078b287b1cec38817e342ff054ea901d199329Andy Huang *
53b5078b287b1cec38817e342ff054ea901d199329Andy Huang * <h5>INPUT HANDLING</h5>
54b5078b287b1cec38817e342ff054ea901d199329Andy Huang * Placing the WebView in the same container as the overlay views means we don't have to do a lot of
55b5078b287b1cec38817e342ff054ea901d199329Andy Huang * manual manipulation of touch events. We do have a
56b5078b287b1cec38817e342ff054ea901d199329Andy Huang * {@link #forwardFakeMotionEvent(MotionEvent, int)} method that deals with one WebView
57b5078b287b1cec38817e342ff054ea901d199329Andy Huang * idiosyncrasy: it doesn't react well when touch MOVE events stream in without a preceding DOWN.
58b5078b287b1cec38817e342ff054ea901d199329Andy Huang *
59b5078b287b1cec38817e342ff054ea901d199329Andy Huang * <h5>VIEW RECYCLING</h5>
60b5078b287b1cec38817e342ff054ea901d199329Andy Huang * Normally, it would make sense to put all overlay views into a {@link ListView}. But this view
61b5078b287b1cec38817e342ff054ea901d199329Andy Huang * sandwich has unique characteristics: the list items are scrolled based on an external controller,
62b5078b287b1cec38817e342ff054ea901d199329Andy Huang * and we happen to know all of the overlay positions up front. So it didn't make sense to shoehorn
63b5078b287b1cec38817e342ff054ea901d199329Andy Huang * a ListView in and instead, we rolled our own view recycler by borrowing key details from
64b5078b287b1cec38817e342ff054ea901d199329Andy Huang * ListView and AbsListView.
65f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang *
66f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang */
67f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangpublic class ConversationContainer extends ViewGroup implements ScrollListener {
68632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang    private static final String TAG = ConversationViewFragment.LAYOUT_TAG;
69bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang
7047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    private static final int[] BOTTOM_LAYER_VIEW_IDS = {
7147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        R.id.webview
7247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    };
7347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
7447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    private static final int[] TOP_LAYER_VIEW_IDS = {
7547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        R.id.conversation_topmost_overlay
7647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    };
7747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
7831c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang    /**
7931c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang     * Maximum scroll speed (in dp/sec) at which the snap header animation will draw.
8031c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang     * Anything faster than that, and drawing it creates visual artifacting (wagon-wheel effect).
8131c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang     */
8231c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang    private static final float SNAP_HEADER_MAX_SCROLL_SPEED = 600f;
8331c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang
847bdc3750454efe59617b7df945eadd7e59bee954Andy Huang    private ConversationViewAdapter mOverlayAdapter;
852ffaeab36408ccfbd6a375d5d31dc2a15d31a004Andy Huang    private int[] mOverlayBottoms;
86bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang    private ConversationWebView mWebView;
8759e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang    private MessageHeaderView mSnapHeader;
8859e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang    private View mTopMostOverlay;
8959e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang
9059e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang    /**
9159e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang     * This is a hack.
9259e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang     *
9359e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang     * <p>Without this hack enabled, very fast scrolling can sometimes cause the top-most layers
9459e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang     * to skip being drawn for a frame or two. It happens specifically when overlay views are
9559e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang     * attached or added, and WebView happens to draw (on its own) immediately afterwards.
9659e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang     *
9759e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang     * <p>The workaround is to force an additional draw of the top-most overlay. Since the problem
9859e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang     * only occurs when scrolling overlays are added, restrict the additional draw to only occur
9959e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang     * if scrolling overlays were added since the last draw.
10059e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang     */
10159e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang    private boolean mAttachedOverlaySinceLastDraw;
102f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
10347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    private final List<View> mNonScrollingChildren = Lists.newArrayList();
10447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
105bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang    /**
10623014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang     * Current document zoom scale per {@link WebView#getScale()}. This is the ratio of actual
10723014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang     * screen pixels to logical WebView HTML pixels. We use it to convert from one to the other.
108bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang     */
109bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang    private float mScale;
110120ea66184c31bdeb729274054ec913afe3872c1Andy Huang    /**
111120ea66184c31bdeb729274054ec913afe3872c1Andy Huang     * Set to true upon receiving the first touch event. Used to help reject invalid WebView scale
112120ea66184c31bdeb729274054ec913afe3872c1Andy Huang     * values.
113120ea66184c31bdeb729274054ec913afe3872c1Andy Huang     */
114120ea66184c31bdeb729274054ec913afe3872c1Andy Huang    private boolean mTouchInitialized;
115f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
116bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang    /**
117bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang     * System touch-slop distance per {@link ViewConfiguration#getScaledTouchSlop()}.
118bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang     */
119bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang    private final int mTouchSlop;
120bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang    /**
121bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang     * Current scroll position, as dictated by the background {@link WebView}.
122bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang     */
123f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    private int mOffsetY;
124bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang    /**
125bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang     * Original pointer Y for slop calculation.
126bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang     */
127bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang    private float mLastMotionY;
128bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang    /**
129bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang     * Original pointer ID for slop calculation.
130bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang     */
131bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang    private int mActivePointerId;
132bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang    /**
133bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang     * Track pointer up/down state to know whether to send a make-up DOWN event to WebView.
134bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang     * WebView internal logic requires that a stream of {@link MotionEvent#ACTION_MOVE} events be
135bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang     * preceded by a {@link MotionEvent#ACTION_DOWN} event.
136bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang     */
137bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang    private boolean mTouchIsDown = false;
138bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang    /**
139bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang     * Remember if touch interception was triggered on a {@link MotionEvent#ACTION_POINTER_DOWN},
140bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang     * so we can send a make-up event in {@link #onTouchEvent(MotionEvent)}.
141bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang     */
142bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang    private boolean mMissedPointerDown;
143f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
1447bdc3750454efe59617b7df945eadd7e59bee954Andy Huang    /**
145c69cee309fb61e071ecae11e72bf2b8621eeb872Andy Huang     * A recycler that holds removed scrap views, organized by integer item view type. All views
146c69cee309fb61e071ecae11e72bf2b8621eeb872Andy Huang     * in this data structure should be removed from their view parent prior to insertion.
1477bdc3750454efe59617b7df945eadd7e59bee954Andy Huang     */
1487bdc3750454efe59617b7df945eadd7e59bee954Andy Huang    private final DequeMap<Integer, View> mScrapViews = new DequeMap<Integer, View>();
149b5078b287b1cec38817e342ff054ea901d199329Andy Huang
150b5078b287b1cec38817e342ff054ea901d199329Andy Huang    /**
15165fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huang     * The current set of overlay views in the view hierarchy. Looking through this map is faster
15265fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huang     * than traversing the view hierarchy.
153b5078b287b1cec38817e342ff054ea901d199329Andy Huang     * <p>
154b5078b287b1cec38817e342ff054ea901d199329Andy Huang     * WebView sometimes notifies of scroll changes during a draw (or display list generation), when
155b5078b287b1cec38817e342ff054ea901d199329Andy Huang     * it's not safe to detach view children because ViewGroup is in the middle of iterating over
15665fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huang     * its child array. So we remove any child from this list immediately and queue up a task to
15765fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huang     * detach it later. Since nobody other than the detach task references that view in the
15865fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huang     * meantime, we don't need any further checks or synchronization.
15946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang     * <p>
16046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang     * We keep {@link OverlayView} wrappers instead of bare views so that when it's time to dispose
16146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang     * of all views (on data set or adapter change), we can at least recycle them into the typed
16246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang     * scrap piles for later reuse.
163b5078b287b1cec38817e342ff054ea901d199329Andy Huang     */
16446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    private final SparseArray<OverlayView> mOverlayViews;
165b5078b287b1cec38817e342ff054ea901d199329Andy Huang
166b5078b287b1cec38817e342ff054ea901d199329Andy Huang    private int mWidthMeasureSpec;
167b5078b287b1cec38817e342ff054ea901d199329Andy Huang
168c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang    private boolean mDisableLayoutTracing;
169c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang
17031c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang    private final InputSmoother mVelocityTracker;
17131c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang
17246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    private final DataSetObserver mAdapterObserver = new AdapterObserver();
17346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
174cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang    /**
17559e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang     * The adapter index of the lowest overlay item that is above the top of the screen and reports
17659e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang     * {@link ConversationOverlayItem#canPushSnapHeader()}. We calculate this after a pass through
17759e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang     * {@link #positionOverlays(int, int)}.
17859e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang     *
17959e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang     */
18059e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang    private int mSnapIndex;
18159e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang
18259e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang    /**
183cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang     * Child views of this container should implement this interface to be notified when they are
184cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang     * being detached.
185cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang     *
186cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang     */
187cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang    public interface DetachListener {
188cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang        /**
189cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang         * Called on a child view when it is removed from its parent as part of
190cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang         * {@link ConversationContainer} view recycling.
191cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang         */
192cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang        void onDetachedFromParent();
193cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang    }
194cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang
19546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    private static class OverlayView {
19646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        public View view;
19746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        int itemType;
19846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
19946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        public OverlayView(View view, int itemType) {
20046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            this.view = view;
20146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            this.itemType = itemType;
20246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        }
20346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    }
20446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
205f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    public ConversationContainer(Context c) {
206f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        this(c, null);
207f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    }
208f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
209f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    public ConversationContainer(Context c, AttributeSet attrs) {
210f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        super(c, attrs);
211bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang
21246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        mOverlayViews = new SparseArray<OverlayView>();
213b5078b287b1cec38817e342ff054ea901d199329Andy Huang
21431c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang        mVelocityTracker = new InputSmoother(c);
21531c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang
216bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang        mTouchSlop = ViewConfiguration.get(c).getScaledTouchSlop();
217bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang
218bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang        // Disabling event splitting fixes pinch-zoom when the first pointer goes down on the
219bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang        // WebView and the second pointer goes down on an overlay view.
220bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang        // Intercepting ACTION_POINTER_DOWN events allows pinch-zoom to work when the first pointer
221bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang        // goes down on an overlay view.
222bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang        setMotionEventSplittingEnabled(false);
223bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang    }
224bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang
225bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang    @Override
226bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang    protected void onFinishInflate() {
227bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang        super.onFinishInflate();
228bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang
2295ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang        mWebView = (ConversationWebView) findViewById(R.id.webview);
230bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang        mWebView.addScrollListener(this);
23147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
23259e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang        mTopMostOverlay = findViewById(R.id.conversation_topmost_overlay);
23359e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang
23459e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang        mSnapHeader = (MessageHeaderView) findViewById(R.id.snap_header);
23559e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang        mSnapHeader.setSnappy(true);
23659e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang
23747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        for (int id : BOTTOM_LAYER_VIEW_IDS) {
23847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang            mNonScrollingChildren.add(findViewById(id));
23947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        }
24047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        for (int id : TOP_LAYER_VIEW_IDS) {
24147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang            mNonScrollingChildren.add(findViewById(id));
24247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        }
243f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    }
244f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
24559e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang    public MessageHeaderView getSnapHeader() {
24659e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang        return mSnapHeader;
24759e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang    }
24859e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang
2497bdc3750454efe59617b7df945eadd7e59bee954Andy Huang    public void setOverlayAdapter(ConversationViewAdapter a) {
25046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        if (mOverlayAdapter != null) {
25146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            mOverlayAdapter.unregisterDataSetObserver(mAdapterObserver);
25246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            clearOverlays();
25346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        }
254f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        mOverlayAdapter = a;
25546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        if (mOverlayAdapter != null) {
25646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            mOverlayAdapter.registerDataSetObserver(mAdapterObserver);
25746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        }
25851067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang    }
25951067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang
26051067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang    public Adapter getOverlayAdapter() {
26151067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang        return mOverlayAdapter;
262f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    }
263f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
26446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    private void clearOverlays() {
26546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        for (int i = 0, len = mOverlayViews.size(); i < len; i++) {
26646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            detachOverlay(mOverlayViews.valueAt(i));
26746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        }
26846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        mOverlayViews.clear();
26946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    }
27046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
27146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    private void onDataSetChanged() {
27246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        clearOverlays();
27328b7aee7fa1f7d096d33fc823a88a64f7a3fa79dAndy Huang        positionOverlays(0, mOffsetY);
27446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    }
27546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
276bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang    private void forwardFakeMotionEvent(MotionEvent original, int newAction) {
277bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang        MotionEvent newEvent = MotionEvent.obtain(original);
278bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang        newEvent.setAction(newAction);
279bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang        mWebView.onTouchEvent(newEvent);
280bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang        LogUtils.v(TAG, "in Container.OnTouch. fake: action=%d x/y=%f/%f pointers=%d",
281bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang                newEvent.getActionMasked(), newEvent.getX(), newEvent.getY(),
282bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang                newEvent.getPointerCount());
283bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang    }
284bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang
285bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang    /**
286bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang     * Touch slop code was copied from {@link ScrollView#onInterceptTouchEvent(MotionEvent)}.
287bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang     */
288bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang    @Override
289bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang    public boolean onInterceptTouchEvent(MotionEvent ev) {
290120ea66184c31bdeb729274054ec913afe3872c1Andy Huang
291120ea66184c31bdeb729274054ec913afe3872c1Andy Huang        if (!mTouchInitialized) {
292120ea66184c31bdeb729274054ec913afe3872c1Andy Huang            mTouchInitialized = true;
293120ea66184c31bdeb729274054ec913afe3872c1Andy Huang        }
294120ea66184c31bdeb729274054ec913afe3872c1Andy Huang
295632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang        // no interception when WebView handles the first DOWN
296632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang        if (mWebView.isHandlingTouch()) {
297632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang            return false;
298632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang        }
299632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang
300bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang        boolean intercept = false;
301bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang        switch (ev.getActionMasked()) {
302bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang            case MotionEvent.ACTION_POINTER_DOWN:
303632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang                LogUtils.d(TAG, "Container is intercepting non-primary touch!");
304bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang                intercept = true;
305bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang                mMissedPointerDown = true;
306632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang                requestDisallowInterceptTouchEvent(true);
307bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang                break;
308bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang
309bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang            case MotionEvent.ACTION_DOWN:
310bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang                mLastMotionY = ev.getY();
311bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang                mActivePointerId = ev.getPointerId(0);
312bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang                break;
313bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang
314bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang            case MotionEvent.ACTION_MOVE:
315bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang                final int pointerIndex = ev.findPointerIndex(mActivePointerId);
316bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang                final float y = ev.getY(pointerIndex);
317bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang                final int yDiff = (int) Math.abs(y - mLastMotionY);
318bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang                if (yDiff > mTouchSlop) {
319bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang                    mLastMotionY = y;
320bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang                    intercept = true;
321bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang                }
322bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang                break;
323bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang        }
324bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang
325632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang//        LogUtils.v(TAG, "in Container.InterceptTouch. action=%d x/y=%f/%f pointers=%d result=%s",
326632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang//                ev.getActionMasked(), ev.getX(), ev.getY(), ev.getPointerCount(), intercept);
327bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang        return intercept;
328bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang    }
329bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang
330bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang    @Override
331bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang    public boolean onTouchEvent(MotionEvent ev) {
332bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang        final int action = ev.getActionMasked();
333bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang
334632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
335bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang            mTouchIsDown = false;
336bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang        } else if (!mTouchIsDown &&
337bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang                (action == MotionEvent.ACTION_MOVE || action == MotionEvent.ACTION_POINTER_DOWN)) {
338bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang
339bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang            forwardFakeMotionEvent(ev, MotionEvent.ACTION_DOWN);
340bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang            if (mMissedPointerDown) {
341bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang                forwardFakeMotionEvent(ev, MotionEvent.ACTION_POINTER_DOWN);
342bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang                mMissedPointerDown = false;
343bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang            }
344bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang
345bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang            mTouchIsDown = true;
346bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang        }
347bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang
348bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang        final boolean webViewResult = mWebView.onTouchEvent(ev);
349bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang
350632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang//        LogUtils.v(TAG, "in Container.OnTouch. action=%d x/y=%f/%f pointers=%d",
351632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang//                ev.getActionMasked(), ev.getX(), ev.getY(), ev.getPointerCount());
352bb56a1512559a5f024ba213c4bdcfe3d9d9387deAndy Huang        return webViewResult;
353f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    }
354f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
355f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    @Override
356b5078b287b1cec38817e342ff054ea901d199329Andy Huang    public void onNotifierScroll(final int x, final int y) {
35731c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang        mVelocityTracker.onInput(y);
358c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang        mDisableLayoutTracing = true;
3597bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        positionOverlays(x, y);
360c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang        mDisableLayoutTracing = false;
361b5078b287b1cec38817e342ff054ea901d199329Andy Huang    }
362b5078b287b1cec38817e342ff054ea901d199329Andy Huang
3637bdc3750454efe59617b7df945eadd7e59bee954Andy Huang    private void positionOverlays(int x, int y) {
364f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        mOffsetY = y;
36507f8732dcc0e411d0b26d9eb88de582f95aaebb6Andy Huang
36607f8732dcc0e411d0b26d9eb88de582f95aaebb6Andy Huang        /*
367120ea66184c31bdeb729274054ec913afe3872c1Andy Huang         * The scale value that WebView reports is inaccurate when measured during WebView
368120ea66184c31bdeb729274054ec913afe3872c1Andy Huang         * initialization. This bug is present in ICS, so to work around it, we ignore all
36923014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang         * reported values and use a calculated expected value from ConversationWebView instead.
37023014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang         * Only when the user actually begins to touch the view (to, say, begin a zoom) do we begin
37123014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang         * to pay attention to WebView-reported scale values.
37207f8732dcc0e411d0b26d9eb88de582f95aaebb6Andy Huang         */
373120ea66184c31bdeb729274054ec913afe3872c1Andy Huang        if (mTouchInitialized) {
37407f8732dcc0e411d0b26d9eb88de582f95aaebb6Andy Huang            mScale = mWebView.getScale();
37523014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang        } else if (mScale == 0) {
37623014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang            mScale = mWebView.getInitialScale();
37707f8732dcc0e411d0b26d9eb88de582f95aaebb6Andy Huang        }
378c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang        traceLayout("in positionOverlays, raw scale=%f, effective scale=%f", mWebView.getScale(),
379c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang                mScale);
380f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
381e20e1639ff0eca873e9986ec52dbaa497059e01eAndy Huang        if (mOverlayBottoms == null || mOverlayAdapter == null) {
38251067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang            return;
38351067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang        }
38451067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang
385b5078b287b1cec38817e342ff054ea901d199329Andy Huang        // recycle scrolled-off views and add newly visible views
386b5078b287b1cec38817e342ff054ea901d199329Andy Huang
3877bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        // we want consecutive spacers/overlays to stack towards the bottom
3887bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        // so iterate from the bottom of the conversation up
3897bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        // starting with the last spacer bottom and the last adapter item, position adapter views
3907bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        // in a single stack until you encounter a non-contiguous expanded message header,
3917bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        // then decrement to the next spacer.
392b5078b287b1cec38817e342ff054ea901d199329Andy Huang
393c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang        traceLayout("IN positionOverlays, spacerCount=%d overlayCount=%d", mOverlayBottoms.length,
394c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang                mOverlayAdapter.getCount());
395c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang
39659e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang        mSnapIndex = -1;
39759e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang
3987bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        int adapterIndex = mOverlayAdapter.getCount() - 1;
399c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang        int spacerIndex = mOverlayBottoms.length - 1;
400c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang        while (spacerIndex >= 0 && adapterIndex >= 0) {
401c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang
4027bdc3750454efe59617b7df945eadd7e59bee954Andy Huang            final int spacerBottomY = getOverlayBottom(spacerIndex);
4037bdc3750454efe59617b7df945eadd7e59bee954Andy Huang
4047bdc3750454efe59617b7df945eadd7e59bee954Andy Huang            // always place at least one overlay per spacer
40546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            ConversationOverlayItem adapterItem = mOverlayAdapter.getItem(adapterIndex);
4067bdc3750454efe59617b7df945eadd7e59bee954Andy Huang
4077bdc3750454efe59617b7df945eadd7e59bee954Andy Huang            int overlayBottomY = spacerBottomY;
4087bdc3750454efe59617b7df945eadd7e59bee954Andy Huang            int overlayTopY = overlayBottomY - adapterItem.getHeight();
4097bdc3750454efe59617b7df945eadd7e59bee954Andy Huang
410c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang            traceLayout("in loop, spacer=%d overlay=%d t/b=%d/%d (%s)", spacerIndex, adapterIndex,
411c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang                    overlayTopY, overlayBottomY, adapterItem);
4127bdc3750454efe59617b7df945eadd7e59bee954Andy Huang            positionOverlay(adapterIndex, overlayTopY, overlayBottomY);
4137bdc3750454efe59617b7df945eadd7e59bee954Andy Huang
4147bdc3750454efe59617b7df945eadd7e59bee954Andy Huang            // and keep stacking overlays as long as they are contiguous
4157bdc3750454efe59617b7df945eadd7e59bee954Andy Huang            while (--adapterIndex >= 0) {
4167bdc3750454efe59617b7df945eadd7e59bee954Andy Huang                adapterItem = mOverlayAdapter.getItem(adapterIndex);
4177bdc3750454efe59617b7df945eadd7e59bee954Andy Huang                if (!adapterItem.isContiguous()) {
4187bdc3750454efe59617b7df945eadd7e59bee954Andy Huang                    // advance to the next spacer, but stay on this adapter item
4197bdc3750454efe59617b7df945eadd7e59bee954Andy Huang                    break;
420b5078b287b1cec38817e342ff054ea901d199329Andy Huang                }
4217bdc3750454efe59617b7df945eadd7e59bee954Andy Huang
4227bdc3750454efe59617b7df945eadd7e59bee954Andy Huang                overlayBottomY = overlayTopY; // stack on top of previous overlay
4237bdc3750454efe59617b7df945eadd7e59bee954Andy Huang                overlayTopY = overlayBottomY - adapterItem.getHeight();
4247bdc3750454efe59617b7df945eadd7e59bee954Andy Huang
425c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang                traceLayout("in contig loop, spacer=%d overlay=%d t/b=%d/%d (%s)", spacerIndex,
426c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang                        adapterIndex, overlayTopY, overlayBottomY, adapterItem);
4277bdc3750454efe59617b7df945eadd7e59bee954Andy Huang                positionOverlay(adapterIndex, overlayTopY, overlayBottomY);
428b5078b287b1cec38817e342ff054ea901d199329Andy Huang            }
429c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang
430c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang            spacerIndex--;
431b5078b287b1cec38817e342ff054ea901d199329Andy Huang        }
43259e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang
43331c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang        positionSnapHeader(mSnapIndex);
434b5078b287b1cec38817e342ff054ea901d199329Andy Huang    }
435b5078b287b1cec38817e342ff054ea901d199329Andy Huang
436b5078b287b1cec38817e342ff054ea901d199329Andy Huang    /**
4379875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang     * Executes a measure pass over the specified child overlay view and returns the measured
4389875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang     * height. The measurement uses whatever the current container's width measure spec is.
4399875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang     * This method ignores view visibility and returns the height that the view would be if visible.
4409875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang     *
4419875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang     * @param overlayView an overlay view to measure. does not actually have to be attached yet.
4429875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang     * @return height that the view would be if it was visible
443b5078b287b1cec38817e342ff054ea901d199329Andy Huang     */
4449875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang    public int measureOverlay(View overlayView) {
4459875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang        measureOverlayView(overlayView);
4469875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang        return overlayView.getMeasuredHeight();
4479875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang    }
448c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang
4499875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang    /**
4509875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang     * Copied/stolen from {@link ListView}.
4519875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang     */
4529875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang    private void measureOverlayView(View child) {
45347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        MarginLayoutParams p = (MarginLayoutParams) child.getLayoutParams();
454b5078b287b1cec38817e342ff054ea901d199329Andy Huang        if (p == null) {
45547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang            p = (MarginLayoutParams) generateDefaultLayoutParams();
456b5078b287b1cec38817e342ff054ea901d199329Andy Huang        }
457b5078b287b1cec38817e342ff054ea901d199329Andy Huang
458b5078b287b1cec38817e342ff054ea901d199329Andy Huang        int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
45947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang                getPaddingLeft() + getPaddingRight() + p.leftMargin + p.rightMargin, p.width);
460b5078b287b1cec38817e342ff054ea901d199329Andy Huang        int lpHeight = p.height;
461b5078b287b1cec38817e342ff054ea901d199329Andy Huang        int childHeightSpec;
462b5078b287b1cec38817e342ff054ea901d199329Andy Huang        if (lpHeight > 0) {
463b5078b287b1cec38817e342ff054ea901d199329Andy Huang            childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
464b5078b287b1cec38817e342ff054ea901d199329Andy Huang        } else {
465b5078b287b1cec38817e342ff054ea901d199329Andy Huang            childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
466b5078b287b1cec38817e342ff054ea901d199329Andy Huang        }
467b5078b287b1cec38817e342ff054ea901d199329Andy Huang        child.measure(childWidthSpec, childHeightSpec);
468b5078b287b1cec38817e342ff054ea901d199329Andy Huang    }
469b5078b287b1cec38817e342ff054ea901d199329Andy Huang
47046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    private void onOverlayScrolledOff(final int adapterIndex, final OverlayView overlay,
47146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            int overlayTop, int overlayBottom) {
47265fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huang        // detach the view asynchronously, as scroll notification can happen during a draw, when
47365fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huang        // it's not safe to remove children
474b5078b287b1cec38817e342ff054ea901d199329Andy Huang
47565fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huang        // but immediately remove this view from the view set so future lookups don't find it
47665fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huang        mOverlayViews.remove(adapterIndex);
477b5078b287b1cec38817e342ff054ea901d199329Andy Huang
478b5078b287b1cec38817e342ff054ea901d199329Andy Huang        post(new Runnable() {
479b5078b287b1cec38817e342ff054ea901d199329Andy Huang            @Override
480b5078b287b1cec38817e342ff054ea901d199329Andy Huang            public void run() {
48146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                detachOverlay(overlay);
482b5078b287b1cec38817e342ff054ea901d199329Andy Huang            }
483b5078b287b1cec38817e342ff054ea901d199329Andy Huang        });
484b5078b287b1cec38817e342ff054ea901d199329Andy Huang
485b5078b287b1cec38817e342ff054ea901d199329Andy Huang        // push it out of view immediately
486b5078b287b1cec38817e342ff054ea901d199329Andy Huang        // otherwise this scrolled-off header will continue to draw until the runnable runs
48746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        layoutOverlay(overlay.view, overlayTop, overlayBottom);
4887bdc3750454efe59617b7df945eadd7e59bee954Andy Huang    }
4897bdc3750454efe59617b7df945eadd7e59bee954Andy Huang
4908fdce82be7c643375470cd6dc48ff531f9175dd0Andy Huang    /**
491c69cee309fb61e071ecae11e72bf2b8621eeb872Andy Huang     * Returns an existing scrap view, if available. The view will already be removed from the view
4928fdce82be7c643375470cd6dc48ff531f9175dd0Andy Huang     * hierarchy. This method will not remove the view from the scrap heap.
4938fdce82be7c643375470cd6dc48ff531f9175dd0Andy Huang     *
4948fdce82be7c643375470cd6dc48ff531f9175dd0Andy Huang     */
4957bdc3750454efe59617b7df945eadd7e59bee954Andy Huang    public View getScrapView(int type) {
4968fdce82be7c643375470cd6dc48ff531f9175dd0Andy Huang        return mScrapViews.peek(type);
4977bdc3750454efe59617b7df945eadd7e59bee954Andy Huang    }
4987bdc3750454efe59617b7df945eadd7e59bee954Andy Huang
4997bdc3750454efe59617b7df945eadd7e59bee954Andy Huang    public void addScrapView(int type, View v) {
5007bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        mScrapViews.add(type, v);
501b5078b287b1cec38817e342ff054ea901d199329Andy Huang    }
502b5078b287b1cec38817e342ff054ea901d199329Andy Huang
50346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    private void detachOverlay(OverlayView overlay) {
504c69cee309fb61e071ecae11e72bf2b8621eeb872Andy Huang        // Prefer removeViewInLayout over removeView. The typical followup layout pass is unneeded
505c69cee309fb61e071ecae11e72bf2b8621eeb872Andy Huang        // because removing overlay views doesn't affect overall layout.
506c69cee309fb61e071ecae11e72bf2b8621eeb872Andy Huang        removeViewInLayout(overlay.view);
50746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        mScrapViews.add(overlay.itemType, overlay.view);
50846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        if (overlay.view instanceof DetachListener) {
50946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            ((DetachListener) overlay.view).onDetachedFromParent();
510cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang        }
511cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang    }
512cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang
513cf5aeaea234a52ff153729d959b207af1ab62abcAndy Huang    @Override
514f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
515f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
516632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang        if (LogUtils.isLoggable(TAG, LogUtils.DEBUG)) {
517632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang            LogUtils.d(TAG, "*** IN header container onMeasure spec for w/h=%s/%s",
518632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang                    MeasureSpec.toString(widthMeasureSpec),
519632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang                    MeasureSpec.toString(heightMeasureSpec));
520632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang        }
521f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
52247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        for (View nonScrollingChild : mNonScrollingChildren) {
52347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang            if (nonScrollingChild.getVisibility() != GONE) {
52447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang                measureChildWithMargins(nonScrollingChild, widthMeasureSpec, 0 /* widthUsed */,
52547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang                        heightMeasureSpec, 0 /* heightUsed */);
52647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang            }
5273233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang        }
528b5078b287b1cec38817e342ff054ea901d199329Andy Huang        mWidthMeasureSpec = widthMeasureSpec;
5297bdc3750454efe59617b7df945eadd7e59bee954Andy Huang
5309875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang        // onLayout will re-measure and re-position overlays for the new container size, but the
5319875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang        // spacer offsets would still need to be updated to have them draw at their new locations.
532f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    }
533f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
534f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    @Override
535f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    protected void onLayout(boolean changed, int l, int t, int r, int b) {
536632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang        LogUtils.d(TAG, "*** IN header container onLayout");
537f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
53847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        for (View nonScrollingChild : mNonScrollingChildren) {
53947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang            if (nonScrollingChild.getVisibility() != GONE) {
54047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang                final int w = nonScrollingChild.getMeasuredWidth();
54147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang                final int h = nonScrollingChild.getMeasuredHeight();
54247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
54347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang                final MarginLayoutParams lp =
54447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang                        (MarginLayoutParams) nonScrollingChild.getLayoutParams();
54547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
54647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang                final int childLeft = lp.leftMargin;
54747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang                final int childTop = lp.topMargin;
54847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang                nonScrollingChild.layout(childLeft, childTop, childLeft + w, childTop + h);
54947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang            }
55047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        }
5515ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang
552e20e1639ff0eca873e9986ec52dbaa497059e01eAndy Huang        if (mOverlayAdapter != null) {
553e20e1639ff0eca873e9986ec52dbaa497059e01eAndy Huang            // being in a layout pass means overlay children may require measurement,
554e20e1639ff0eca873e9986ec52dbaa497059e01eAndy Huang            // so invalidate them
555e20e1639ff0eca873e9986ec52dbaa497059e01eAndy Huang            for (int i = 0, len = mOverlayAdapter.getCount(); i < len; i++) {
556e20e1639ff0eca873e9986ec52dbaa497059e01eAndy Huang                mOverlayAdapter.getItem(i).invalidateMeasurement();
557e20e1639ff0eca873e9986ec52dbaa497059e01eAndy Huang            }
5588778a8717b21335ebc3127b7be1a2cddf6b64697Andy Huang        }
5598778a8717b21335ebc3127b7be1a2cddf6b64697Andy Huang
5608778a8717b21335ebc3127b7be1a2cddf6b64697Andy Huang        positionOverlays(0, mOffsetY);
5619875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang    }
5629875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang
56347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    @Override
5647517e3b61b898a57f19be0671f70d58a82224643Andy Huang    public void requestLayout() {
5657517e3b61b898a57f19be0671f70d58a82224643Andy Huang        Utils.checkRequestLayout(this);
5667517e3b61b898a57f19be0671f70d58a82224643Andy Huang        super.requestLayout();
5677517e3b61b898a57f19be0671f70d58a82224643Andy Huang    }
5687517e3b61b898a57f19be0671f70d58a82224643Andy Huang
5697517e3b61b898a57f19be0671f70d58a82224643Andy Huang    @Override
57059e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang    protected void dispatchDraw(Canvas canvas) {
57159e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang        super.dispatchDraw(canvas);
57259e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang
57359e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang        if (mAttachedOverlaySinceLastDraw) {
57459e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang            drawChild(canvas, mTopMostOverlay, getDrawingTime());
57559e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang            mAttachedOverlaySinceLastDraw = false;
57659e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang        }
57759e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang    }
57859e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang
57959e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang    @Override
58047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    protected LayoutParams generateDefaultLayoutParams() {
58147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        return new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
58247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    }
58347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
58447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    @Override
58547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    public LayoutParams generateLayoutParams(AttributeSet attrs) {
58647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        return new MarginLayoutParams(getContext(), attrs);
58747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    }
58847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
58947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    @Override
59047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
59147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        return new MarginLayoutParams(p);
59247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    }
59347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
59447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    @Override
59547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
59647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        return p instanceof MarginLayoutParams;
59747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    }
59847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
5997bdc3750454efe59617b7df945eadd7e59bee954Andy Huang    private int getOverlayBottom(int spacerIndex) {
6007bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        // TODO: round or truncate?
6017bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        return (int) (mOverlayBottoms[spacerIndex] * mScale);
602b5078b287b1cec38817e342ff054ea901d199329Andy Huang    }
603b5078b287b1cec38817e342ff054ea901d199329Andy Huang
6047bdc3750454efe59617b7df945eadd7e59bee954Andy Huang    private void positionOverlay(int adapterIndex, int overlayTopY, int overlayBottomY) {
60546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        final OverlayView overlay = mOverlayViews.get(adapterIndex);
60646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        final ConversationOverlayItem item = mOverlayAdapter.getItem(adapterIndex);
6079875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang
60831c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang        // save off the item's current top for later snap calculations
60931c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang        item.setTop(overlayTopY);
61031c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang
611c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang        // is the overlay visible and does it have non-zero height?
612c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang        if (overlayTopY != overlayBottomY && overlayBottomY > mOffsetY
613c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang                && overlayTopY < mOffsetY + getHeight()) {
61446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            View overlayView = overlay != null ? overlay.view : null;
6157bdc3750454efe59617b7df945eadd7e59bee954Andy Huang            // show and/or move overlay
6167bdc3750454efe59617b7df945eadd7e59bee954Andy Huang            if (overlayView == null) {
6177bdc3750454efe59617b7df945eadd7e59bee954Andy Huang                overlayView = addOverlayView(adapterIndex);
6189875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang                measureOverlayView(overlayView);
6199875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang                item.markMeasurementValid();
6209875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang                traceLayout("show/measure overlay %d", adapterIndex);
621c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang            } else {
622c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang                traceLayout("move overlay %d", adapterIndex);
6239875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang                if (!item.isMeasurementValid()) {
6249875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang                    measureOverlayView(overlayView);
6259875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang                    item.markMeasurementValid();
6269875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang                    traceLayout("and (re)measure overlay %d, old/new heights=%d/%d", adapterIndex,
6279875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang                            overlayView.getHeight(), overlayView.getMeasuredHeight());
6289875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang                }
6297bdc3750454efe59617b7df945eadd7e59bee954Andy Huang            }
6309875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang            traceLayout("laying out overlay %d with h=%d", adapterIndex,
6319875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang                    overlayView.getMeasuredHeight());
63265fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huang            layoutOverlay(overlayView, overlayTopY, overlayTopY + overlayView.getMeasuredHeight());
6337bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        } else {
6347bdc3750454efe59617b7df945eadd7e59bee954Andy Huang            // hide overlay
63546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            if (overlay != null) {
636c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang                traceLayout("hide overlay %d", adapterIndex);
63746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                onOverlayScrolledOff(adapterIndex, overlay, overlayTopY, overlayBottomY);
638c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang            } else {
639c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang                traceLayout("ignore non-visible overlay %d", adapterIndex);
6407bdc3750454efe59617b7df945eadd7e59bee954Andy Huang            }
6417bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        }
64259e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang
64359e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang        if (overlayTopY <= mOffsetY && item.canPushSnapHeader()) {
64459e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang            if (mSnapIndex == -1) {
64559e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang                mSnapIndex = adapterIndex;
64659e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang            } else if (adapterIndex > mSnapIndex) {
64759e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang                mSnapIndex = adapterIndex;
64859e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang            }
64959e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang        }
65059e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang
651b5078b287b1cec38817e342ff054ea901d199329Andy Huang    }
652b5078b287b1cec38817e342ff054ea901d199329Andy Huang
6537bdc3750454efe59617b7df945eadd7e59bee954Andy Huang    // layout an existing view
6547bdc3750454efe59617b7df945eadd7e59bee954Andy Huang    // need its top offset into the conversation, its height, and the scroll offset
655c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang    private void layoutOverlay(View child, int childTop, int childBottom) {
6567bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        final int top = childTop - mOffsetY;
657c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang        final int bottom = childBottom - mOffsetY;
65847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
65947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
66047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        final int childLeft = getPaddingLeft() + lp.leftMargin;
66147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
66247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        child.layout(childLeft, top, childLeft + child.getMeasuredWidth(), bottom);
663b5078b287b1cec38817e342ff054ea901d199329Andy Huang    }
664b5078b287b1cec38817e342ff054ea901d199329Andy Huang
6657bdc3750454efe59617b7df945eadd7e59bee954Andy Huang    private View addOverlayView(int adapterIndex) {
6667bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        final int itemType = mOverlayAdapter.getItemViewType(adapterIndex);
6677bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        final View convertView = mScrapViews.poll(itemType);
6687bdc3750454efe59617b7df945eadd7e59bee954Andy Huang
669256b35c0a8287f48c28e0d1ba3fae65790063295Andy Huang        final View view = mOverlayAdapter.getView(adapterIndex, convertView, this);
67046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        mOverlayViews.put(adapterIndex, new OverlayView(view, itemType));
671f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
67259e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang        final int index = BOTTOM_LAYER_VIEW_IDS.length;
67347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
674c69cee309fb61e071ecae11e72bf2b8621eeb872Andy Huang        if (convertView == view) {
675c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang            LogUtils.d(TAG, "want to REUSE scrolled-in view: index=%d obj=%s", adapterIndex, view);
6767bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        } else {
677c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang            LogUtils.d(TAG, "want to CREATE scrolled-in view: index=%d obj=%s", adapterIndex, view);
678f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        }
679c69cee309fb61e071ecae11e72bf2b8621eeb872Andy Huang        addViewInLayout(view, index, view.getLayoutParams(), true /* preventRequestLayout */);
6807bdc3750454efe59617b7df945eadd7e59bee954Andy Huang
68159e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang        mAttachedOverlaySinceLastDraw = true;
68259e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang
6837bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        return view;
684f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    }
685f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
68631c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang    // render and/or re-position snap header
68731c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang    private void positionSnapHeader(int snapIndex) {
68831c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang        ConversationOverlayItem snapItem = null;
68931c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang        if (snapIndex != -1) {
69031c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang            final ConversationOverlayItem item = mOverlayAdapter.getItem(snapIndex);
69131c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang            if (item.canBecomeSnapHeader()) {
69231c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang                snapItem = item;
69331c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang            }
69431c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang        }
69531c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang        if (snapItem == null) {
69631c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang            mSnapHeader.setVisibility(GONE);
69731c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang            mSnapHeader.unbind();
69831c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang            return;
69931c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang        }
70031c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang
70131c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang        snapItem.bindView(mSnapHeader, false /* measureOnly */);
70231c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang        mSnapHeader.setVisibility(VISIBLE);
70331c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang
7040a648c9b8f9d02f89e8711e97215b0e30f2cf0b6Andy Huang        // overlap is negative or zero; bump the snap header upwards by that much
70531c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang        int overlap = 0;
70631c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang
70731c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang        final ConversationOverlayItem next = findNextPushingOverlay(snapIndex + 1);
70831c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang        if (next != null) {
70931c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang            overlap = Math.min(0, next.getTop() - mSnapHeader.getHeight() - mOffsetY);
71031c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang
71131c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang            // disable overlap drawing past a certain speed
71231c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang            if (overlap < 0) {
71331c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang                final Float v = mVelocityTracker.getSmoothedVelocity();
71431c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang                if (v != null && v > SNAP_HEADER_MAX_SCROLL_SPEED) {
71531c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang                    overlap = 0;
71631c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang                }
71731c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang            }
71831c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang        }
7190a648c9b8f9d02f89e8711e97215b0e30f2cf0b6Andy Huang
7200a648c9b8f9d02f89e8711e97215b0e30f2cf0b6Andy Huang        mSnapHeader.setTranslationY(overlap);
72131c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang    }
72231c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang
72331c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang    // find the next header that can push the snap header up
72431c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang    private ConversationOverlayItem findNextPushingOverlay(int start) {
72531c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang        int value = -1;
72631c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang        for (int i = start, len = mOverlayAdapter.getCount(); i < len; i++) {
72731c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang            final ConversationOverlayItem next = mOverlayAdapter.getItem(i);
72831c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang            if (next.canPushSnapHeader()) {
72931c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang                return next;
73031c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang            }
73131c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang        }
73231c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang        return null;
73331c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang    }
73431c38a8247b4583ac1cc506acf8454d8922ee491Andy Huang
735c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang    /**
736c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang     * Prevents any layouts from happening until the next time {@link #onGeometryChange(int[])} is
737c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang     * called. Useful when you know the HTML spacer coordinates are inconsistent with adapter items.
738c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang     * <p>
739c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang     * If you call this, you must ensure that a followup call to {@link #onGeometryChange(int[])}
740c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang     * is made later, when the HTML spacer coordinates are updated.
741c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang     *
742c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang     */
743c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang    public void invalidateSpacerGeometry() {
744c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang        mOverlayBottoms = null;
745c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang    }
746c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang
7477bdc3750454efe59617b7df945eadd7e59bee954Andy Huang    public void onGeometryChange(int[] overlayBottoms) {
748c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang        traceLayout("*** got overlay spacer bottoms:");
7497bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        for (int offsetY : overlayBottoms) {
750c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang            traceLayout("%d", offsetY);
751f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        }
752f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
7537bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        mOverlayBottoms = overlayBottoms;
7547bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        positionOverlays(0, mOffsetY);
755f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    }
756f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
757c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang    private void traceLayout(String msg, Object... params) {
758c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang        if (mDisableLayoutTracing) {
759c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang            return;
760c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang        }
7617f9ef60c5e42470814f6b4bb8ed771c17fdfea22Andy Huang        LogUtils.d(TAG, msg, params);
762c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang    }
763c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang
76446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    private class AdapterObserver extends DataSetObserver {
76546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        @Override
76646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        public void onChanged() {
76746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            onDataSetChanged();
76846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        }
76946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    }
770f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang}
771