ConversationViewFragment.java revision 986776bbd046c9569a4abb67501819bee61e7194
19b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira/* 29b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira * Copyright (C) 2012 Google Inc. 39b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira * Licensed to The Android Open Source Project. 49b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira * 59b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira * Licensed under the Apache License, Version 2.0 (the "License"); 69b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira * you may not use this file except in compliance with the License. 79b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira * You may obtain a copy of the License at 89b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira * 99b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira * http://www.apache.org/licenses/LICENSE-2.0 109b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira * 119b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira * Unless required by applicable law or agreed to in writing, software 129b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira * distributed under the License is distributed on an "AS IS" BASIS, 139b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 149b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira * See the License for the specific language governing permissions and 159b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira * limitations under the License. 169b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira */ 179b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira 189b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereirapackage com.android.mail.ui; 199b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira 20b8361c9f8938b74977316319885998aae09aab77Paul Westbrookimport android.content.ContentResolver; 219b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereiraimport android.content.Context; 228e915724b6e4374da9b70161ee0a55f0c763e563Mindy Pereiraimport android.content.Loader; 23ad0c30d0517e06c472c76b11795092f5e67d8f5amindypimport android.content.res.Resources; 249b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereiraimport android.database.Cursor; 259d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huangimport android.database.DataSetObserver; 26b8361c9f8938b74977316319885998aae09aab77Paul Westbrookimport android.net.Uri; 27cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrookimport android.os.AsyncTask; 289b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereiraimport android.os.Bundle; 293bcf180f8104bc27319086a9a6ece5a3c2917c37mindypimport android.os.SystemClock; 303af481c20edd84419d0d4a8476994ac008c0b97aAndrew Sappersteinimport android.support.v4.text.BidiFormatter; 318ec43e877a9c1925514f066655984e21fbd255e8Andrew Sappersteinimport android.support.v4.util.ArrayMap; 3247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huangimport android.text.TextUtils; 339b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereiraimport android.view.LayoutInflater; 34c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huangimport android.view.View; 354071c2f73218ce75750345557bb31a9110737841Mark Weiimport android.view.View.OnLayoutChangeListener; 369b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereiraimport android.view.ViewGroup; 37f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport android.webkit.ConsoleMessage; 38cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrookimport android.webkit.CookieManager; 39cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrookimport android.webkit.CookieSyncManager; 40974c966c14000d42f089e2021b098d5cf61f9245Mindy Pereiraimport android.webkit.JavascriptInterface; 41f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport android.webkit.WebChromeClient; 42f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport android.webkit.WebSettings; 4317a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huangimport android.webkit.WebView; 44821fa87279d590e682effbdb652c8a92e805eec8Andrew Sappersteinimport android.widget.Button; 459b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira 46821e578a71c7015646522e729600618f0ec16fc0Tony Mantlerimport com.android.emailcommon.mail.Address; 4759e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huangimport com.android.mail.FormattedDateBuilder; 489b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereiraimport com.android.mail.R; 49e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huangimport com.android.mail.analytics.Analytics; 505ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huangimport com.android.mail.browse.ConversationContainer; 51adbf3e8cadb66666f307352b72537fbac57b916fAndy Huangimport com.android.mail.browse.ConversationContainer.OverlayPosition; 528812d3c50e35c4f2a02d29c35c76082c4ebec0cdAndrew Sappersteinimport com.android.mail.browse.ConversationMessage; 5346dfba6160b55a582b344328067e3dafeb881dd9Andy Huangimport com.android.mail.browse.ConversationOverlayItem; 547bdc3750454efe59617b7df945eadd7e59bee954Andy Huangimport com.android.mail.browse.ConversationViewAdapter; 5514f937408fe2451a91b44d3cd7d141347e716775Andrew Sappersteinimport com.android.mail.browse.ConversationViewAdapter.BorderItem; 5646dfba6160b55a582b344328067e3dafeb881dd9Andy Huangimport com.android.mail.browse.ConversationViewAdapter.MessageFooterItem; 577bdc3750454efe59617b7df945eadd7e59bee954Andy Huangimport com.android.mail.browse.ConversationViewAdapter.MessageHeaderItem; 5846dfba6160b55a582b344328067e3dafeb881dd9Andy Huangimport com.android.mail.browse.ConversationViewAdapter.SuperCollapsedBlockItem; 595ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huangimport com.android.mail.browse.ConversationViewHeader; 605ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huangimport com.android.mail.browse.ConversationWebView; 618ec43e877a9c1925514f066655984e21fbd255e8Andrew Sappersteinimport com.android.mail.browse.InlineAttachmentViewIntentBuilderCreator; 628ec43e877a9c1925514f066655984e21fbd255e8Andrew Sappersteinimport com.android.mail.browse.InlineAttachmentViewIntentBuilderCreatorHolder; 63c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huangimport com.android.mail.browse.MailWebView.ContentSizeChangeListener; 647bdc3750454efe59617b7df945eadd7e59bee954Andy Huangimport com.android.mail.browse.MessageCursor; 6559e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huangimport com.android.mail.browse.MessageHeaderView; 66adbf3e8cadb66666f307352b72537fbac57b916fAndy Huangimport com.android.mail.browse.ScrollIndicatorsView; 6746dfba6160b55a582b344328067e3dafeb881dd9Andy Huangimport com.android.mail.browse.SuperCollapsedBlock; 680b7ed6fae6e36f2abc4ca916764177d1879731b4Andy Huangimport com.android.mail.browse.WebViewContextMenu; 69c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrookimport com.android.mail.content.ObjectCursor; 70562c5ba7235948cf1d20a9afa40e67cd62f43cf7Andrew Sappersteinimport com.android.mail.print.PrintUtils; 719b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereiraimport com.android.mail.providers.Account; 729b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereiraimport com.android.mail.providers.Conversation; 73f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport com.android.mail.providers.Message; 74f323c046034b4658a80438575d8e9f01d92e57e6Alice Yangimport com.android.mail.providers.Settings; 75b8361c9f8938b74977316319885998aae09aab77Paul Westbrookimport com.android.mail.providers.UIProvider; 76cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huangimport com.android.mail.ui.ConversationViewState.ExpansionState; 77376294bbb5ded471ad577fdb492875a837021d08Andrew Sappersteinimport com.android.mail.utils.ConversationViewUtils; 78b334c9035e9b7a38766bb66c29da2208525d1e11Paul Westbrookimport com.android.mail.utils.LogTag; 799b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereiraimport com.android.mail.utils.LogUtils; 802e9acfe527426c00b5df2ca66189262dc40c8575Andy Huangimport com.android.mail.utils.Utils; 81543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huangimport com.google.common.collect.ImmutableList; 8246dfba6160b55a582b344328067e3dafeb881dd9Andy Huangimport com.google.common.collect.Lists; 8305c70c88e6b421bedce134799a0ea928dbf44cd5Andy Huangimport com.google.common.collect.Maps; 84b8331b4565566ca733997398e8c07a26cd2bee98Andy Huangimport com.google.common.collect.Sets; 8565fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huang 86eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedyimport java.util.ArrayList; 8746dfba6160b55a582b344328067e3dafeb881dd9Andy Huangimport java.util.List; 8805c70c88e6b421bedce134799a0ea928dbf44cd5Andy Huangimport java.util.Map; 89b8331b4565566ca733997398e8c07a26cd2bee98Andy Huangimport java.util.Set; 909b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira 919b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira/** 929b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira * The conversation view UI component. 939b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira */ 949f957f3463fd149d33c209409e2bba500b539177Andrew Sappersteinpublic class ConversationViewFragment extends AbstractConversationViewFragment implements 954ddda2f0a4ee5381a90779a6939b05b064ce5d11Andrew Sapperstein SuperCollapsedBlock.OnClickListener, OnLayoutChangeListener, 964ddda2f0a4ee5381a90779a6939b05b064ce5d11Andrew Sapperstein MessageHeaderView.MessageHeaderViewCallbacks { 978e915724b6e4374da9b70161ee0a55f0c763e563Mindy Pereira 98b334c9035e9b7a38766bb66c29da2208525d1e11Paul Westbrook private static final String LOG_TAG = LogTag.getLogTag(); 99632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang public static final String LAYOUT_TAG = "ConvLayout"; 1009b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira 1019d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang /** 1021b3cc47f54072105c161d6ed557550e0e149b8bbmindyp * Difference in the height of the message header whose details have been expanded/collapsed 1031b3cc47f54072105c161d6ed557550e0e149b8bbmindyp */ 1041b3cc47f54072105c161d6ed557550e0e149b8bbmindyp private int mDiff = 0; 1051b3cc47f54072105c161d6ed557550e0e149b8bbmindyp 1061b3cc47f54072105c161d6ed557550e0e149b8bbmindyp /** 1079d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang * Default value for {@link #mLoadWaitReason}. Conversation load will happen immediately. 1089d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang */ 1099d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang private final int LOAD_NOW = 0; 1109d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang /** 1119d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang * Value for {@link #mLoadWaitReason} that means we are offscreen and waiting for the visible 1129d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang * conversation to finish loading before beginning our load. 1139d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang * <p> 1149d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang * When this value is set, the fragment should register with {@link ConversationListCallbacks} 1159d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang * to know when the visible conversation is loaded. When it is unset, it should unregister. 1169d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang */ 1179d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang private final int LOAD_WAIT_FOR_INITIAL_CONVERSATION = 1; 1189d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang /** 1199d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang * Value for {@link #mLoadWaitReason} used when a conversation is too heavyweight to load at 1209d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang * all when not visible (e.g. requires network fetch, or too complex). Conversation load will 1219d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang * wait until this fragment is visible. 1229d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang */ 1239d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang private final int LOAD_WAIT_UNTIL_VISIBLE = 2; 1243bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp 1259f957f3463fd149d33c209409e2bba500b539177Andrew Sapperstein protected ConversationContainer mConversationContainer; 1269b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira 1279f957f3463fd149d33c209409e2bba500b539177Andrew Sapperstein protected ConversationWebView mWebView; 1289b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira 12956d83850db72592a16f4e6ee9e0d59b60ec0824aMark Wei private ScrollIndicatorsView mScrollIndicators; 13056d83850db72592a16f4e6ee9e0d59b60ec0824aMark Wei 131376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein private ConversationViewProgressController mProgressController; 132376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein 133821fa87279d590e682effbdb652c8a92e805eec8Andrew Sapperstein private Button mNewMessageBar; 13447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 1359f957f3463fd149d33c209409e2bba500b539177Andrew Sapperstein protected HtmlConversationTemplates mTemplates; 136f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 137f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang private final MailJsBridge mJsBridge = new MailJsBridge(); 138f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 1399f957f3463fd149d33c209409e2bba500b539177Andrew Sapperstein protected ConversationViewAdapter mAdapter; 14051067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang 141b1d184d164303d83d342c60e4bffc732aa10ac2cAndrew Sapperstein protected boolean mViewsCreated; 1424071c2f73218ce75750345557bb31a9110737841Mark Wei // True if we attempted to render before the views were laid out 1434071c2f73218ce75750345557bb31a9110737841Mark Wei // We will render immediately once layout is done 1444071c2f73218ce75750345557bb31a9110737841Mark Wei private boolean mNeedRender; 14551067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang 14646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang /** 14746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang * Temporary string containing the message bodies of the messages within a super-collapsed 14846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang * block, for one-time use during block expansion. We cannot easily pass the body HTML 14946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang * into JS without problematic escaping, so hold onto it momentarily and signal JS to fetch it 15046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang * using {@link MailJsBridge}. 15146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang */ 15246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang private String mTempBodiesHtml; 15346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 154632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang private int mMaxAutoLoadMessages; 155632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang 1569f957f3463fd149d33c209409e2bba500b539177Andrew Sapperstein protected int mSideMarginPx; 15702f9d18a54072db8d86c524f9c09e508092ddd7cAndy Huang 1589d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang /** 1599d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang * If this conversation fragment is not visible, and it's inappropriate to load up front, 1609d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang * this is the reason we are waiting. This flag should be cleared once it's okay to load 1619d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang * the conversation. 1629d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang */ 1639d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang private int mLoadWaitReason = LOAD_NOW; 164632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang 1653bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp private boolean mEnableContentReadySignal; 16628b7aee7fa1f7d096d33fc823a88a64f7a3fa79dAndy Huang 167dde3f9ff76c1a7f9a6e9fa959a6422a8a8d82e81mindyp private ContentSizeChangeListener mWebViewSizeChangeListener; 168dde3f9ff76c1a7f9a6e9fa959a6422a8a8d82e81mindyp 169e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang private float mWebViewYPercent; 170e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang 171e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang /** 172e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang * Has loadData been called on the WebView yet? 173e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang */ 174e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang private boolean mWebViewLoadedData; 175e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang 17663b3c6725d60386f564ab53e3ba6495f0c158d9bAndy Huang private long mWebViewLoadStartMs; 17763b3c6725d60386f564ab53e3ba6495f0c158d9bAndy Huang 17805c70c88e6b421bedce134799a0ea928dbf44cd5Andy Huang private final Map<String, String> mMessageTransforms = Maps.newHashMap(); 17905c70c88e6b421bedce134799a0ea928dbf44cd5Andy Huang 1809d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang private final DataSetObserver mLoadedObserver = new DataSetObserver() { 1819d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang @Override 1829d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang public void onChanged() { 183376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein getHandler().post(new FragmentRunnable("delayedConversationLoad", 184376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein ConversationViewFragment.this) { 1859d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang @Override 1869d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang public void go() { 1879d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang LogUtils.d(LOG_TAG, "CVF load observer fired, this=%s", 1889d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang ConversationViewFragment.this); 1899d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang handleDelayedConversationLoad(); 1909d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang } 1919d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang }); 1929d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang } 1939d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang }; 194f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 195376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein private final Runnable mOnProgressDismiss = new FragmentRunnable("onProgressDismiss", this) { 1967d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang @Override 1977d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang public void go() { 19858192e5202d379ff62bf99995b08a0a7cf6646d1Scott Kennedy LogUtils.d(LOG_TAG, "onProgressDismiss go() - isUserVisible() = %b", isUserVisible()); 1997d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang if (isUserVisible()) { 2007d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang onConversationSeen(); 2017d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang } 20230bcfe7b69f9d8e0cad2ee62dae9d3571cd0d88bAndy Huang mWebView.onRenderComplete(); 2037d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang } 2047d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang }; 2057d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang 206bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang private static final boolean DEBUG_DUMP_CONVERSATION_HTML = false; 20747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang private static final boolean DISABLE_OFFSCREEN_LOADING = false; 20806c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang private static final boolean DEBUG_DUMP_CURSOR_CONTENTS = false; 209e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang 210e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang private static final String BUNDLE_KEY_WEBVIEW_Y_PERCENT = 211e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang ConversationViewFragment.class.getName() + "webview-y-percent"; 212bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang 2132fd167d6131482da984768b5ee75cefa32ed3e32Andrew Sapperstein private BidiFormatter mBidiFormatter; 2143af481c20edd84419d0d4a8476994ac008c0b97aAndrew Sapperstein 2156c51158ad3269f157424e6c7bd488425c98da08fVikram Aggarwal /** 2168ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein * Contains a mapping between inline image attachments and their local message id. 2178ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein */ 2188ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein private Map<String, String> mUrlToMessageIdMap; 2198ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein 2208ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein /** 2216c51158ad3269f157424e6c7bd488425c98da08fVikram Aggarwal * Constructor needs to be public to handle orientation changes and activity lifecycle events. 2226c51158ad3269f157424e6c7bd488425c98da08fVikram Aggarwal */ 223f0ea4849bf7a2c11f99ca0b42307ae8ba665b1dcPaul Westbrook public ConversationViewFragment() {} 2249b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira 2259b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira /** 2269b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira * Creates a new instance of {@link ConversationViewFragment}, initialized 227632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang * to display a conversation with other parameters inherited/copied from an existing bundle, 228632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang * typically one created using {@link #makeBasicArgs}. 229632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang */ 230632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang public static ConversationViewFragment newInstance(Bundle existingArgs, 231632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang Conversation conversation) { 232632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang ConversationViewFragment f = new ConversationViewFragment(); 233632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang Bundle args = new Bundle(existingArgs); 234632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang args.putParcelable(ARG_CONVERSATION, conversation); 235632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang f.setArguments(args); 236632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang return f; 237632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang } 238632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang 239f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp @Override 240adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang public void onAccountChanged(Account newAccount, Account oldAccount) { 241adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang // if overview mode has changed, re-render completely (no need to also update headers) 242adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang if (isOverviewMode(newAccount) != isOverviewMode(oldAccount)) { 243adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang setupOverviewMode(); 244adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang final MessageCursor c = getMessageCursor(); 245adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang if (c != null) { 246adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang renderConversation(c); 247adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang } else { 248adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang // Null cursor means this fragment is either waiting to load or in the middle of 249adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang // loading. Either way, a future render will happen anyway, and the new setting 250adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang // will take effect when that happens. 251adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang } 252adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang return; 253adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang } 254adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang 255f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp // settings may have been updated; refresh views that are known to 256f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp // depend on settings 257f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp mAdapter.notifyDataSetChanged(); 258632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang } 259632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang 2609b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira @Override 2619b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira public void onActivityCreated(Bundle savedInstanceState) { 2629d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang LogUtils.d(LOG_TAG, "IN CVF.onActivityCreated, this=%s visible=%s", this, isUserVisible()); 2639b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira super.onActivityCreated(savedInstanceState); 2641abfcaf0ee345acee28f0b4892314119082b28a2Mark Wei 2651abfcaf0ee345acee28f0b4892314119082b28a2Mark Wei if (mActivity == null || mActivity.isFinishing()) { 2661abfcaf0ee345acee28f0b4892314119082b28a2Mark Wei // Activity is finishing, just bail. 2671abfcaf0ee345acee28f0b4892314119082b28a2Mark Wei return; 2681abfcaf0ee345acee28f0b4892314119082b28a2Mark Wei } 2691abfcaf0ee345acee28f0b4892314119082b28a2Mark Wei 270f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp Context context = getContext(); 271f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp mTemplates = new HtmlConversationTemplates(context); 27259e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang 273f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp final FormattedDateBuilder dateBuilder = new FormattedDateBuilder(context); 27459e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang 2758081df46ef5a7794374e41cd1836e778a2da9b31Paul Westbrook mAdapter = new ConversationViewAdapter(mActivity, this, 276f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp getLoaderManager(), this, getContactInfoSource(), this, 2772fd167d6131482da984768b5ee75cefa32ed3e32Andrew Sapperstein this, mAddressCache, dateBuilder, mBidiFormatter); 27851067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang mConversationContainer.setOverlayAdapter(mAdapter); 27951067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang 28059e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang // set up snap header (the adapter usually does this with the other ones) 28185ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein mConversationContainer.getSnapHeader().initialize( 28285ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein this, mAddressCache, this, getContactInfoSource(), 28385ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein mActivity.getAccountController().getVeiledAddressMatcher()); 284c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang 28590eccf969d691abb34f9c6f3d854091adb0c18d1Andrew Sapperstein final Resources resources = getResources(); 28690eccf969d691abb34f9c6f3d854091adb0c18d1Andrew Sapperstein mMaxAutoLoadMessages = resources.getInteger(R.integer.max_auto_load_messages); 287632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang 28890eccf969d691abb34f9c6f3d854091adb0c18d1Andrew Sapperstein mSideMarginPx = resources.getDimensionPixelOffset( 28902f9d18a54072db8d86c524f9c09e508092ddd7cAndy Huang R.dimen.conversation_message_content_margin_side); 29002f9d18a54072db8d86c524f9c09e508092ddd7cAndy Huang 2918ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein mUrlToMessageIdMap = new ArrayMap<String, String>(); 2928ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein final InlineAttachmentViewIntentBuilderCreator creator = 2938ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein InlineAttachmentViewIntentBuilderCreatorHolder. 2948ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein getInlineAttachmentViewIntentCreator(); 2958ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein mWebView.setOnCreateContextMenuListener(new WebViewContextMenu(getActivity(), 2968ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein creator.createInlineAttachmentViewIntentBuilder( 2978ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein mUrlToMessageIdMap, mAccount.getEmailAddress(), 2988ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein mConversation != null ? mConversation.id : -1))); 2990b7ed6fae6e36f2abc4ca916764177d1879731b4Andy Huang 300adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang // set this up here instead of onCreateView to ensure the latest Account is loaded 301adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang setupOverviewMode(); 302adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang 3039d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang // Defer the call to initLoader with a Handler. 3049d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang // We want to wait until we know which fragments are present and their final visibility 3059d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang // states before going off and doing work. This prevents extraneous loading from occurring 3069d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang // as the ViewPager shifts about before the initial position is set. 3079d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang // 3089d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang // e.g. click on item #10 3099d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang // ViewPager.setAdapter() actually first loads #0 and #1 under the assumption that #0 is 3109d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang // the initial primary item 3119d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang // Then CPC immediately sets the primary item to #10, which tears down #0/#1 and sets up 3129d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang // #9/#10/#11. 313376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein getHandler().post(new FragmentRunnable("showConversation", this) { 3149d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang @Override 3159d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang public void go() { 3169d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang showConversation(); 3179d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang } 3189d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang }); 319cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook 320606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein if (mConversation != null && mConversation.conversationBaseUri != null && 321b8361c9f8938b74977316319885998aae09aab77Paul Westbrook !Utils.isEmpty(mAccount.accoutCookieQueryUri)) { 322cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook // Set the cookie for this base url 323b8361c9f8938b74977316319885998aae09aab77Paul Westbrook new SetCookieTask(getContext(), mConversation.conversationBaseUri, 324b8361c9f8938b74977316319885998aae09aab77Paul Westbrook mAccount.accoutCookieQueryUri).execute(); 325cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook } 3269b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira } 3279b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira 3289b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira @Override 329e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang public void onCreate(Bundle savedState) { 330e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang super.onCreate(savedState); 331e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang 332b1d184d164303d83d342c60e4bffc732aa10ac2cAndrew Sapperstein mWebViewClient = createConversationWebViewClient(); 333376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein 334e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang if (savedState != null) { 335e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang mWebViewYPercent = savedState.getFloat(BUNDLE_KEY_WEBVIEW_Y_PERCENT); 336e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang } 3373af481c20edd84419d0d4a8476994ac008c0b97aAndrew Sapperstein 3382fd167d6131482da984768b5ee75cefa32ed3e32Andrew Sapperstein mBidiFormatter = BidiFormatter.getInstance(); 339e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang } 340e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang 341b1d184d164303d83d342c60e4bffc732aa10ac2cAndrew Sapperstein protected ConversationWebViewClient createConversationWebViewClient() { 342b1d184d164303d83d342c60e4bffc732aa10ac2cAndrew Sapperstein return new ConversationWebViewClient(mAccount); 343b1d184d164303d83d342c60e4bffc732aa10ac2cAndrew Sapperstein } 344b1d184d164303d83d342c60e4bffc732aa10ac2cAndrew Sapperstein 345e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang @Override 3469b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira public View onCreateView(LayoutInflater inflater, 3479b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira ViewGroup container, Bundle savedInstanceState) { 348839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang 349632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang View rootView = inflater.inflate(R.layout.conversation_view, container, false); 350f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang mConversationContainer = (ConversationContainer) rootView 351f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang .findViewById(R.id.conversation_container); 3528f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang mConversationContainer.setAccountController(this); 35347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 35485ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein final ViewGroup topmostOverlay = 35585ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein (ViewGroup) mConversationContainer.findViewById(R.id.conversation_topmost_overlay); 35685ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein inflateSnapHeader(topmostOverlay, inflater); 35785ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein mConversationContainer.setupSnapHeader(); 35885ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein 35985ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein setupNewMessageBar(); 36047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 361376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein mProgressController = new ConversationViewProgressController(this, getHandler()); 362376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein mProgressController.instantiateProgressIndicators(rootView); 3633bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp 3645ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang mWebView = (ConversationWebView) mConversationContainer.findViewById(R.id.webview); 365f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 366f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang mWebView.addJavascriptInterface(mJsBridge, "mail"); 3673bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp // On JB or newer, we use the 'webkitAnimationStart' DOM event to signal load complete 3683bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp // Below JB, try to speed up initial render by having the webview do supplemental draws to 3693bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp // custom a software canvas. 370b941fdb95f61cdde4e1775ac9034343951e8b075mindyp // TODO(mindyp): 371b941fdb95f61cdde4e1775ac9034343951e8b075mindyp //PAGE READINESS SIGNAL FOR JELLYBEAN AND NEWER 372b941fdb95f61cdde4e1775ac9034343951e8b075mindyp // Notify the app on 'webkitAnimationStart' of a simple dummy element with a simple no-op 373b941fdb95f61cdde4e1775ac9034343951e8b075mindyp // animation that immediately runs on page load. The app uses this as a signal that the 374b941fdb95f61cdde4e1775ac9034343951e8b075mindyp // content is loaded and ready to draw, since WebView delays firing this event until the 375b941fdb95f61cdde4e1775ac9034343951e8b075mindyp // layers are composited and everything is ready to draw. 376b941fdb95f61cdde4e1775ac9034343951e8b075mindyp // This signal does not seem to be reliable, so just use the old method for now. 377f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang final boolean isJBOrLater = Utils.isRunningJellybeanOrLater(); 378f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang final boolean isUserVisible = isUserVisible(); 379f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang mWebView.setUseSoftwareLayer(!isJBOrLater); 380f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang mEnableContentReadySignal = isJBOrLater && isUserVisible; 381f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang mWebView.onUserVisibilityChanged(isUserVisible); 38217a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huang mWebView.setWebViewClient(mWebViewClient); 383c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang final WebChromeClient wcc = new WebChromeClient() { 384f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang @Override 385f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang public boolean onConsoleMessage(ConsoleMessage consoleMessage) { 3868ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein if (consoleMessage.messageLevel() == ConsoleMessage.MessageLevel.ERROR) { 3878ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein LogUtils.wtf(LOG_TAG, "JS: %s (%s:%d) f=%s", consoleMessage.message(), 3888ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein consoleMessage.sourceId(), consoleMessage.lineNumber(), 3898ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein ConversationViewFragment.this); 3908ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein } else { 3918ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein LogUtils.i(LOG_TAG, "JS: %s (%s:%d) f=%s", consoleMessage.message(), 3928ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein consoleMessage.sourceId(), consoleMessage.lineNumber(), 3938ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein ConversationViewFragment.this); 3948ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein } 395f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang return true; 396f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang } 397c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang }; 398c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang mWebView.setWebChromeClient(wcc); 399f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 4003233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang final WebSettings settings = mWebView.getSettings(); 401f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 40256d83850db72592a16f4e6ee9e0d59b60ec0824aMark Wei mScrollIndicators = (ScrollIndicatorsView) rootView.findViewById(R.id.scroll_indicators); 40356d83850db72592a16f4e6ee9e0d59b60ec0824aMark Wei mScrollIndicators.setSourceView(mWebView); 40456d83850db72592a16f4e6ee9e0d59b60ec0824aMark Wei 405f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang settings.setJavaScriptEnabled(true); 406f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 407376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein ConversationViewUtils.setTextZoom(getResources(), settings); 408c319b551bebe40b9607acf0ee1d26a880fde9212Andy Huang 40951067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang mViewsCreated = true; 410e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang mWebViewLoadedData = false; 41151067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang 4129b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira return rootView; 4139b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira } 4149b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira 41585ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein protected void inflateSnapHeader(ViewGroup topmostOverlay, LayoutInflater inflater) { 41685ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein inflater.inflate(R.layout.conversation_topmost_overlay_items, topmostOverlay, true); 41785ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein } 41885ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein 41985ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein protected void setupNewMessageBar() { 42085ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein mNewMessageBar = (Button) mConversationContainer.findViewById( 42185ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein R.id.new_message_notification_bar); 42285ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein mNewMessageBar.setOnClickListener(new View.OnClickListener() { 42385ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein @Override 42485ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein public void onClick(View v) { 42585ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein onNewMessageBarClick(); 42685ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein } 42785ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein }); 42885ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein } 42985ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein 4309b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira @Override 431f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang public void onResume() { 432f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang super.onResume(); 433f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang if (mWebView != null) { 434f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang mWebView.onResume(); 435f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang } 436f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang } 437f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang 438f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang @Override 439f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang public void onPause() { 440f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang super.onPause(); 441f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang if (mWebView != null) { 442f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang mWebView.onPause(); 443f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang } 444f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang } 445f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang 446f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang @Override 4479b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira public void onDestroyView() { 4489b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira super.onDestroyView(); 44946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang mConversationContainer.setOverlayAdapter(null); 45046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang mAdapter = null; 4519d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang resetLoadWaiting(); // be sure to unregister any active load observer 45251067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang mViewsCreated = false; 4539b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira } 4549b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira 455e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang @Override 456e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang public void onSaveInstanceState(Bundle outState) { 457e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang super.onSaveInstanceState(outState); 458e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang 459e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang outState.putFloat(BUNDLE_KEY_WEBVIEW_Y_PERCENT, calculateScrollYPercent()); 460e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang } 461e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang 462e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang private float calculateScrollYPercent() { 4631b56a67396e40b65e15743bcf8ccce9a623b2f2cPaul Westbrook final float p; 4641b56a67396e40b65e15743bcf8ccce9a623b2f2cPaul Westbrook if (mWebView == null) { 4651b56a67396e40b65e15743bcf8ccce9a623b2f2cPaul Westbrook // onCreateView hasn't been called, return 0 as the user hasn't scrolled the view. 4661b56a67396e40b65e15743bcf8ccce9a623b2f2cPaul Westbrook return 0; 4671b56a67396e40b65e15743bcf8ccce9a623b2f2cPaul Westbrook } 4681b56a67396e40b65e15743bcf8ccce9a623b2f2cPaul Westbrook 4691b56a67396e40b65e15743bcf8ccce9a623b2f2cPaul Westbrook final int scrollY = mWebView.getScrollY(); 4701b56a67396e40b65e15743bcf8ccce9a623b2f2cPaul Westbrook final int viewH = mWebView.getHeight(); 4711b56a67396e40b65e15743bcf8ccce9a623b2f2cPaul Westbrook final int webH = (int) (mWebView.getContentHeight() * mWebView.getScale()); 472e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang 473e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang if (webH == 0 || webH <= viewH) { 474e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang p = 0; 475e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang } else if (scrollY + viewH >= webH) { 476e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang // The very bottom is a special case, it acts as a stronger anchor than the scroll top 477e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang // at that point. 478e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang p = 1.0f; 479e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang } else { 480e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang p = (float) scrollY / webH; 481e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang } 482e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang return p; 483e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang } 484e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang 4859d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang private void resetLoadWaiting() { 4869d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang if (mLoadWaitReason == LOAD_WAIT_FOR_INITIAL_CONVERSATION) { 4879d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang getListController().unregisterConversationLoadedObserver(mLoadedObserver); 4889d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang } 4899d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang mLoadWaitReason = LOAD_NOW; 4909d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang } 4919d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang 4925ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang @Override 493f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp protected void markUnread() { 494d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal super.markUnread(); 495839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang // Ignore unsafe calls made after a fragment is detached from an activity 496839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang final ControllableActivity activity = (ControllableActivity) getActivity(); 497839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang if (activity == null) { 498839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang LogUtils.w(LOG_TAG, "ignoring markUnread for conv=%s", mConversation.id); 499839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang return; 500839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang } 501839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang 50228e31e2417d775b343919cf6018f85871e40a294Andy Huang if (mViewState == null) { 50328e31e2417d775b343919cf6018f85871e40a294Andy Huang LogUtils.i(LOG_TAG, "ignoring markUnread for conv with no view state (%d)", 50428e31e2417d775b343919cf6018f85871e40a294Andy Huang mConversation.id); 50528e31e2417d775b343919cf6018f85871e40a294Andy Huang return; 50628e31e2417d775b343919cf6018f85871e40a294Andy Huang } 507839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang activity.getConversationUpdater().markConversationMessagesUnread(mConversation, 5084a878b606961825fb4fd412b460df75779f09ad2Vikram Aggarwal mViewState.getUnreadMessageUris(), mViewState.getConversationInfo()); 509839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang } 510839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang 511f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp @Override 512f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp public void onUserVisibleHintChanged() { 5139d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang final boolean userVisible = isUserVisible(); 51458192e5202d379ff62bf99995b08a0a7cf6646d1Scott Kennedy LogUtils.d(LOG_TAG, "ConversationViewFragment#onUserVisibleHintChanged(), userVisible = %b", 51558192e5202d379ff62bf99995b08a0a7cf6646d1Scott Kennedy userVisible); 5169d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang 5179d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang if (!userVisible) { 518376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein mProgressController.dismissLoadingStatus(); 5199d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang } else if (mViewsCreated) { 520e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang String loadTag = null; 521eeb4a35d7c596192666152d7115ac3c842b901c4Andy Huang final boolean isInitialLoading; 522eeb4a35d7c596192666152d7115ac3c842b901c4Andy Huang if (mActivity != null) { 523eeb4a35d7c596192666152d7115ac3c842b901c4Andy Huang isInitialLoading = mActivity.getConversationUpdater() 524e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang .isInitialConversationLoading(); 525eeb4a35d7c596192666152d7115ac3c842b901c4Andy Huang } else { 526eeb4a35d7c596192666152d7115ac3c842b901c4Andy Huang isInitialLoading = true; 527eeb4a35d7c596192666152d7115ac3c842b901c4Andy Huang } 528e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang 5299d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang if (getMessageCursor() != null) { 5309d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang LogUtils.d(LOG_TAG, "Fragment is now user-visible, onConversationSeen: %s", this); 531e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang if (!isInitialLoading) { 532e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang loadTag = "preloaded"; 533e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang } 534f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp onConversationSeen(); 5359d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang } else if (isLoadWaiting()) { 5369d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang LogUtils.d(LOG_TAG, "Fragment is now user-visible, showing conversation: %s", this); 537e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang if (!isInitialLoading) { 538e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang loadTag = "load_deferred"; 539e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang } 5409d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang handleDelayedConversationLoad(); 541632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang } 542e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang 543e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang if (loadTag != null) { 544e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang // pager swipes are visibility transitions to 'visible' except during initial 545e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang // pager load on A) enter conversation mode B) rotate C) 2-pane conv-mode list-tap 546e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang Analytics.getInstance().sendEvent("pager_swipe", loadTag, 547e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang getCurrentFolderTypeDesc(), 0); 548e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang } 549632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang } 550f8cf5468500677fceac3025727daba8155d319d6Andy Huang 55130bcfe7b69f9d8e0cad2ee62dae9d3571cd0d88bAndy Huang if (mWebView != null) { 55230bcfe7b69f9d8e0cad2ee62dae9d3571cd0d88bAndy Huang mWebView.onUserVisibilityChanged(userVisible); 55330bcfe7b69f9d8e0cad2ee62dae9d3571cd0d88bAndy Huang } 554f8cf5468500677fceac3025727daba8155d319d6Andy Huang } 555f8cf5468500677fceac3025727daba8155d319d6Andy Huang 5569d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang /** 5579d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang * Will either call initLoader now to begin loading, or set {@link #mLoadWaitReason} and do 5589d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang * nothing (in which case you should later call {@link #handleDelayedConversationLoad()}). 5599d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang */ 5609b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira private void showConversation() { 5619d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang final int reason; 5629d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang 5639d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang if (isUserVisible()) { 5649d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang LogUtils.i(LOG_TAG, 5659d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang "SHOWCONV: CVF is user-visible, immediately loading conversation (%s)", this); 5669d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang reason = LOAD_NOW; 567243c23618b066bcdcd0ab9e36d8c01f50db2cbd0Andy Huang timerMark("CVF.showConversation"); 5689d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang } else { 5699d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang final boolean disableOffscreenLoading = DISABLE_OFFSCREEN_LOADING 5700b8c080491f6884c2cfb7fcc473d970a9f4b97b9Alice Yang || Utils.isLowRamDevice(getContext()) 571606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein || (mConversation != null && (mConversation.isRemote 572606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein || mConversation.getNumMessages() > mMaxAutoLoadMessages)); 5739d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang 5749d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang // When not visible, we should not immediately load if either this conversation is 5759d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang // too heavyweight, or if the main/initial conversation is busy loading. 5769d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang if (disableOffscreenLoading) { 5779d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang reason = LOAD_WAIT_UNTIL_VISIBLE; 5789d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang LogUtils.i(LOG_TAG, "SHOWCONV: CVF waiting until visible to load (%s)", this); 5799d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang } else if (getListController().isInitialConversationLoading()) { 5809d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang reason = LOAD_WAIT_FOR_INITIAL_CONVERSATION; 5819d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang LogUtils.i(LOG_TAG, "SHOWCONV: CVF waiting for initial to finish (%s)", this); 5829d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang getListController().registerConversationLoadedObserver(mLoadedObserver); 5839d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang } else { 5849d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang LogUtils.i(LOG_TAG, 5859d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang "SHOWCONV: CVF is not visible, but no reason to wait. loading now. (%s)", 5869d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang this); 5879d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang reason = LOAD_NOW; 5889d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang } 589632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang } 5909d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang 5919d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang mLoadWaitReason = reason; 5929d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang if (mLoadWaitReason == LOAD_NOW) { 5939d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang startConversationLoad(); 5949d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang } 5959d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang } 5969d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang 5979d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang private void handleDelayedConversationLoad() { 5989d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang resetLoadWaiting(); 5999d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang startConversationLoad(); 6009d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang } 6019d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang 6029d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang private void startConversationLoad() { 6033bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp mWebView.setVisibility(View.VISIBLE); 604606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein loadContent(); 6053bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp // TODO(mindyp): don't show loading status for a previously rendered 6063bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp // conversation. Ielieve this is better done by making sure don't show loading status 6073bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp // until XX ms have passed without loading completed. 608376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein mProgressController.showLoadingStatus(isUserVisible()); 6098e915724b6e4374da9b70161ee0a55f0c763e563Mindy Pereira } 6108e915724b6e4374da9b70161ee0a55f0c763e563Mindy Pereira 611606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein /** 612606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein * Can be overridden in case a subclass needs to load something other than 613606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein * the messages of a conversation. 614606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein */ 615606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein protected void loadContent() { 616606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein getLoaderManager().initLoader(MESSAGE_LOADER, Bundle.EMPTY, getMessageLoaderCallbacks()); 617606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein } 618606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein 6197d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang private void revealConversation() { 620243c23618b066bcdcd0ab9e36d8c01f50db2cbd0Andy Huang timerMark("revealing conversation"); 621376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein mProgressController.dismissLoadingStatus(mOnProgressDismiss); 6227d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang } 6237d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang 6249d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang private boolean isLoadWaiting() { 6259d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang return mLoadWaitReason != LOAD_NOW; 6269d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang } 6279d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang 62851067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang private void renderConversation(MessageCursor messageCursor) { 6293bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp final String convHtml = renderMessageBodies(messageCursor, mEnableContentReadySignal); 630243c23618b066bcdcd0ab9e36d8c01f50db2cbd0Andy Huang timerMark("rendered conversation"); 631bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang 632bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang if (DEBUG_DUMP_CONVERSATION_HTML) { 633bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang java.io.FileWriter fw = null; 634bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang try { 635f1566b1e77e9d7ac66ebb47897b5c652e2b18943Andrew Sapperstein fw = new java.io.FileWriter(getSdCardFilePath()); 636bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang fw.write(convHtml); 637bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang } catch (java.io.IOException e) { 638bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang e.printStackTrace(); 639bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang } finally { 640bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang if (fw != null) { 641bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang try { 642bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang fw.close(); 643bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang } catch (java.io.IOException e) { 644bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang e.printStackTrace(); 645bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang } 646bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang } 647bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang } 648bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang } 649bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang 650e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang // save off existing scroll position before re-rendering 651e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang if (mWebViewLoadedData) { 652e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang mWebViewYPercent = calculateScrollYPercent(); 653e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang } 654e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang 655bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang mWebView.loadDataWithBaseURL(mBaseUri, convHtml, "text/html", "utf-8", null); 656e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang mWebViewLoadedData = true; 65763b3c6725d60386f564ab53e3ba6495f0c158d9bAndy Huang mWebViewLoadStartMs = SystemClock.uptimeMillis(); 65851067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang } 65951067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang 660f1566b1e77e9d7ac66ebb47897b5c652e2b18943Andrew Sapperstein protected String getSdCardFilePath() { 661f1566b1e77e9d7ac66ebb47897b5c652e2b18943Andrew Sapperstein return "/sdcard/conv" + mConversation.id + ".html"; 662f1566b1e77e9d7ac66ebb47897b5c652e2b18943Andrew Sapperstein } 663f1566b1e77e9d7ac66ebb47897b5c652e2b18943Andrew Sapperstein 6647bdc3750454efe59617b7df945eadd7e59bee954Andy Huang /** 6657bdc3750454efe59617b7df945eadd7e59bee954Andy Huang * Populate the adapter with overlay views (message headers, super-collapsed blocks, a 6667bdc3750454efe59617b7df945eadd7e59bee954Andy Huang * conversation header), and return an HTML document with spacer divs inserted for all overlays. 6677bdc3750454efe59617b7df945eadd7e59bee954Andy Huang * 6687bdc3750454efe59617b7df945eadd7e59bee954Andy Huang */ 6699f957f3463fd149d33c209409e2bba500b539177Andrew Sapperstein protected String renderMessageBodies(MessageCursor messageCursor, 6703bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp boolean enableContentReadySignal) { 671f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang int pos = -1; 672632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang 6731ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang LogUtils.d(LOG_TAG, "IN renderMessageBodies, fragment=%s", this); 6747bdc3750454efe59617b7df945eadd7e59bee954Andy Huang boolean allowNetworkImages = false; 6757bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 676c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang // TODO: re-use any existing adapter item state (expanded, details expanded, show pics) 67728b7aee7fa1f7d096d33fc823a88a64f7a3fa79dAndy Huang 6787bdc3750454efe59617b7df945eadd7e59bee954Andy Huang // Walk through the cursor and build up an overlay adapter as you go. 6797bdc3750454efe59617b7df945eadd7e59bee954Andy Huang // Each overlay has an entry in the adapter for easy scroll handling in the container. 6807bdc3750454efe59617b7df945eadd7e59bee954Andy Huang // Items are not necessarily 1:1 in cursor and adapter because of super-collapsed blocks. 6817bdc3750454efe59617b7df945eadd7e59bee954Andy Huang // When adding adapter items, also add their heights to help the container later determine 6827bdc3750454efe59617b7df945eadd7e59bee954Andy Huang // overlay dimensions. 6837bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 684db620fe42dcf1909468822b61238a23103d15376Andy Huang // When re-rendering, prevent ConversationContainer from laying out overlays until after 685db620fe42dcf1909468822b61238a23103d15376Andy Huang // the new spacers are positioned by WebView. 686db620fe42dcf1909468822b61238a23103d15376Andy Huang mConversationContainer.invalidateSpacerGeometry(); 687db620fe42dcf1909468822b61238a23103d15376Andy Huang 6887bdc3750454efe59617b7df945eadd7e59bee954Andy Huang mAdapter.clear(); 6897bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 69047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang // re-evaluate the message parts of the view state, since the messages may have changed 69147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang // since the previous render 69247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang final ConversationViewState prevState = mViewState; 69347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang mViewState = new ConversationViewState(prevState); 69447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 6955ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang // N.B. the units of height for spacers are actually dp and not px because WebView assumes 6962e9acfe527426c00b5df2ca66189262dc40c8575Andy Huang // a pixel is an mdpi pixel, unless you set device-dpi. 6975ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang 6987bdc3750454efe59617b7df945eadd7e59bee954Andy Huang // add a single conversation header item 6997bdc3750454efe59617b7df945eadd7e59bee954Andy Huang final int convHeaderPos = mAdapter.addConversationHeader(mConversation); 70023014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang final int convHeaderPx = measureOverlayHeight(convHeaderPos); 7013233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang 7024dc732387454eef3ee6d89f9fa393630eb6213f9Andy Huang mTemplates.startConversation(mWebView.getViewportWidth(), 7034dc732387454eef3ee6d89f9fa393630eb6213f9Andy Huang mWebView.screenPxToWebPx(mSideMarginPx), mWebView.screenPxToWebPx(convHeaderPx)); 7043233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang 70546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang int collapsedStart = -1; 706839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang ConversationMessage prevCollapsedMsg = null; 707e8221483ce5f013f02a4ef63d6682cc36313cda7Andrew Sapperstein 7088ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein final boolean alwaysShowImages = shouldAlwaysShowImages(); 709f323c046034b4658a80438575d8e9f01d92e57e6Alice Yang 710e8221483ce5f013f02a4ef63d6682cc36313cda7Andrew Sapperstein boolean prevSafeForImages = alwaysShowImages; 71146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 712cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein // Store the previous expanded state so that the border between 713cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein // the previous and current message can be properly initialized. 714cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein int previousExpandedState = ExpansionState.NONE; 715f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang while (messageCursor.moveToPosition(++pos)) { 716839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang final ConversationMessage msg = messageCursor.getMessage(); 71746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 718e8221483ce5f013f02a4ef63d6682cc36313cda7Andrew Sapperstein final boolean safeForImages = alwaysShowImages || 719202738427ede23dd5863583aa3df17e4abfbf5e2Scott Kennedy msg.alwaysShowImages || prevState.getShouldShowImages(msg); 7203233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang allowNetworkImages |= safeForImages; 7212405528bb15245f594277fe7b6efacd6a18ce69eAndy Huang 72208098ec4c894d9a15dfe800ad2397494e7e0a79aPaul Westbrook final Integer savedExpanded = prevState.getExpansionState(msg); 72308098ec4c894d9a15dfe800ad2397494e7e0a79aPaul Westbrook final int expandedState; 724839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang if (savedExpanded != null) { 7251ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang if (ExpansionState.isSuperCollapsed(savedExpanded) && messageCursor.isLast()) { 7261ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang // override saved state when this is now the new last message 7271ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang // this happens to the second-to-last message when you discard a draft 7281ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang expandedState = ExpansionState.EXPANDED; 7291ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang } else { 7301ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang expandedState = savedExpanded; 7311ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang } 732839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang } else { 733cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang // new messages that are not expanded default to being eligible for super-collapse 73408098ec4c894d9a15dfe800ad2397494e7e0a79aPaul Westbrook expandedState = (!msg.read || msg.starred || messageCursor.isLast()) ? 735cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang ExpansionState.EXPANDED : ExpansionState.SUPER_COLLAPSED; 736839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang } 737202738427ede23dd5863583aa3df17e4abfbf5e2Scott Kennedy mViewState.setShouldShowImages(msg, prevState.getShouldShowImages(msg)); 73808098ec4c894d9a15dfe800ad2397494e7e0a79aPaul Westbrook mViewState.setExpansionState(msg, expandedState); 739839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang 740839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang // save off "read" state from the cursor 741839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang // later, the view may not match the cursor (e.g. conversation marked read on open) 742423bea25992492efea7d414819729f9eae7ce72eAndy Huang // however, if a previous state indicated this message was unread, trust that instead 743423bea25992492efea7d414819729f9eae7ce72eAndy Huang // so "mark unread" marks all originally unread messages 744423bea25992492efea7d414819729f9eae7ce72eAndy Huang mViewState.setReadState(msg, msg.read && !prevState.isUnread(msg)); 745c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang 746cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang // We only want to consider this for inclusion in the super collapsed block if 747cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang // 1) The we don't have previous state about this message (The first time that the 748cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang // user opens a conversation) 749cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang // 2) The previously saved state for this message indicates that this message is 750cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang // in the super collapsed block. 751cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang if (ExpansionState.isSuperCollapsed(expandedState)) { 752cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang // contribute to a super-collapsed block that will be emitted just before the 753cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang // next expanded header 754cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang if (collapsedStart < 0) { 755cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang collapsedStart = pos; 75646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 757cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang prevCollapsedMsg = msg; 758cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang prevSafeForImages = safeForImages; 7597dc7fa00a6bea16b1142dc4e9ef8aabb73747fb7Andrew Sapperstein 7607dc7fa00a6bea16b1142dc4e9ef8aabb73747fb7Andrew Sapperstein // This line puts the from address in the address cache so that 7617dc7fa00a6bea16b1142dc4e9ef8aabb73747fb7Andrew Sapperstein // we get the sender image for it if it's in a super-collapsed block. 7627dc7fa00a6bea16b1142dc4e9ef8aabb73747fb7Andrew Sapperstein getAddress(msg.getFrom()); 763cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein previousExpandedState = expandedState; 764cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang continue; 76546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 7667bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 76746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang // resolve any deferred decisions on previous collapsed items 76846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang if (collapsedStart >= 0) { 76946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang if (pos - collapsedStart == 1) { 770cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein // Special-case for a single collapsed message: no need to super-collapse it. 771cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein // Since it is super-collapsed, there is no previous message to be 772cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein // collapsed and the border above it is the first border. 773cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein renderMessage(prevCollapsedMsg, false /* previousCollapsed */, 774cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein false /* expanded */, prevSafeForImages, true /* firstBorder */); 77546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } else { 77646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang renderSuperCollapsedBlock(collapsedStart, pos - 1); 77746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 77846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang prevCollapsedMsg = null; 77946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang collapsedStart = -1; 78046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 7812405528bb15245f594277fe7b6efacd6a18ce69eAndy Huang 782cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein renderMessage(msg, ExpansionState.isCollapsed(previousExpandedState), 783cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein ExpansionState.isExpanded(expandedState), safeForImages, 784e539880d39620d7e26b31c8b5b381a7e9d1a722eAndrew Sapperstein pos == 0 /* firstBorder */); 785cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein 786cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein previousExpandedState = expandedState; 787f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang } 7883233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang 7893233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang mWebView.getSettings().setBlockNetworkImage(!allowNetworkImages); 7903233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang 7912fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein final boolean applyTransforms = shouldApplyTransforms(); 79257f354c02dbf56ebc893a570564906e61c31e09eAndy Huang 793cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein renderBorder(true /* contiguous */, true /* expanded */, 794cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein false /* firstBorder */, true /* lastBorder */); 79514f937408fe2451a91b44d3cd7d141347e716775Andrew Sapperstein 796c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang // If the conversation has specified a base uri, use it here, otherwise use mBaseUri 79768141df2d855af6520a19ff9127f69463d49c3deAndrew Sapperstein return mTemplates.endConversation(mBaseUri, mConversation.getBaseUri(mBaseUri), 7982160d53e6ae0bfb797569d616e735e46c21522ffAndy Huang mWebView.getViewportWidth(), mWebView.getWidthInDp(mSideMarginPx), 7992160d53e6ae0bfb797569d616e735e46c21522ffAndy Huang enableContentReadySignal, isOverviewMode(mAccount), applyTransforms, 8002160d53e6ae0bfb797569d616e735e46c21522ffAndy Huang applyTransforms); 801f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang } 802f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 80346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang private void renderSuperCollapsedBlock(int start, int end) { 804cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein renderBorder(true /* contiguous */, true /* expanded */, 805cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein true /* firstBorder */, false /* lastBorder */); 80646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang final int blockPos = mAdapter.addSuperCollapsedBlock(start, end); 80723014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang final int blockPx = measureOverlayHeight(blockPos); 80823014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang mTemplates.appendSuperCollapsedHtml(start, mWebView.screenPxToWebPx(blockPx)); 80946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 81046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 811cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein protected void renderBorder( 812cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein boolean contiguous, boolean expanded, boolean firstBorder, boolean lastBorder) { 813cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein final int blockPos = mAdapter.addBorder(contiguous, expanded, firstBorder, lastBorder); 81414f937408fe2451a91b44d3cd7d141347e716775Andrew Sapperstein final int blockPx = measureOverlayHeight(blockPos); 81514f937408fe2451a91b44d3cd7d141347e716775Andrew Sapperstein mTemplates.appendBorder(mWebView.screenPxToWebPx(blockPx)); 81614f937408fe2451a91b44d3cd7d141347e716775Andrew Sapperstein } 81714f937408fe2451a91b44d3cd7d141347e716775Andrew Sapperstein 818cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein private void renderMessage(ConversationMessage msg, boolean previousCollapsed, 819cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein boolean expanded, boolean safeForImages, boolean firstBorder) { 820cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein renderMessage(msg, previousCollapsed, expanded, safeForImages, 821cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein true /* renderBorder */, firstBorder); 8221f082231c4a51eb3be37df6d2a0024634dfe4a9bAndrew Sapperstein } 8231f082231c4a51eb3be37df6d2a0024634dfe4a9bAndrew Sapperstein 824cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein private void renderMessage(ConversationMessage msg, boolean previousCollapsed, 825cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein boolean expanded, boolean safeForImages, boolean renderBorder, boolean firstBorder) { 8261f082231c4a51eb3be37df6d2a0024634dfe4a9bAndrew Sapperstein if (renderBorder) { 827cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein // The border should be collapsed only if both the current 828cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein // and previous messages are collapsed. 829cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein renderBorder(true /* contiguous */, !previousCollapsed || expanded, 830cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein firstBorder, false /* lastBorder */); 8311f082231c4a51eb3be37df6d2a0024634dfe4a9bAndrew Sapperstein } 83214f937408fe2451a91b44d3cd7d141347e716775Andrew Sapperstein 833202738427ede23dd5863583aa3df17e4abfbf5e2Scott Kennedy final int headerPos = mAdapter.addMessageHeader(msg, expanded, 834202738427ede23dd5863583aa3df17e4abfbf5e2Scott Kennedy mViewState.getShouldShowImages(msg)); 83546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang final MessageHeaderItem headerItem = (MessageHeaderItem) mAdapter.getItem(headerPos); 83646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 83746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang final int footerPos = mAdapter.addMessageFooter(headerItem); 83846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 83946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang // Measure item header and footer heights to allocate spacers in HTML 84046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang // But since the views themselves don't exist yet, render each item temporarily into 84146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang // a host view for measurement. 84223014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang final int headerPx = measureOverlayHeight(headerPos); 84323014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang final int footerPx = measureOverlayHeight(footerPos); 84446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 845256b35c0a8287f48c28e0d1ba3fae65790063295Andy Huang mTemplates.appendMessageHtml(msg, expanded, safeForImages, 84623014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang mWebView.screenPxToWebPx(headerPx), mWebView.screenPxToWebPx(footerPx)); 847243c23618b066bcdcd0ab9e36d8c01f50db2cbd0Andy Huang timerMark("rendered message"); 84846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 84946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 85046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang private String renderCollapsedHeaders(MessageCursor cursor, 85146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang SuperCollapsedBlockItem blockToReplace) { 85246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang final List<ConversationOverlayItem> replacements = Lists.newArrayList(); 85346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 85446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang mTemplates.reset(); 85546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 856f323c046034b4658a80438575d8e9f01d92e57e6Alice Yang final boolean alwaysShowImages = (mAccount != null) && 857f323c046034b4658a80438575d8e9f01d92e57e6Alice Yang (mAccount.settings.showImages == Settings.ShowImages.ALWAYS); 858e8221483ce5f013f02a4ef63d6682cc36313cda7Andrew Sapperstein 8592b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei // In devices with non-integral density multiplier, screen pixels translate to non-integral 8602b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei // web pixels. Keep track of the error that occurs when we cast all heights to int 8612b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei float error = 0f; 86214f937408fe2451a91b44d3cd7d141347e716775Andrew Sapperstein boolean first = true; 86346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang for (int i = blockToReplace.getStart(), end = blockToReplace.getEnd(); i <= end; i++) { 86446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang cursor.moveToPosition(i); 865839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang final ConversationMessage msg = cursor.getMessage(); 86614f937408fe2451a91b44d3cd7d141347e716775Andrew Sapperstein 86714f937408fe2451a91b44d3cd7d141347e716775Andrew Sapperstein final int borderPx; 86814f937408fe2451a91b44d3cd7d141347e716775Andrew Sapperstein if (first) { 86914f937408fe2451a91b44d3cd7d141347e716775Andrew Sapperstein borderPx = 0; 87014f937408fe2451a91b44d3cd7d141347e716775Andrew Sapperstein first = false; 87114f937408fe2451a91b44d3cd7d141347e716775Andrew Sapperstein } else { 872cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein // When replacing the super-collapsed block, 873cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein // the border is always collapsed between messages. 874cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein final BorderItem border = mAdapter.newBorderItem( 875cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein true /* contiguous */, false /* expanded */); 87614f937408fe2451a91b44d3cd7d141347e716775Andrew Sapperstein borderPx = measureOverlayHeight(border); 87714f937408fe2451a91b44d3cd7d141347e716775Andrew Sapperstein replacements.add(border); 87814f937408fe2451a91b44d3cd7d141347e716775Andrew Sapperstein mTemplates.appendBorder(mWebView.screenPxToWebPx(borderPx)); 87914f937408fe2451a91b44d3cd7d141347e716775Andrew Sapperstein } 88014f937408fe2451a91b44d3cd7d141347e716775Andrew Sapperstein 8814ddda2f0a4ee5381a90779a6939b05b064ce5d11Andrew Sapperstein final MessageHeaderItem header = ConversationViewAdapter.newMessageHeaderItem( 88214f937408fe2451a91b44d3cd7d141347e716775Andrew Sapperstein mAdapter, mAdapter.getDateBuilder(), msg, false /* expanded */, 883e8221483ce5f013f02a4ef63d6682cc36313cda7Andrew Sapperstein alwaysShowImages || mViewState.getShouldShowImages(msg)); 88446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang final MessageFooterItem footer = mAdapter.newMessageFooterItem(header); 88546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 88623014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang final int headerPx = measureOverlayHeight(header); 88723014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang final int footerPx = measureOverlayHeight(footer); 8882b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei error += mWebView.screenPxToWebPxError(headerPx) 88914f937408fe2451a91b44d3cd7d141347e716775Andrew Sapperstein + mWebView.screenPxToWebPxError(footerPx) 89014f937408fe2451a91b44d3cd7d141347e716775Andrew Sapperstein + mWebView.screenPxToWebPxError(borderPx); 8912b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei 8922b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei // When the error becomes greater than 1 pixel, make the next header 1 pixel taller 8932b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei int correction = 0; 8942b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei if (error >= 1) { 8952b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei correction = 1; 8962b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei error -= 1; 8972b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei } 89846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 899e8221483ce5f013f02a4ef63d6682cc36313cda7Andrew Sapperstein mTemplates.appendMessageHtml(msg, false /* expanded */, 900e8221483ce5f013f02a4ef63d6682cc36313cda7Andrew Sapperstein alwaysShowImages || msg.alwaysShowImages, 9012b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei mWebView.screenPxToWebPx(headerPx) + correction, 9022b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei mWebView.screenPxToWebPx(footerPx)); 90346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang replacements.add(header); 90446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang replacements.add(footer); 905839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang 90608098ec4c894d9a15dfe800ad2397494e7e0a79aPaul Westbrook mViewState.setExpansionState(msg, ExpansionState.COLLAPSED); 90746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 90846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 90946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang mAdapter.replaceSuperCollapsedBlock(blockToReplace, replacements); 91006c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang mAdapter.notifyDataSetChanged(); 91146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 91246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang return mTemplates.emit(); 91346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 91446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 9159f957f3463fd149d33c209409e2bba500b539177Andrew Sapperstein protected int measureOverlayHeight(int position) { 91646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang return measureOverlayHeight(mAdapter.getItem(position)); 91746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 91846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 9197bdc3750454efe59617b7df945eadd7e59bee954Andy Huang /** 920b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang * Measure the height of an adapter view by rendering an adapter item into a temporary 92146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang * host view, and asking the view to immediately measure itself. This method will reuse 9227bdc3750454efe59617b7df945eadd7e59bee954Andy Huang * a previous adapter view from {@link ConversationContainer}'s scrap views if one was generated 9237bdc3750454efe59617b7df945eadd7e59bee954Andy Huang * earlier. 9247bdc3750454efe59617b7df945eadd7e59bee954Andy Huang * <p> 92546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang * After measuring the height, this method also saves the height in the 92646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang * {@link ConversationOverlayItem} for later use in overlay positioning. 9277bdc3750454efe59617b7df945eadd7e59bee954Andy Huang * 92846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang * @param convItem adapter item with data to render and measure 92923014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang * @return height of the rendered view in screen px 9307bdc3750454efe59617b7df945eadd7e59bee954Andy Huang */ 93146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang private int measureOverlayHeight(ConversationOverlayItem convItem) { 9327bdc3750454efe59617b7df945eadd7e59bee954Andy Huang final int type = convItem.getType(); 9337bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 9347bdc3750454efe59617b7df945eadd7e59bee954Andy Huang final View convertView = mConversationContainer.getScrapView(type); 935b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang final View hostView = mAdapter.getView(convItem, convertView, mConversationContainer, 936b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang true /* measureOnly */); 9377bdc3750454efe59617b7df945eadd7e59bee954Andy Huang if (convertView == null) { 9387bdc3750454efe59617b7df945eadd7e59bee954Andy Huang mConversationContainer.addScrapView(type, hostView); 9397bdc3750454efe59617b7df945eadd7e59bee954Andy Huang } 9407bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 9419875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang final int heightPx = mConversationContainer.measureOverlay(hostView); 9427bdc3750454efe59617b7df945eadd7e59bee954Andy Huang convItem.setHeight(heightPx); 9439875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang convItem.markMeasurementValid(); 9447bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 94523014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang return heightPx; 9467bdc3750454efe59617b7df945eadd7e59bee954Andy Huang } 9477bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 9485ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang @Override 9495ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang public void onConversationViewHeaderHeightChange(int newHeight) { 950ab2d998506c83e82ddae25b6ba1419414e1e8122Mark Wei final int h = mWebView.screenPxToWebPx(newHeight); 951ab2d998506c83e82ddae25b6ba1419414e1e8122Mark Wei 952ab2d998506c83e82ddae25b6ba1419414e1e8122Mark Wei mWebView.loadUrl(String.format("javascript:setConversationHeaderSpacerHeight(%s);", h)); 9535ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang } 9545ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang 9553233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang // END conversation header callbacks 9563233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang 9573233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang // START message header callbacks 9583233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang @Override 959c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang public void setMessageSpacerHeight(MessageHeaderItem item, int newSpacerHeightPx) { 960c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang mConversationContainer.invalidateSpacerGeometry(); 961c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang 962c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang // update message HTML spacer height 96323014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang final int h = mWebView.screenPxToWebPx(newSpacerHeightPx); 96423014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang LogUtils.i(LAYOUT_TAG, "setting HTML spacer h=%dwebPx (%dscreenPx)", h, 96523014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang newSpacerHeightPx); 9665349ce14695dc98615108eb26f96806921b0abbaVikram Aggarwal mWebView.loadUrl(String.format("javascript:setMessageHeaderSpacerHeight('%s', %s);", 967014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang mTemplates.getMessageDomId(item.getMessage()), h)); 9683233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang } 9693233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang 9703233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang @Override 971cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein public void setMessageExpanded(MessageHeaderItem item, int newSpacerHeightPx, 972cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein int topBorderHeight, int bottomBorderHeight) { 973c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang mConversationContainer.invalidateSpacerGeometry(); 974c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang 975c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang // show/hide the HTML message body and update the spacer height 97623014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang final int h = mWebView.screenPxToWebPx(newSpacerHeightPx); 977cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein final int topHeight = mWebView.screenPxToWebPx(topBorderHeight); 978cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein final int bottomHeight = mWebView.screenPxToWebPx(bottomBorderHeight); 97923014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang LogUtils.i(LAYOUT_TAG, "setting HTML spacer expanded=%s h=%dwebPx (%dscreenPx)", 98023014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang item.isExpanded(), h, newSpacerHeightPx); 981cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein mWebView.loadUrl(String.format("javascript:setMessageBodyVisible('%s', %s, %s, %s, %s);", 982cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein mTemplates.getMessageDomId(item.getMessage()), item.isExpanded(), 983cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein h, topHeight, bottomHeight)); 984839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang 985014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang mViewState.setExpansionState(item.getMessage(), 98608098ec4c894d9a15dfe800ad2397494e7e0a79aPaul Westbrook item.isExpanded() ? ExpansionState.EXPANDED : ExpansionState.COLLAPSED); 9873233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang } 9883233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang 9893233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang @Override 990eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy public void showExternalResources(final Message msg) { 991202738427ede23dd5863583aa3df17e4abfbf5e2Scott Kennedy mViewState.setShouldShowImages(msg, true); 9923233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang mWebView.getSettings().setBlockNetworkImage(false); 993eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy mWebView.loadUrl("javascript:unblockImages(['" + mTemplates.getMessageDomId(msg) + "']);"); 994eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy } 995eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy 996eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy @Override 997eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy public void showExternalResources(final String senderRawAddress) { 998eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy mWebView.getSettings().setBlockNetworkImage(false); 999eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy 1000eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy final Address sender = getAddress(senderRawAddress); 1001eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy final MessageCursor cursor = getMessageCursor(); 1002eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy 1003eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy final List<String> messageDomIds = new ArrayList<String>(); 1004eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy 1005eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy int pos = -1; 1006eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy while (cursor.moveToPosition(++pos)) { 1007eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy final ConversationMessage message = cursor.getMessage(); 1008eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy if (sender.equals(getAddress(message.getFrom()))) { 1009eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy message.alwaysShowImages = true; 1010eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy 1011eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy mViewState.setShouldShowImages(message, true); 1012eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy messageDomIds.add(mTemplates.getMessageDomId(message)); 1013eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy } 1014eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy } 1015eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy 1016eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy final String url = String.format( 1017eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy "javascript:unblockImages(['%s']);", TextUtils.join("','", messageDomIds)); 1018eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy mWebView.loadUrl(url); 10193233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang } 10201ebc2db723ed29093d724eb5da906a496ee57224Alice Yang 10211ebc2db723ed29093d724eb5da906a496ee57224Alice Yang @Override 102275b52a50a7b8382d9046d48ea8cda97b5471cbc8Andy Huang public boolean supportsMessageTransforms() { 102375b52a50a7b8382d9046d48ea8cda97b5471cbc8Andy Huang return true; 102475b52a50a7b8382d9046d48ea8cda97b5471cbc8Andy Huang } 102575b52a50a7b8382d9046d48ea8cda97b5471cbc8Andy Huang 102675b52a50a7b8382d9046d48ea8cda97b5471cbc8Andy Huang @Override 10271ebc2db723ed29093d724eb5da906a496ee57224Alice Yang public String getMessageTransforms(final Message msg) { 10281ebc2db723ed29093d724eb5da906a496ee57224Alice Yang final String domId = mTemplates.getMessageDomId(msg); 10291ebc2db723ed29093d724eb5da906a496ee57224Alice Yang return (domId == null) ? null : mMessageTransforms.get(domId); 10301ebc2db723ed29093d724eb5da906a496ee57224Alice Yang } 10311ebc2db723ed29093d724eb5da906a496ee57224Alice Yang 10323233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang // END message header callbacks 10335ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang 103446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang @Override 10352fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein public void showUntransformedConversation() { 10362fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein super.showUntransformedConversation(); 10372fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein renderConversation(getMessageCursor()); 10382fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein } 10392fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein 10402fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein @Override 104146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang public void onSuperCollapsedClick(SuperCollapsedBlockItem item) { 1042f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp MessageCursor cursor = getMessageCursor(); 1043f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp if (cursor == null || !mViewsCreated) { 104446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang return; 104546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 104646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 1047f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp mTempBodiesHtml = renderCollapsedHeaders(cursor, item); 104846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang mWebView.loadUrl("javascript:replaceSuperCollapsedBlock(" + item.getStart() + ")"); 104946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 105046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 105147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang private void showNewMessageNotification(NewMessagesInfo info) { 1052821fa87279d590e682effbdb652c8a92e805eec8Andrew Sapperstein mNewMessageBar.setText(info.getNotificationText()); 105347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang mNewMessageBar.setVisibility(View.VISIBLE); 105447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang } 105547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 105647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang private void onNewMessageBarClick() { 105747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang mNewMessageBar.setVisibility(View.GONE); 105847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 1059f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp renderConversation(getMessageCursor()); // mCursor is already up-to-date 1060f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp // per onLoadFinished() 10615fbda023f1b0570e192e03a33834244a05edf200Andy Huang } 10625fbda023f1b0570e192e03a33834244a05edf200Andy Huang 1063adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang private static OverlayPosition[] parsePositions(final String[] topArray, 1064adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang final String[] bottomArray) { 1065adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang final int len = topArray.length; 1066adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang final OverlayPosition[] positions = new OverlayPosition[len]; 1067b5078b287b1cec38817e342ff054ea901d199329Andy Huang for (int i = 0; i < len; i++) { 1068adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang positions[i] = new OverlayPosition( 1069adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang Integer.parseInt(topArray[i]), Integer.parseInt(bottomArray[i])); 1070b5078b287b1cec38817e342ff054ea901d199329Andy Huang } 1071adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang return positions; 1072b5078b287b1cec38817e342ff054ea901d199329Andy Huang } 1073b5078b287b1cec38817e342ff054ea901d199329Andy Huang 10749f957f3463fd149d33c209409e2bba500b539177Andrew Sapperstein protected Address getAddress(String rawFrom) { 10750dfae6942a834f32f031c466017539d43d06e466Paul Westbrook return Utils.getAddress(mAddressCache, rawFrom); 10761617481de7c1f9b8dfcd25ba9828e933cf9d5490Andy Huang } 10771617481de7c1f9b8dfcd25ba9828e933cf9d5490Andy Huang 10789d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang private void ensureContentSizeChangeListener() { 10799d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang if (mWebViewSizeChangeListener == null) { 1080c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang mWebViewSizeChangeListener = new ContentSizeChangeListener() { 10819d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang @Override 10829d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang public void onHeightChange(int h) { 10839d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang // When WebKit says the DOM height has changed, re-measure 10849d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang // bodies and re-position their headers. 10859d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang // This is separate from the typical JavaScript DOM change 10869d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang // listeners because cases like NARROW_COLUMNS text reflow do not trigger DOM 10879d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang // events. 10889d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang mWebView.loadUrl("javascript:measurePositions();"); 10899d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang } 10909d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang }; 10919d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang } 10929d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang mWebView.setContentSizeChangeListener(mWebViewSizeChangeListener); 10939d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang } 10949d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang 10959f957f3463fd149d33c209409e2bba500b539177Andrew Sapperstein public static boolean isOverviewMode(Account acct) { 1096ccf6780d9c80070beca4ade6e4084c3fb719af51Andy Huang return acct.settings.isOverviewMode(); 1097adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang } 1098adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang 1099adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang private void setupOverviewMode() { 110002f9d18a54072db8d86c524f9c09e508092ddd7cAndy Huang // for now, overview mode means use the built-in WebView zoom and disable custom scale 110102f9d18a54072db8d86c524f9c09e508092ddd7cAndy Huang // gesture handling 1102adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang final boolean overviewMode = isOverviewMode(mAccount); 1103adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang final WebSettings settings = mWebView.getSettings(); 11044dc732387454eef3ee6d89f9fa393630eb6213f9Andy Huang final WebSettings.LayoutAlgorithm layout; 110506def56be1f03acd2e5706ecf207ab147ce6f5c4Andy Huang settings.setUseWideViewPort(overviewMode); 110657f354c02dbf56ebc893a570564906e61c31e09eAndy Huang settings.setSupportZoom(overviewMode); 110757f354c02dbf56ebc893a570564906e61c31e09eAndy Huang settings.setBuiltInZoomControls(overviewMode); 11084dc732387454eef3ee6d89f9fa393630eb6213f9Andy Huang settings.setLoadWithOverviewMode(overviewMode); 110957f354c02dbf56ebc893a570564906e61c31e09eAndy Huang if (overviewMode) { 111057f354c02dbf56ebc893a570564906e61c31e09eAndy Huang settings.setDisplayZoomControls(false); 11114dc732387454eef3ee6d89f9fa393630eb6213f9Andy Huang layout = WebSettings.LayoutAlgorithm.NORMAL; 11124dc732387454eef3ee6d89f9fa393630eb6213f9Andy Huang } else { 11134dc732387454eef3ee6d89f9fa393630eb6213f9Andy Huang layout = WebSettings.LayoutAlgorithm.NARROW_COLUMNS; 1114adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang } 11154dc732387454eef3ee6d89f9fa393630eb6213f9Andy Huang settings.setLayoutAlgorithm(layout); 1116adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang } 1117adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang 1118b1d184d164303d83d342c60e4bffc732aa10ac2cAndrew Sapperstein public class ConversationWebViewClient extends AbstractConversationWebViewClient { 11194ddda2f0a4ee5381a90779a6939b05b064ce5d11Andrew Sapperstein public ConversationWebViewClient(Account account) { 11204ddda2f0a4ee5381a90779a6939b05b064ce5d11Andrew Sapperstein super(account); 1121376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein } 1122376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein 112317a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huang @Override 112417a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huang public void onPageFinished(WebView view, String url) { 11259a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang // Ignore unsafe calls made after a fragment is detached from an activity. 11269a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang // This method needs to, for example, get at the loader manager, which needs 11279a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang // the fragment to be added. 11289a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang if (!isAdded() || !mViewsCreated) { 1129006e13c4943d83ce3277d502f531236b903e94fdPaul Westbrook LogUtils.d(LOG_TAG, "ignoring CVF.onPageFinished, url=%s fragment=%s", url, 1130b95da853ffbff343c8ca11470de2f0047e0807a6Andy Huang ConversationViewFragment.this); 1131b95da853ffbff343c8ca11470de2f0047e0807a6Andy Huang return; 1132b95da853ffbff343c8ca11470de2f0047e0807a6Andy Huang } 1133b95da853ffbff343c8ca11470de2f0047e0807a6Andy Huang 1134006e13c4943d83ce3277d502f531236b903e94fdPaul Westbrook LogUtils.d(LOG_TAG, "IN CVF.onPageFinished, url=%s fragment=%s wv=%s t=%sms", url, 113530bcfe7b69f9d8e0cad2ee62dae9d3571cd0d88bAndy Huang ConversationViewFragment.this, view, 113663b3c6725d60386f564ab53e3ba6495f0c158d9bAndy Huang (SystemClock.uptimeMillis() - mWebViewLoadStartMs)); 1137632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang 11389d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang ensureContentSizeChangeListener(); 11399d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang 11403bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp if (!mEnableContentReadySignal) { 11417d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang revealConversation(); 11423bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp } 11439d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang 11449a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang final Set<String> emailAddresses = Sets.newHashSet(); 1145543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang final List<Address> cacheCopy; 1146543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang synchronized (mAddressCache) { 1147543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang cacheCopy = ImmutableList.copyOf(mAddressCache.values()); 1148543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang } 1149543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang for (Address addr : cacheCopy) { 11509a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang emailAddresses.add(addr.getAddress()); 1151b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang } 11524ddda2f0a4ee5381a90779a6939b05b064ce5d11Andrew Sapperstein final ContactLoaderCallbacks callbacks = getContactInfoSource(); 11534ddda2f0a4ee5381a90779a6939b05b064ce5d11Andrew Sapperstein callbacks.setSenders(emailAddresses); 11549a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang getLoaderManager().restartLoader(CONTACT_LOADER, Bundle.EMPTY, callbacks); 115517a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huang } 115617a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huang 1157af5d4e0e2a5ce3300fffa0c484431d83adb89ee8Andy Huang @Override 1158af5d4e0e2a5ce3300fffa0c484431d83adb89ee8Andy Huang public boolean shouldOverrideUrlLoading(WebView view, String url) { 1159542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook return mViewsCreated && super.shouldOverrideUrlLoading(view, url); 1160af5d4e0e2a5ce3300fffa0c484431d83adb89ee8Andy Huang } 116117a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huang } 116217a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huang 1163f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang /** 1164f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang * NOTE: all public methods must be listed in the proguard flags so that they can be accessed 1165f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang * via reflection and not stripped. 1166f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang * 1167f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang */ 1168f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang private class MailJsBridge { 1169974c966c14000d42f089e2021b098d5cf61f9245Mindy Pereira @JavascriptInterface 1170adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang public void onWebContentGeometryChange(final String[] overlayTopStrs, 1171adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang final String[] overlayBottomStrs) { 11728ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein try { 11738ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein getHandler().post(new FragmentRunnable("onWebContentGeometryChange", 11748ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein ConversationViewFragment.this) { 11758ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein @Override 11768ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein public void go() { 117746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang if (!mViewsCreated) { 11781b3cc47f54072105c161d6ed557550e0e149b8bbmindyp LogUtils.d(LOG_TAG, "ignoring webContentGeometryChange because views" 11791b3cc47f54072105c161d6ed557550e0e149b8bbmindyp + " are gone, %s", ConversationViewFragment.this); 118046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang return; 118146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 1182adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang mConversationContainer.onGeometryChange( 1183adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang parsePositions(overlayTopStrs, overlayBottomStrs)); 11841b3cc47f54072105c161d6ed557550e0e149b8bbmindyp if (mDiff != 0) { 11851b3cc47f54072105c161d6ed557550e0e149b8bbmindyp // SCROLL! 11861b3cc47f54072105c161d6ed557550e0e149b8bbmindyp int scale = (int) (mWebView.getScale() / mWebView.getInitialScale()); 11871b3cc47f54072105c161d6ed557550e0e149b8bbmindyp if (scale > 1) { 11881b3cc47f54072105c161d6ed557550e0e149b8bbmindyp mWebView.scrollBy(0, (mDiff * (scale - 1))); 11891b3cc47f54072105c161d6ed557550e0e149b8bbmindyp } 11901b3cc47f54072105c161d6ed557550e0e149b8bbmindyp mDiff = 0; 11911b3cc47f54072105c161d6ed557550e0e149b8bbmindyp } 119251067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang } 11938ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein }); 11948ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein } catch (Throwable t) { 11958ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein LogUtils.e(LOG_TAG, t, "Error in MailJsBridge.onWebContentGeometryChange"); 11968ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein } 119746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 119851067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang 1199974c966c14000d42f089e2021b098d5cf61f9245Mindy Pereira @JavascriptInterface 120046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang public String getTempMessageBodies() { 120146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang try { 120246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang if (!mViewsCreated) { 120346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang return ""; 1204f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang } 120546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 120646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang final String s = mTempBodiesHtml; 120746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang mTempBodiesHtml = null; 120846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang return s; 120946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } catch (Throwable t) { 121046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang LogUtils.e(LOG_TAG, t, "Error in MailJsBridge.getTempMessageBodies"); 121146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang return ""; 121246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 1213f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang } 1214f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 1215014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang @JavascriptInterface 1216014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang public String getMessageBody(String domId) { 1217014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang try { 1218014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang final MessageCursor cursor = getMessageCursor(); 1219014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang if (!mViewsCreated || cursor == null) { 1220014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang return ""; 1221014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang } 1222014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang 1223014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang int pos = -1; 1224014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang while (cursor.moveToPosition(++pos)) { 1225014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang final ConversationMessage msg = cursor.getMessage(); 1226014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang if (TextUtils.equals(domId, mTemplates.getMessageDomId(msg))) { 1227986776bbd046c9569a4abb67501819bee61e7194Andy Huang return HtmlConversationTemplates.wrapMessageBody(msg.getBodyAsHtml()); 1228014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang } 1229014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang } 1230014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang 1231014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang return ""; 1232014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang 1233014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang } catch (Throwable t) { 1234014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang LogUtils.e(LOG_TAG, t, "Error in MailJsBridge.getMessageBody"); 1235014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang return ""; 1236014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang } 1237014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang } 1238014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang 1239974c966c14000d42f089e2021b098d5cf61f9245Mindy Pereira @JavascriptInterface 1240543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang public String getMessageSender(String domId) { 1241543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang try { 1242543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang final MessageCursor cursor = getMessageCursor(); 1243543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang if (!mViewsCreated || cursor == null) { 1244543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang return ""; 1245543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang } 1246543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang 1247543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang int pos = -1; 1248543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang while (cursor.moveToPosition(++pos)) { 1249543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang final ConversationMessage msg = cursor.getMessage(); 1250543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang if (TextUtils.equals(domId, mTemplates.getMessageDomId(msg))) { 1251543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang return getAddress(msg.getFrom()).getAddress(); 1252543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang } 1253543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang } 1254543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang 1255543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang return ""; 1256543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang 1257543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang } catch (Throwable t) { 1258543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang LogUtils.e(LOG_TAG, t, "Error in MailJsBridge.getMessageSender"); 1259543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang return ""; 1260543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang } 1261543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang } 1262543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang 1263543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang @JavascriptInterface 12643bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp public void onContentReady() { 12658ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein try { 12668ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein getHandler().post(new FragmentRunnable("onContentReady", 12678ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein ConversationViewFragment.this) { 12688ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein @Override 12698ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein public void go() { 12708ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein try { 12718ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein if (mWebViewLoadStartMs != 0) { 12728ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein LogUtils.i(LOG_TAG, "IN CVF.onContentReady, f=%s vis=%s t=%sms", 12738ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein ConversationViewFragment.this, 12748ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein isUserVisible(), 12758ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein (SystemClock.uptimeMillis() - mWebViewLoadStartMs)); 12768ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein } 12778ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein revealConversation(); 12788ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein } catch (Throwable t) { 12798ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein LogUtils.e(LOG_TAG, t, "Error in MailJsBridge.onContentReady"); 12808ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein // Still try to show the conversation. 12818ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein revealConversation(); 128263b3c6725d60386f564ab53e3ba6495f0c158d9bAndy Huang } 12833bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp } 12848ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein }); 12858ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein } catch (Throwable t) { 12868ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein LogUtils.e(LOG_TAG, t, "Error in MailJsBridge.onContentReady"); 12878ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein } 12883bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp } 1289e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang 1290e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang @JavascriptInterface 1291e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang public float getScrollYPercent() { 1292e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang try { 1293e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang return mWebViewYPercent; 1294e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang } catch (Throwable t) { 1295e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang LogUtils.e(LOG_TAG, t, "Error in MailJsBridge.getScrollYPercent"); 1296e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang return 0f; 1297e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang } 1298e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang } 129905c70c88e6b421bedce134799a0ea928dbf44cd5Andy Huang 130005c70c88e6b421bedce134799a0ea928dbf44cd5Andy Huang @JavascriptInterface 130105c70c88e6b421bedce134799a0ea928dbf44cd5Andy Huang public void onMessageTransform(String messageDomId, String transformText) { 1302ae92e15b48a00613e06ea4c6b274bd73a4e6ec40Andrew Sapperstein try { 1303ae92e15b48a00613e06ea4c6b274bd73a4e6ec40Andrew Sapperstein LogUtils.i(LOG_TAG, "TRANSFORM: (%s) %s", messageDomId, transformText); 1304ae92e15b48a00613e06ea4c6b274bd73a4e6ec40Andrew Sapperstein mMessageTransforms.put(messageDomId, transformText); 1305ae92e15b48a00613e06ea4c6b274bd73a4e6ec40Andrew Sapperstein onConversationTransformed(); 1306ae92e15b48a00613e06ea4c6b274bd73a4e6ec40Andrew Sapperstein } catch (Throwable t) { 1307ae92e15b48a00613e06ea4c6b274bd73a4e6ec40Andrew Sapperstein LogUtils.e(LOG_TAG, t, "Error in MailJsBridge.onMessageTransform"); 13088ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein } 13098ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein } 13108ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein 13118ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein @JavascriptInterface 13128ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein public void onInlineAttachmentsParsed(final String[] urls, final String[] messageIds) { 13138ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein try { 13148ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein getHandler().post(new FragmentRunnable("onInlineAttachmentsParsed", 13158ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein ConversationViewFragment.this) { 13168ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein @Override 13178ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein public void go() { 13188ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein try { 13198ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein for (int i = 0, size = urls.length; i < size; i++) { 13208ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein mUrlToMessageIdMap.put(urls[i], messageIds[i]); 13218ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein } 13228ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein } catch (ArrayIndexOutOfBoundsException e) { 13238ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein LogUtils.e(LOG_TAG, e, 13248ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein "Number of urls does not match number of message ids - %s:%s", 13258ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein urls.length, messageIds.length); 13268ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein } 13278ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein } 13288ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein }); 13298ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein } catch (Throwable t) { 13308ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein LogUtils.e(LOG_TAG, t, "Error in MailJsBridge.onInlineAttachmentsParsed"); 1331ae92e15b48a00613e06ea4c6b274bd73a4e6ec40Andrew Sapperstein } 133205c70c88e6b421bedce134799a0ea928dbf44cd5Andy Huang } 1333674afa42682908640167fc6109b76f6f843e6fbeMindy Pereira } 1334f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 133547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang private class NewMessagesInfo { 133647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang int count; 133706c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang int countFromSelf; 133847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang String senderAddress; 133947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 134047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang /** 134147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang * Return the display text for the new message notification overlay. It will be formatted 134247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang * appropriately for a single new message vs. multiple new messages. 134347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang * 134447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang * @return display text 134547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang */ 134647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang public String getNotificationText() { 1347ad0c30d0517e06c472c76b11795092f5e67d8f5amindyp Resources res = getResources(); 134847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang if (count > 1) { 134966d6911d57495b7010abeb5576a15f1521af443dAndrew Sapperstein return res.getQuantityString(R.plurals.new_incoming_messages_many, count, count); 135047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang } else { 13511617481de7c1f9b8dfcd25ba9828e933cf9d5490Andy Huang final Address addr = getAddress(senderAddress); 1352ad0c30d0517e06c472c76b11795092f5e67d8f5amindyp return res.getString(R.string.new_incoming_messages_one, 13532fd167d6131482da984768b5ee75cefa32ed3e32Andrew Sapperstein mBidiFormatter.unicodeWrap(TextUtils.isEmpty(addr.getPersonal()) 13542fd167d6131482da984768b5ee75cefa32ed3e32Andrew Sapperstein ? addr.getAddress() : addr.getPersonal())); 135547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang } 135647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang } 135747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang } 135847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 1359f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp @Override 1360c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook public void onMessageCursorLoadFinished(Loader<ObjectCursor<ConversationMessage>> loader, 1361c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook MessageCursor newCursor, MessageCursor oldCursor) { 1362f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp /* 1363f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * what kind of changes affect the MessageCursor? 1. new message(s) 2. 1364f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * read/unread state change 3. deleted message, either regular or draft 1365f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * 4. updated message, either from self or from others, updated in 1366f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * content or state or sender 5. star/unstar of message (technically 1367f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * similar to #1) 6. other label change Use MessageCursor.hashCode() to 1368f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * sort out interesting vs. no-op cursor updates. 1369f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp */ 1370014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang 1371233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang if (oldCursor != null && !oldCursor.isClosed()) { 1372014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang final NewMessagesInfo info = getNewIncomingMessagesInfo(newCursor); 1373014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang 1374014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang if (info.count > 0) { 1375014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang // don't immediately render new incoming messages from other 1376014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang // senders 1377014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang // (to avoid a new message from losing the user's focus) 1378014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang LogUtils.i(LOG_TAG, "CONV RENDER: conversation updated" 13799d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang + ", holding cursor for new incoming message (%s)", this); 1380014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang showNewMessageNotification(info); 1381014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang return; 1382014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang } 1383014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang 138406c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang final int oldState = oldCursor.getStateHashCode(); 138506c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang final boolean changed = newCursor.getStateHashCode() != oldState; 1386233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang 1387014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang if (!changed) { 1388014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang final boolean processedInPlace = processInPlaceUpdates(newCursor, oldCursor); 1389014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang if (processedInPlace) { 13909d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang LogUtils.i(LOG_TAG, "CONV RENDER: processed update(s) in place (%s)", this); 13911ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang } else { 1392f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp LogUtils.i(LOG_TAG, "CONV RENDER: uninteresting update" 13939d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang + ", ignoring this conversation update (%s)", this); 13941ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang } 13951ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang return; 139606c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang } else if (info.countFromSelf == 1) { 139706c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang // Special-case the very common case of a new cursor that is the same as the old 139806c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang // one, except that there is a new message from yourself. This happens upon send. 139906c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang final boolean sameExceptNewLast = newCursor.getStateHashCode(1) == oldState; 140006c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang if (sameExceptNewLast) { 140106c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang LogUtils.i(LOG_TAG, "CONV RENDER: update is a single new message from self" 140206c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang + " (%s)", this); 140306c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang newCursor.moveToLast(); 140406c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang processNewOutgoingMessage(newCursor.getMessage()); 140506c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang return; 140606c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang } 14071ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang } 14086766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang // cursors are different, and not due to an incoming message. fall 14096766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang // through and render. 14106766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang LogUtils.i(LOG_TAG, "CONV RENDER: conversation updated" 14116766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang + ", but not due to incoming message. rendering. (%s)", this); 141206c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang 141306c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang if (DEBUG_DUMP_CURSOR_CONTENTS) { 141406c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang LogUtils.i(LOG_TAG, "old cursor: %s", oldCursor.getDebugDump()); 141506c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang LogUtils.i(LOG_TAG, "new cursor: %s", newCursor.getDebugDump()); 141606c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang } 14176766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang } else { 14186766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang LogUtils.i(LOG_TAG, "CONV RENDER: initial render. (%s)", this); 1419243c23618b066bcdcd0ab9e36d8c01f50db2cbd0Andy Huang timerMark("message cursor load finished"); 1420b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang } 1421b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang 1422606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein renderContent(newCursor); 1423606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein } 1424606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein 1425606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein protected void renderContent(MessageCursor messageCursor) { 14264071c2f73218ce75750345557bb31a9110737841Mark Wei // if layout hasn't happened, delay render 14274071c2f73218ce75750345557bb31a9110737841Mark Wei // This is needed in addition to the showConversation() delay to speed 14284071c2f73218ce75750345557bb31a9110737841Mark Wei // up rotation and restoration. 14294071c2f73218ce75750345557bb31a9110737841Mark Wei if (mConversationContainer.getWidth() == 0) { 14304071c2f73218ce75750345557bb31a9110737841Mark Wei mNeedRender = true; 14314071c2f73218ce75750345557bb31a9110737841Mark Wei mConversationContainer.addOnLayoutChangeListener(this); 14324071c2f73218ce75750345557bb31a9110737841Mark Wei } else { 1433606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein renderConversation(messageCursor); 14344071c2f73218ce75750345557bb31a9110737841Mark Wei } 1435b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang } 1436b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang 1437f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp private NewMessagesInfo getNewIncomingMessagesInfo(MessageCursor newCursor) { 1438f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp final NewMessagesInfo info = new NewMessagesInfo(); 1439b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang 1440f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp int pos = -1; 1441f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp while (newCursor.moveToPosition(++pos)) { 1442f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp final Message m = newCursor.getMessage(); 1443f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp if (!mViewState.contains(m)) { 1444f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp LogUtils.i(LOG_TAG, "conversation diff: found new msg: %s", m.uri); 1445f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 14468960f0af431bc164003e09b3c8981aab808d9ec1Scott Kennedy final Address from = getAddress(m.getFrom()); 1447f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp // distinguish ours from theirs 1448f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp // new messages from the account owner should not trigger a 1449f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp // notification 1450f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp if (mAccount.ownsFromAddress(from.getAddress())) { 1451f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp LogUtils.i(LOG_TAG, "found message from self: %s", m.uri); 145206c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang info.countFromSelf++; 1453f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp continue; 1454f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 1455b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang 1456f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp info.count++; 14578960f0af431bc164003e09b3c8981aab808d9ec1Scott Kennedy info.senderAddress = m.getFrom(); 1458b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang } 1459b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang } 1460f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp return info; 1461b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang } 1462b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang 1463014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang private boolean processInPlaceUpdates(MessageCursor newCursor, MessageCursor oldCursor) { 1464014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang final Set<String> idsOfChangedBodies = Sets.newHashSet(); 14656b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang final List<Integer> changedOverlayPositions = Lists.newArrayList(); 14666b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang 1467014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang boolean changed = false; 1468014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang 1469014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang int pos = 0; 1470014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang while (true) { 1471014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang if (!newCursor.moveToPosition(pos) || !oldCursor.moveToPosition(pos)) { 1472014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang break; 1473014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang } 1474014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang 1475014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang final ConversationMessage newMsg = newCursor.getMessage(); 1476014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang final ConversationMessage oldMsg = oldCursor.getMessage(); 1477014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang 14788960f0af431bc164003e09b3c8981aab808d9ec1Scott Kennedy if (!TextUtils.equals(newMsg.getFrom(), oldMsg.getFrom()) || 14792a1e8e30bdd02dc08bdf2f878201b279f60d5142Andy Huang newMsg.isSending != oldMsg.isSending) { 14806b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang mAdapter.updateItemsForMessage(newMsg, changedOverlayPositions); 14812a1e8e30bdd02dc08bdf2f878201b279f60d5142Andy Huang LogUtils.i(LOG_TAG, "msg #%d (%d): detected from/sending change. isSending=%s", 14822a1e8e30bdd02dc08bdf2f878201b279f60d5142Andy Huang pos, newMsg.id, newMsg.isSending); 1483014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang } 1484014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang 1485014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang // update changed message bodies in-place 1486014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang if (!TextUtils.equals(newMsg.bodyHtml, oldMsg.bodyHtml) || 1487014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang !TextUtils.equals(newMsg.bodyText, oldMsg.bodyText)) { 1488014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang // maybe just set a flag to notify JS to re-request changed bodies 1489014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang idsOfChangedBodies.add('"' + mTemplates.getMessageDomId(newMsg) + '"'); 1490014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang LogUtils.i(LOG_TAG, "msg #%d (%d): detected body change", pos, newMsg.id); 1491014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang } 1492014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang 1493014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang pos++; 1494014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang } 1495014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang 14966b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang 14976b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang if (!changedOverlayPositions.isEmpty()) { 149806c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang // notify once after the entire adapter is updated 14996b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang mConversationContainer.onOverlayModelUpdate(changedOverlayPositions); 15006b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang changed = true; 150106c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang } 150206c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang 1503014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang if (!idsOfChangedBodies.isEmpty()) { 1504014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang mWebView.loadUrl(String.format("javascript:replaceMessageBodies([%s]);", 1505014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang TextUtils.join(",", idsOfChangedBodies))); 1506014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang changed = true; 1507014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang } 1508014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang 1509014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang return changed; 1510014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang } 1511014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang 151206c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang private void processNewOutgoingMessage(ConversationMessage msg) { 151399ee4565c0a2953fe95b2d5f8e80a84ae0f1b1a5Andrew Sapperstein // if there are items in the adapter and the last item is a border, 151499ee4565c0a2953fe95b2d5f8e80a84ae0f1b1a5Andrew Sapperstein // make the last border no longer be the last border 151599ee4565c0a2953fe95b2d5f8e80a84ae0f1b1a5Andrew Sapperstein if (mAdapter.getCount() > 0) { 151699ee4565c0a2953fe95b2d5f8e80a84ae0f1b1a5Andrew Sapperstein final ConversationOverlayItem item = mAdapter.getItem(mAdapter.getCount() - 1); 151799ee4565c0a2953fe95b2d5f8e80a84ae0f1b1a5Andrew Sapperstein if (item.getType() == ConversationViewAdapter.VIEW_TYPE_BORDER) { 151899ee4565c0a2953fe95b2d5f8e80a84ae0f1b1a5Andrew Sapperstein ((BorderItem) item).setIsLastBorder(false); 151999ee4565c0a2953fe95b2d5f8e80a84ae0f1b1a5Andrew Sapperstein } 152099ee4565c0a2953fe95b2d5f8e80a84ae0f1b1a5Andrew Sapperstein } 1521cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein 152206c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang mTemplates.reset(); 152306c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang // this method will add some items to mAdapter, but we deliberately want to avoid notifying 152406c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang // adapter listeners (i.e. ConversationContainer) until onWebContentGeometryChange is next 152506c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang // called, to prevent N+1 headers rendering with N message bodies. 1526cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein 1527cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein // We can just call previousCollapsed false here since the border 1528cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein // above the message we're about to render should always show 1529cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein // (which it also will since the message being render is expanded). 1530cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein renderMessage(msg, false /* previousCollapsed */, true /* expanded */, 1531cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein msg.alwaysShowImages, false /* renderBorder */, false /* firstBorder */); 1532cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein renderBorder(true /* contiguous */, true /* expanded */, 1533cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein false /* firstBorder */, true /* lastBorder */); 153406c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang mTempBodiesHtml = mTemplates.emit(); 153506c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang 153606c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang mViewState.setExpansionState(msg, ExpansionState.EXPANDED); 153706c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang // FIXME: should the provider set this as initial state? 153806c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang mViewState.setReadState(msg, false /* read */); 153906c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang 154091d782abc8015bd651fb5d0252b4d1ef369ec57bAndy Huang // From now until the updated spacer geometry is returned, the adapter items are mismatched 154191d782abc8015bd651fb5d0252b4d1ef369ec57bAndy Huang // with the existing spacers. Do not let them layout. 154291d782abc8015bd651fb5d0252b4d1ef369ec57bAndy Huang mConversationContainer.invalidateSpacerGeometry(); 154391d782abc8015bd651fb5d0252b4d1ef369ec57bAndy Huang 154406c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang mWebView.loadUrl("javascript:appendMessageHtml();"); 154506c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang } 154606c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang 1547cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook private class SetCookieTask extends AsyncTask<Void, Void, Void> { 1548cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook final String mUri; 1549b8361c9f8938b74977316319885998aae09aab77Paul Westbrook final Uri mAccountCookieQueryUri; 1550b8361c9f8938b74977316319885998aae09aab77Paul Westbrook final ContentResolver mResolver; 1551cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook 1552b8361c9f8938b74977316319885998aae09aab77Paul Westbrook SetCookieTask(Context context, Uri baseUri, Uri accountCookieQueryUri) { 1553b8361c9f8938b74977316319885998aae09aab77Paul Westbrook mUri = baseUri.toString(); 1554b8361c9f8938b74977316319885998aae09aab77Paul Westbrook mAccountCookieQueryUri = accountCookieQueryUri; 1555b8361c9f8938b74977316319885998aae09aab77Paul Westbrook mResolver = context.getContentResolver(); 1556cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook } 1557cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook 1558cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook @Override 1559cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook public Void doInBackground(Void... args) { 1560b8361c9f8938b74977316319885998aae09aab77Paul Westbrook // First query for the coookie string from the UI provider 1561b8361c9f8938b74977316319885998aae09aab77Paul Westbrook final Cursor cookieCursor = mResolver.query(mAccountCookieQueryUri, 1562b8361c9f8938b74977316319885998aae09aab77Paul Westbrook UIProvider.ACCOUNT_COOKIE_PROJECTION, null, null, null); 1563b8361c9f8938b74977316319885998aae09aab77Paul Westbrook if (cookieCursor == null) { 1564b8361c9f8938b74977316319885998aae09aab77Paul Westbrook return null; 1565b8361c9f8938b74977316319885998aae09aab77Paul Westbrook } 1566b8361c9f8938b74977316319885998aae09aab77Paul Westbrook 1567b8361c9f8938b74977316319885998aae09aab77Paul Westbrook try { 1568b8361c9f8938b74977316319885998aae09aab77Paul Westbrook if (cookieCursor.moveToFirst()) { 1569b8361c9f8938b74977316319885998aae09aab77Paul Westbrook final String cookie = cookieCursor.getString( 1570b8361c9f8938b74977316319885998aae09aab77Paul Westbrook cookieCursor.getColumnIndex(UIProvider.AccountCookieColumns.COOKIE)); 1571b8361c9f8938b74977316319885998aae09aab77Paul Westbrook 1572b8361c9f8938b74977316319885998aae09aab77Paul Westbrook if (cookie != null) { 1573b8361c9f8938b74977316319885998aae09aab77Paul Westbrook final CookieSyncManager csm = 1574b8361c9f8938b74977316319885998aae09aab77Paul Westbrook CookieSyncManager.createInstance(getContext()); 1575b8361c9f8938b74977316319885998aae09aab77Paul Westbrook CookieManager.getInstance().setCookie(mUri, cookie); 1576b8361c9f8938b74977316319885998aae09aab77Paul Westbrook csm.sync(); 1577b8361c9f8938b74977316319885998aae09aab77Paul Westbrook } 1578b8361c9f8938b74977316319885998aae09aab77Paul Westbrook } 1579b8361c9f8938b74977316319885998aae09aab77Paul Westbrook 1580b8361c9f8938b74977316319885998aae09aab77Paul Westbrook } finally { 1581b8361c9f8938b74977316319885998aae09aab77Paul Westbrook cookieCursor.close(); 1582b8361c9f8938b74977316319885998aae09aab77Paul Westbrook } 1583b8361c9f8938b74977316319885998aae09aab77Paul Westbrook 1584b8361c9f8938b74977316319885998aae09aab77Paul Westbrook 1585cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook return null; 1586cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook } 1587cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook } 158836280f3c2939bb2dacb4077bd528900346ff4bb9mindyp 158926d4d2d9c43c499f458808f050ec73ea3c28dec4mindyp @Override 159036280f3c2939bb2dacb4077bd528900346ff4bb9mindyp public void onConversationUpdated(Conversation conv) { 159136280f3c2939bb2dacb4077bd528900346ff4bb9mindyp final ConversationViewHeader headerView = (ConversationViewHeader) mConversationContainer 159236280f3c2939bb2dacb4077bd528900346ff4bb9mindyp .findViewById(R.id.conversation_header); 1593b2b98ba97983f225eec19dc9bc5333e6ef80ee15mindyp mConversation = conv; 15949e0b236be55cf9dab8f1670a9d91ed16e055a9d0mindyp if (headerView != null) { 15959e0b236be55cf9dab8f1670a9d91ed16e055a9d0mindyp headerView.onConversationUpdated(conv); 15962fd167d6131482da984768b5ee75cefa32ed3e32Andrew Sapperstein headerView.setSubject(conv.subject, mBidiFormatter); 15979e0b236be55cf9dab8f1670a9d91ed16e055a9d0mindyp } 159836280f3c2939bb2dacb4077bd528900346ff4bb9mindyp } 15994071c2f73218ce75750345557bb31a9110737841Mark Wei 16004071c2f73218ce75750345557bb31a9110737841Mark Wei @Override 16014071c2f73218ce75750345557bb31a9110737841Mark Wei public void onLayoutChange(View v, int left, int top, int right, 16024071c2f73218ce75750345557bb31a9110737841Mark Wei int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { 16034071c2f73218ce75750345557bb31a9110737841Mark Wei boolean sizeChanged = mNeedRender 16044071c2f73218ce75750345557bb31a9110737841Mark Wei && mConversationContainer.getWidth() != 0; 16054071c2f73218ce75750345557bb31a9110737841Mark Wei if (sizeChanged) { 16064071c2f73218ce75750345557bb31a9110737841Mark Wei mNeedRender = false; 16074071c2f73218ce75750345557bb31a9110737841Mark Wei mConversationContainer.removeOnLayoutChangeListener(this); 16084071c2f73218ce75750345557bb31a9110737841Mark Wei renderConversation(getMessageCursor()); 16094071c2f73218ce75750345557bb31a9110737841Mark Wei } 16104071c2f73218ce75750345557bb31a9110737841Mark Wei } 16111b3cc47f54072105c161d6ed557550e0e149b8bbmindyp 16121b3cc47f54072105c161d6ed557550e0e149b8bbmindyp @Override 16137cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux public void setMessageDetailsExpanded(MessageHeaderItem i, boolean expanded, int heightBefore) { 16141b3cc47f54072105c161d6ed557550e0e149b8bbmindyp mDiff = (expanded ? 1 : -1) * Math.abs(i.getHeight() - heightBefore); 16151b3cc47f54072105c161d6ed557550e0e149b8bbmindyp } 161602f9d18a54072db8d86c524f9c09e508092ddd7cAndy Huang 16177cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux /** 16187cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux * @return {@code true} because either the Print or Print All menu item is shown in GMail 16197cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux */ 16207cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux @Override 16217cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux protected boolean shouldShowPrintInOverflow() { 16227cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux return true; 16237cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux } 16247cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux 16257cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux @Override 16265c1692a5faeab220881a17a3427a8986ef874403Andrew Sapperstein protected void printConversation() { 1627234d35352eeaaa8d3928e31028be49112607bd29Andrew Sapperstein PrintUtils.printConversation(mActivity.getActivityContext(), getMessageCursor(), 1628234d35352eeaaa8d3928e31028be49112607bd29Andrew Sapperstein mAddressCache, mConversation.getBaseUri(mBaseUri), true /* useJavascript */); 16295c1692a5faeab220881a17a3427a8986ef874403Andrew Sapperstein } 16309b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira} 1631