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