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; 260b69338a45faa422ccba8faf64c9816c55d33e4aJin Caoimport android.graphics.Rect; 27b8361c9f8938b74977316319885998aae09aab77Paul Westbrookimport android.net.Uri; 28cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrookimport android.os.AsyncTask; 299b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereiraimport android.os.Bundle; 303bcf180f8104bc27319086a9a6ece5a3c2917c37mindypimport android.os.SystemClock; 31c966a8a65a75559f677574c2a53cb9d43490f04eJin Caoimport android.support.annotation.IdRes; 32fa674294e4ccfae7bd1317d87065b842bdc46181Tony Mantlerimport android.support.annotation.Nullable; 333af481c20edd84419d0d4a8476994ac008c0b97aAndrew Sappersteinimport android.support.v4.text.BidiFormatter; 348ec43e877a9c1925514f066655984e21fbd255e8Andrew Sappersteinimport android.support.v4.util.ArrayMap; 3547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huangimport android.text.TextUtils; 36a7404589b03ac9dd0d07b3f7d0a1ec92ac9acb62Jin Caoimport android.view.KeyEvent; 379b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereiraimport android.view.LayoutInflater; 38c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huangimport android.view.View; 394071c2f73218ce75750345557bb31a9110737841Mark Weiimport android.view.View.OnLayoutChangeListener; 409b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereiraimport android.view.ViewGroup; 41f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport android.webkit.ConsoleMessage; 42cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrookimport android.webkit.CookieManager; 43cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrookimport android.webkit.CookieSyncManager; 44974c966c14000d42f089e2021b098d5cf61f9245Mindy Pereiraimport android.webkit.JavascriptInterface; 45f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport android.webkit.WebChromeClient; 46e1302c3bdc0db5e104d10e8ad5de4c76424fb45aJames Lemieuximport android.webkit.WebResourceResponse; 47f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport android.webkit.WebSettings; 4817a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huangimport android.webkit.WebView; 499b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira 50821e578a71c7015646522e729600618f0ec16fc0Tony Mantlerimport com.android.emailcommon.mail.Address; 5159e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huangimport com.android.mail.FormattedDateBuilder; 529b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereiraimport com.android.mail.R; 53e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huangimport com.android.mail.analytics.Analytics; 5472953f25e00ee8e7b5c7682148430dc82f00a77dJin Caoimport com.android.mail.analytics.AnalyticsTimer; 555ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huangimport com.android.mail.browse.ConversationContainer; 56adbf3e8cadb66666f307352b72537fbac57b916fAndy Huangimport com.android.mail.browse.ConversationContainer.OverlayPosition; 57735a22a197215ec4787ad9f3cbaf465cce54f4d0Andrew Sappersteinimport com.android.mail.browse.ConversationFooterView.ConversationFooterCallbacks; 588812d3c50e35c4f2a02d29c35c76082c4ebec0cdAndrew Sappersteinimport com.android.mail.browse.ConversationMessage; 5946dfba6160b55a582b344328067e3dafeb881dd9Andy Huangimport com.android.mail.browse.ConversationOverlayItem; 607bdc3750454efe59617b7df945eadd7e59bee954Andy Huangimport com.android.mail.browse.ConversationViewAdapter; 61e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sappersteinimport com.android.mail.browse.ConversationViewAdapter.ConversationFooterItem; 6246dfba6160b55a582b344328067e3dafeb881dd9Andy Huangimport com.android.mail.browse.ConversationViewAdapter.MessageFooterItem; 637bdc3750454efe59617b7df945eadd7e59bee954Andy Huangimport com.android.mail.browse.ConversationViewAdapter.MessageHeaderItem; 6446dfba6160b55a582b344328067e3dafeb881dd9Andy Huangimport com.android.mail.browse.ConversationViewAdapter.SuperCollapsedBlockItem; 655ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huangimport com.android.mail.browse.ConversationViewHeader; 665ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huangimport com.android.mail.browse.ConversationWebView; 678ec43e877a9c1925514f066655984e21fbd255e8Andrew Sappersteinimport com.android.mail.browse.InlineAttachmentViewIntentBuilderCreator; 688ec43e877a9c1925514f066655984e21fbd255e8Andrew Sappersteinimport com.android.mail.browse.InlineAttachmentViewIntentBuilderCreatorHolder; 69c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huangimport com.android.mail.browse.MailWebView.ContentSizeChangeListener; 707bdc3750454efe59617b7df945eadd7e59bee954Andy Huangimport com.android.mail.browse.MessageCursor; 71381c322eb30c39f63a2bb82812d63262eb3c1c1cAndrew Sappersteinimport com.android.mail.browse.MessageFooterView; 7259e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huangimport com.android.mail.browse.MessageHeaderView; 73adbf3e8cadb66666f307352b72537fbac57b916fAndy Huangimport com.android.mail.browse.ScrollIndicatorsView; 7446dfba6160b55a582b344328067e3dafeb881dd9Andy Huangimport com.android.mail.browse.SuperCollapsedBlock; 750b7ed6fae6e36f2abc4ca916764177d1879731b4Andy Huangimport com.android.mail.browse.WebViewContextMenu; 76a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Caoimport com.android.mail.compose.ComposeActivity; 77c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrookimport com.android.mail.content.ObjectCursor; 78562c5ba7235948cf1d20a9afa40e67cd62f43cf7Andrew Sappersteinimport com.android.mail.print.PrintUtils; 799b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereiraimport com.android.mail.providers.Account; 809b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereiraimport com.android.mail.providers.Conversation; 81f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport com.android.mail.providers.Message; 82f323c046034b4658a80438575d8e9f01d92e57e6Alice Yangimport com.android.mail.providers.Settings; 83b8361c9f8938b74977316319885998aae09aab77Paul Westbrookimport com.android.mail.providers.UIProvider; 84cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huangimport com.android.mail.ui.ConversationViewState.ExpansionState; 85376294bbb5ded471ad577fdb492875a837021d08Andrew Sappersteinimport com.android.mail.utils.ConversationViewUtils; 8652a40d5900b66bb06e7df3a40e338760d69e8ce6Jin Caoimport com.android.mail.utils.KeyboardUtils; 87b334c9035e9b7a38766bb66c29da2208525d1e11Paul Westbrookimport com.android.mail.utils.LogTag; 889b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereiraimport com.android.mail.utils.LogUtils; 892e9acfe527426c00b5df2ca66189262dc40c8575Andy Huangimport com.android.mail.utils.Utils; 908da4dc852b131e7516ad1ab888e2d601d3f51bedJin Caoimport com.android.mail.utils.ViewUtils; 91543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huangimport com.google.common.collect.ImmutableList; 9246dfba6160b55a582b344328067e3dafeb881dd9Andy Huangimport com.google.common.collect.Lists; 9305c70c88e6b421bedce134799a0ea928dbf44cd5Andy Huangimport com.google.common.collect.Maps; 94b8331b4565566ca733997398e8c07a26cd2bee98Andy Huangimport com.google.common.collect.Sets; 9565fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huang 96eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedyimport java.util.ArrayList; 9746dfba6160b55a582b344328067e3dafeb881dd9Andy Huangimport java.util.List; 9805c70c88e6b421bedce134799a0ea928dbf44cd5Andy Huangimport java.util.Map; 99b8331b4565566ca733997398e8c07a26cd2bee98Andy Huangimport java.util.Set; 1009b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira 1019b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira/** 1029b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira * The conversation view UI component. 1039b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira */ 1049f957f3463fd149d33c209409e2bba500b539177Andrew Sappersteinpublic class ConversationViewFragment extends AbstractConversationViewFragment implements 1054ddda2f0a4ee5381a90779a6939b05b064ce5d11Andrew Sapperstein SuperCollapsedBlock.OnClickListener, OnLayoutChangeListener, 106735a22a197215ec4787ad9f3cbaf465cce54f4d0Andrew Sapperstein MessageHeaderView.MessageHeaderViewCallbacks, MessageFooterView.MessageFooterCallbacks, 107a7404589b03ac9dd0d07b3f7d0a1ec92ac9acb62Jin Cao WebViewContextMenu.Callbacks, ConversationFooterCallbacks, View.OnKeyListener { 1088e915724b6e4374da9b70161ee0a55f0c763e563Mindy Pereira 109b334c9035e9b7a38766bb66c29da2208525d1e11Paul Westbrook private static final String LOG_TAG = LogTag.getLogTag(); 110632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang public static final String LAYOUT_TAG = "ConvLayout"; 1119b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira 1129d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang /** 1131b3cc47f54072105c161d6ed557550e0e149b8bbmindyp * Difference in the height of the message header whose details have been expanded/collapsed 1141b3cc47f54072105c161d6ed557550e0e149b8bbmindyp */ 1151b3cc47f54072105c161d6ed557550e0e149b8bbmindyp private int mDiff = 0; 1161b3cc47f54072105c161d6ed557550e0e149b8bbmindyp 1171b3cc47f54072105c161d6ed557550e0e149b8bbmindyp /** 1189d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang * Default value for {@link #mLoadWaitReason}. Conversation load will happen immediately. 1199d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang */ 1209d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang private final int LOAD_NOW = 0; 1219d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang /** 1229d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang * Value for {@link #mLoadWaitReason} that means we are offscreen and waiting for the visible 1239d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang * conversation to finish loading before beginning our load. 1249d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang * <p> 1259d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang * When this value is set, the fragment should register with {@link ConversationListCallbacks} 1269d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang * to know when the visible conversation is loaded. When it is unset, it should unregister. 1279d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang */ 1289d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang private final int LOAD_WAIT_FOR_INITIAL_CONVERSATION = 1; 1299d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang /** 1309d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang * Value for {@link #mLoadWaitReason} used when a conversation is too heavyweight to load at 1319d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang * all when not visible (e.g. requires network fetch, or too complex). Conversation load will 1329d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang * wait until this fragment is visible. 1339d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang */ 1349d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang private final int LOAD_WAIT_UNTIL_VISIBLE = 2; 1353bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp 136ea7a5dbc54ccaaf858e63177f1c1b66bdc361d54Jin Cao // Default scroll distance when the user tries to scroll with up/down 137ea7a5dbc54ccaaf858e63177f1c1b66bdc361d54Jin Cao private final int DEFAULT_VERTICAL_SCROLL_DISTANCE_PX = 50; 138ea7a5dbc54ccaaf858e63177f1c1b66bdc361d54Jin Cao 1390b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao // Keyboard navigation 1400b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao private KeyboardNavigationController mNavigationController; 1410b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao // Since we manually control navigation for most of the conversation view due to problems 1420b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao // with two-pane layout but still rely on the system for SOME navigation, we need to keep track 1430b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao // of the view that had focus when KeyEvent.ACTION_DOWN was fired. This is because we only 1440b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao // manually change focus on KeyEvent.ACTION_UP (to prevent holding down the DOWN button and 1450b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao // lagging the app), however, the view in focus might have changed between ACTION_UP and 1460b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao // ACTION_DOWN since the system might have handled the ACTION_DOWN and moved focus. 1470b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao private View mOriginalKeyedView; 1480b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao private int mMaxScreenHeight; 1490b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao private int mTopOfVisibleScreen; 1500b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao 1519f957f3463fd149d33c209409e2bba500b539177Andrew Sapperstein protected ConversationContainer mConversationContainer; 1529b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira 1539f957f3463fd149d33c209409e2bba500b539177Andrew Sapperstein protected ConversationWebView mWebView; 1549b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira 155a7404589b03ac9dd0d07b3f7d0a1ec92ac9acb62Jin Cao private ViewGroup mTopmostOverlay; 156a7404589b03ac9dd0d07b3f7d0a1ec92ac9acb62Jin Cao 157a7fa9bfc60682e376d7ee10701ed59fe66add1beAndrew Sapperstein private ConversationViewProgressController mProgressController; 158376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein 1590ec03e8d930a58c9138b5ebe84468f99c332c504James Lemieux private ActionableToastBar mNewMessageBar; 1600ec03e8d930a58c9138b5ebe84468f99c332c504James Lemieux private ActionableToastBar.ActionClickedListener mNewMessageBarActionListener; 16147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 1629f957f3463fd149d33c209409e2bba500b539177Andrew Sapperstein protected HtmlConversationTemplates mTemplates; 163f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 164f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang private final MailJsBridge mJsBridge = new MailJsBridge(); 165f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 1669f957f3463fd149d33c209409e2bba500b539177Andrew Sapperstein protected ConversationViewAdapter mAdapter; 16751067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang 168b1d184d164303d83d342c60e4bffc732aa10ac2cAndrew Sapperstein protected boolean mViewsCreated; 1694071c2f73218ce75750345557bb31a9110737841Mark Wei // True if we attempted to render before the views were laid out 1704071c2f73218ce75750345557bb31a9110737841Mark Wei // We will render immediately once layout is done 1714071c2f73218ce75750345557bb31a9110737841Mark Wei private boolean mNeedRender; 17251067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang 17346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang /** 17446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang * Temporary string containing the message bodies of the messages within a super-collapsed 17546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang * block, for one-time use during block expansion. We cannot easily pass the body HTML 17646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang * into JS without problematic escaping, so hold onto it momentarily and signal JS to fetch it 17746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang * using {@link MailJsBridge}. 17846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang */ 17946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang private String mTempBodiesHtml; 18046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 181632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang private int mMaxAutoLoadMessages; 182632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang 1839f957f3463fd149d33c209409e2bba500b539177Andrew Sapperstein protected int mSideMarginPx; 18402f9d18a54072db8d86c524f9c09e508092ddd7cAndy Huang 1859d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang /** 1869d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang * If this conversation fragment is not visible, and it's inappropriate to load up front, 1879d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang * this is the reason we are waiting. This flag should be cleared once it's okay to load 1889d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang * the conversation. 1899d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang */ 1909d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang private int mLoadWaitReason = LOAD_NOW; 191632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang 1923bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp private boolean mEnableContentReadySignal; 19328b7aee7fa1f7d096d33fc823a88a64f7a3fa79dAndy Huang 194dde3f9ff76c1a7f9a6e9fa959a6422a8a8d82e81mindyp private ContentSizeChangeListener mWebViewSizeChangeListener; 195dde3f9ff76c1a7f9a6e9fa959a6422a8a8d82e81mindyp 196e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang private float mWebViewYPercent; 197e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang 198e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang /** 199e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang * Has loadData been called on the WebView yet? 200e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang */ 201e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang private boolean mWebViewLoadedData; 202e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang 20363b3c6725d60386f564ab53e3ba6495f0c158d9bAndy Huang private long mWebViewLoadStartMs; 20463b3c6725d60386f564ab53e3ba6495f0c158d9bAndy Huang 20505c70c88e6b421bedce134799a0ea928dbf44cd5Andy Huang private final Map<String, String> mMessageTransforms = Maps.newHashMap(); 20605c70c88e6b421bedce134799a0ea928dbf44cd5Andy Huang 2079d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang private final DataSetObserver mLoadedObserver = new DataSetObserver() { 2089d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang @Override 2099d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang public void onChanged() { 210376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein getHandler().post(new FragmentRunnable("delayedConversationLoad", 211376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein ConversationViewFragment.this) { 2129d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang @Override 2139d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang public void go() { 2149d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang LogUtils.d(LOG_TAG, "CVF load observer fired, this=%s", 2159d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang ConversationViewFragment.this); 2169d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang handleDelayedConversationLoad(); 2179d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang } 2189d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang }); 2199d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang } 2209d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang }; 221f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 222376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein private final Runnable mOnProgressDismiss = new FragmentRunnable("onProgressDismiss", this) { 2237d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang @Override 2247d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang public void go() { 22558192e5202d379ff62bf99995b08a0a7cf6646d1Scott Kennedy LogUtils.d(LOG_TAG, "onProgressDismiss go() - isUserVisible() = %b", isUserVisible()); 2267d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang if (isUserVisible()) { 2277d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang onConversationSeen(); 2287d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang } 22930bcfe7b69f9d8e0cad2ee62dae9d3571cd0d88bAndy Huang mWebView.onRenderComplete(); 2307d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang } 2317d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang }; 2327d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang 233bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang private static final boolean DEBUG_DUMP_CONVERSATION_HTML = false; 23447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang private static final boolean DISABLE_OFFSCREEN_LOADING = false; 23506c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang private static final boolean DEBUG_DUMP_CURSOR_CONTENTS = false; 236e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang 237e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang private static final String BUNDLE_KEY_WEBVIEW_Y_PERCENT = 238e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang ConversationViewFragment.class.getName() + "webview-y-percent"; 239bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang 2402fd167d6131482da984768b5ee75cefa32ed3e32Andrew Sapperstein private BidiFormatter mBidiFormatter; 2413af481c20edd84419d0d4a8476994ac008c0b97aAndrew Sapperstein 2426c51158ad3269f157424e6c7bd488425c98da08fVikram Aggarwal /** 2438ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein * Contains a mapping between inline image attachments and their local message id. 2448ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein */ 2458ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein private Map<String, String> mUrlToMessageIdMap; 2468ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein 2478ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein /** 2486c51158ad3269f157424e6c7bd488425c98da08fVikram Aggarwal * Constructor needs to be public to handle orientation changes and activity lifecycle events. 2496c51158ad3269f157424e6c7bd488425c98da08fVikram Aggarwal */ 250f0ea4849bf7a2c11f99ca0b42307ae8ba665b1dcPaul Westbrook public ConversationViewFragment() {} 2519b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira 2529b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira /** 2539b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira * Creates a new instance of {@link ConversationViewFragment}, initialized 254632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang * to display a conversation with other parameters inherited/copied from an existing bundle, 255632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang * typically one created using {@link #makeBasicArgs}. 256632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang */ 257632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang public static ConversationViewFragment newInstance(Bundle existingArgs, 258632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang Conversation conversation) { 259632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang ConversationViewFragment f = new ConversationViewFragment(); 260632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang Bundle args = new Bundle(existingArgs); 261632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang args.putParcelable(ARG_CONVERSATION, conversation); 262632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang f.setArguments(args); 263632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang return f; 264632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang } 265632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang 266f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp @Override 267adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang public void onAccountChanged(Account newAccount, Account oldAccount) { 268adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang // if overview mode has changed, re-render completely (no need to also update headers) 269adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang if (isOverviewMode(newAccount) != isOverviewMode(oldAccount)) { 270adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang setupOverviewMode(); 271adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang final MessageCursor c = getMessageCursor(); 272adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang if (c != null) { 273adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang renderConversation(c); 274adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang } else { 275adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang // Null cursor means this fragment is either waiting to load or in the middle of 276adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang // loading. Either way, a future render will happen anyway, and the new setting 277adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang // will take effect when that happens. 278adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang } 279adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang return; 280adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang } 281adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang 282f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp // settings may have been updated; refresh views that are known to 283f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp // depend on settings 284f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp mAdapter.notifyDataSetChanged(); 285632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang } 286632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang 2879b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira @Override 2889b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira public void onActivityCreated(Bundle savedInstanceState) { 2899d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang LogUtils.d(LOG_TAG, "IN CVF.onActivityCreated, this=%s visible=%s", this, isUserVisible()); 2909b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira super.onActivityCreated(savedInstanceState); 2911abfcaf0ee345acee28f0b4892314119082b28a2Mark Wei 2921abfcaf0ee345acee28f0b4892314119082b28a2Mark Wei if (mActivity == null || mActivity.isFinishing()) { 2931abfcaf0ee345acee28f0b4892314119082b28a2Mark Wei // Activity is finishing, just bail. 2941abfcaf0ee345acee28f0b4892314119082b28a2Mark Wei return; 2951abfcaf0ee345acee28f0b4892314119082b28a2Mark Wei } 2961abfcaf0ee345acee28f0b4892314119082b28a2Mark Wei 297f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp Context context = getContext(); 298f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp mTemplates = new HtmlConversationTemplates(context); 29959e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang 300f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp final FormattedDateBuilder dateBuilder = new FormattedDateBuilder(context); 30159e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang 3020b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao mNavigationController = mActivity.getKeyboardNavigationController(); 3030b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao 3048081df46ef5a7794374e41cd1836e778a2da9b31Paul Westbrook mAdapter = new ConversationViewAdapter(mActivity, this, 305735a22a197215ec4787ad9f3cbaf465cce54f4d0Andrew Sapperstein getLoaderManager(), this, this, getContactInfoSource(), this, this, 306a7404589b03ac9dd0d07b3f7d0a1ec92ac9acb62Jin Cao getListController(), this, mAddressCache, dateBuilder, mBidiFormatter, this); 30751067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang mConversationContainer.setOverlayAdapter(mAdapter); 30851067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang 30959e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang // set up snap header (the adapter usually does this with the other ones) 31085ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein mConversationContainer.getSnapHeader().initialize( 31185ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein this, mAddressCache, this, getContactInfoSource(), 31285ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein mActivity.getAccountController().getVeiledAddressMatcher()); 313c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang 31490eccf969d691abb34f9c6f3d854091adb0c18d1Andrew Sapperstein final Resources resources = getResources(); 31590eccf969d691abb34f9c6f3d854091adb0c18d1Andrew Sapperstein mMaxAutoLoadMessages = resources.getInteger(R.integer.max_auto_load_messages); 316632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang 31790eccf969d691abb34f9c6f3d854091adb0c18d1Andrew Sapperstein mSideMarginPx = resources.getDimensionPixelOffset( 31802f9d18a54072db8d86c524f9c09e508092ddd7cAndy Huang R.dimen.conversation_message_content_margin_side); 31902f9d18a54072db8d86c524f9c09e508092ddd7cAndy Huang 3208ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein mUrlToMessageIdMap = new ArrayMap<String, String>(); 3218ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein final InlineAttachmentViewIntentBuilderCreator creator = 3228ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein InlineAttachmentViewIntentBuilderCreatorHolder. 3238ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein getInlineAttachmentViewIntentCreator(); 3243c6fd44f9ae0cf60248dc64ee74d46afed633c45Andy Huang final WebViewContextMenu contextMenu = new WebViewContextMenu(getActivity(), 32591fa034d624d8690905f30a13ae3ffb9601cf948Paul Westbrook creator.createInlineAttachmentViewIntentBuilder(mAccount, 3263c6fd44f9ae0cf60248dc64ee74d46afed633c45Andy Huang mConversation != null ? mConversation.id : -1)); 3273c6fd44f9ae0cf60248dc64ee74d46afed633c45Andy Huang contextMenu.setCallbacks(this); 3283c6fd44f9ae0cf60248dc64ee74d46afed633c45Andy Huang mWebView.setOnCreateContextMenuListener(contextMenu); 3290b7ed6fae6e36f2abc4ca916764177d1879731b4Andy Huang 330adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang // set this up here instead of onCreateView to ensure the latest Account is loaded 331adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang setupOverviewMode(); 332adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang 3339d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang // Defer the call to initLoader with a Handler. 3349d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang // We want to wait until we know which fragments are present and their final visibility 3359d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang // states before going off and doing work. This prevents extraneous loading from occurring 3369d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang // as the ViewPager shifts about before the initial position is set. 3379d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang // 3389d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang // e.g. click on item #10 3399d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang // ViewPager.setAdapter() actually first loads #0 and #1 under the assumption that #0 is 3409d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang // the initial primary item 3419d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang // Then CPC immediately sets the primary item to #10, which tears down #0/#1 and sets up 3429d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang // #9/#10/#11. 343376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein getHandler().post(new FragmentRunnable("showConversation", this) { 3449d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang @Override 3459d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang public void go() { 3469d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang showConversation(); 3479d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang } 3489d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang }); 349cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook 350606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein if (mConversation != null && mConversation.conversationBaseUri != null && 351f59d01c3116dc2adde97a5b52aa6094144c2d315Andrew Sapperstein !Utils.isEmpty(mAccount.accountCookieQueryUri)) { 352cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook // Set the cookie for this base url 353f59d01c3116dc2adde97a5b52aa6094144c2d315Andrew Sapperstein new SetCookieTask(getContext(), mConversation.conversationBaseUri.toString(), 354f59d01c3116dc2adde97a5b52aa6094144c2d315Andrew Sapperstein mAccount.accountCookieQueryUri).execute(); 355cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook } 3560b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao 3570b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao // Find the height of the screen for manually scrolling the webview via keyboard. 3580b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao final Rect screen = new Rect(); 3590b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao mActivity.getWindow().getDecorView().getWindowVisibleDisplayFrame(screen); 3600b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao mMaxScreenHeight = screen.bottom; 3610b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao mTopOfVisibleScreen = screen.top + mActivity.getSupportActionBar().getHeight(); 3629b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira } 3639b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira 3649b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira @Override 365e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang public void onCreate(Bundle savedState) { 366e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang super.onCreate(savedState); 367e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang 368b1d184d164303d83d342c60e4bffc732aa10ac2cAndrew Sapperstein mWebViewClient = createConversationWebViewClient(); 369376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein 370e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang if (savedState != null) { 371e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang mWebViewYPercent = savedState.getFloat(BUNDLE_KEY_WEBVIEW_Y_PERCENT); 372e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang } 3733af481c20edd84419d0d4a8476994ac008c0b97aAndrew Sapperstein 3742fd167d6131482da984768b5ee75cefa32ed3e32Andrew Sapperstein mBidiFormatter = BidiFormatter.getInstance(); 375e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang } 376e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang 377b1d184d164303d83d342c60e4bffc732aa10ac2cAndrew Sapperstein protected ConversationWebViewClient createConversationWebViewClient() { 378b1d184d164303d83d342c60e4bffc732aa10ac2cAndrew Sapperstein return new ConversationWebViewClient(mAccount); 379b1d184d164303d83d342c60e4bffc732aa10ac2cAndrew Sapperstein } 380b1d184d164303d83d342c60e4bffc732aa10ac2cAndrew Sapperstein 381e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang @Override 3829b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira public View onCreateView(LayoutInflater inflater, 3839b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira ViewGroup container, Bundle savedInstanceState) { 384632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang View rootView = inflater.inflate(R.layout.conversation_view, container, false); 385f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang mConversationContainer = (ConversationContainer) rootView 386f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang .findViewById(R.id.conversation_container); 3878f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang mConversationContainer.setAccountController(this); 38847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 389a7404589b03ac9dd0d07b3f7d0a1ec92ac9acb62Jin Cao mTopmostOverlay = 39085ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein (ViewGroup) mConversationContainer.findViewById(R.id.conversation_topmost_overlay); 391a7404589b03ac9dd0d07b3f7d0a1ec92ac9acb62Jin Cao mTopmostOverlay.setOnKeyListener(this); 392a7404589b03ac9dd0d07b3f7d0a1ec92ac9acb62Jin Cao inflateSnapHeader(mTopmostOverlay, inflater); 39385ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein mConversationContainer.setupSnapHeader(); 39485ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein 39585ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein setupNewMessageBar(); 39647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 397376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein mProgressController = new ConversationViewProgressController(this, getHandler()); 398376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein mProgressController.instantiateProgressIndicators(rootView); 3993bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp 400e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein mWebView = (ConversationWebView) 401e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein mConversationContainer.findViewById(R.id.conversation_webview); 402f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 403f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang mWebView.addJavascriptInterface(mJsBridge, "mail"); 4043bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp // On JB or newer, we use the 'webkitAnimationStart' DOM event to signal load complete 4053bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp // Below JB, try to speed up initial render by having the webview do supplemental draws to 4063bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp // custom a software canvas. 407b941fdb95f61cdde4e1775ac9034343951e8b075mindyp // TODO(mindyp): 408b941fdb95f61cdde4e1775ac9034343951e8b075mindyp //PAGE READINESS SIGNAL FOR JELLYBEAN AND NEWER 409b941fdb95f61cdde4e1775ac9034343951e8b075mindyp // Notify the app on 'webkitAnimationStart' of a simple dummy element with a simple no-op 410b941fdb95f61cdde4e1775ac9034343951e8b075mindyp // animation that immediately runs on page load. The app uses this as a signal that the 411b941fdb95f61cdde4e1775ac9034343951e8b075mindyp // content is loaded and ready to draw, since WebView delays firing this event until the 412b941fdb95f61cdde4e1775ac9034343951e8b075mindyp // layers are composited and everything is ready to draw. 413b941fdb95f61cdde4e1775ac9034343951e8b075mindyp // This signal does not seem to be reliable, so just use the old method for now. 414f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang final boolean isJBOrLater = Utils.isRunningJellybeanOrLater(); 415f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang final boolean isUserVisible = isUserVisible(); 416f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang mWebView.setUseSoftwareLayer(!isJBOrLater); 417f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang mEnableContentReadySignal = isJBOrLater && isUserVisible; 418f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang mWebView.onUserVisibilityChanged(isUserVisible); 41917a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huang mWebView.setWebViewClient(mWebViewClient); 420c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang final WebChromeClient wcc = new WebChromeClient() { 421f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang @Override 422f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang public boolean onConsoleMessage(ConsoleMessage consoleMessage) { 4238ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein if (consoleMessage.messageLevel() == ConsoleMessage.MessageLevel.ERROR) { 42412664d5e6d7e4636d043f91bdd33c1f0f269b8f3Andrew Sapperstein LogUtils.e(LOG_TAG, "JS: %s (%s:%d) f=%s", consoleMessage.message(), 4258ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein consoleMessage.sourceId(), consoleMessage.lineNumber(), 4268ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein ConversationViewFragment.this); 4278ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein } else { 4288ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein LogUtils.i(LOG_TAG, "JS: %s (%s:%d) f=%s", consoleMessage.message(), 4298ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein consoleMessage.sourceId(), consoleMessage.lineNumber(), 4308ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein ConversationViewFragment.this); 4318ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein } 432f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang return true; 433f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang } 434c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang }; 435c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang mWebView.setWebChromeClient(wcc); 436f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 4373233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang final WebSettings settings = mWebView.getSettings(); 438f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 439f50aafa8f98545b1a0f88523c253bc2b94117783Greg Bullock final ScrollIndicatorsView scrollIndicators = 44082d9f637491dcdfbb13dfc33c9cef6543d5a74d6Greg Bullock (ScrollIndicatorsView) rootView.findViewById(R.id.scroll_indicators); 441f50aafa8f98545b1a0f88523c253bc2b94117783Greg Bullock scrollIndicators.setSourceView(mWebView); 44256d83850db72592a16f4e6ee9e0d59b60ec0824aMark Wei 443f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang settings.setJavaScriptEnabled(true); 444f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 445376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein ConversationViewUtils.setTextZoom(getResources(), settings); 446c319b551bebe40b9607acf0ee1d26a880fde9212Andy Huang 4478291a8a61e2f75b454c3f97f060ef0c48fe7ede0Andrew Sapperstein if (Utils.isRunningLOrLater()) { 4488291a8a61e2f75b454c3f97f060ef0c48fe7ede0Andrew Sapperstein CookieManager.getInstance().setAcceptThirdPartyCookies(mWebView, true /* accept */); 4498291a8a61e2f75b454c3f97f060ef0c48fe7ede0Andrew Sapperstein } 4508291a8a61e2f75b454c3f97f060ef0c48fe7ede0Andrew Sapperstein 45151067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang mViewsCreated = true; 452e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang mWebViewLoadedData = false; 45351067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang 4549b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira return rootView; 4559b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira } 4569b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira 45785ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein protected void inflateSnapHeader(ViewGroup topmostOverlay, LayoutInflater inflater) { 45885ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein inflater.inflate(R.layout.conversation_topmost_overlay_items, topmostOverlay, true); 45985ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein } 46085ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein 46185ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein protected void setupNewMessageBar() { 4620ec03e8d930a58c9138b5ebe84468f99c332c504James Lemieux mNewMessageBar = (ActionableToastBar) mConversationContainer.findViewById( 46385ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein R.id.new_message_notification_bar); 4640ec03e8d930a58c9138b5ebe84468f99c332c504James Lemieux mNewMessageBarActionListener = new ActionableToastBar.ActionClickedListener() { 46585ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein @Override 4660ec03e8d930a58c9138b5ebe84468f99c332c504James Lemieux public void onActionClicked(Context context) { 46785ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein onNewMessageBarClick(); 46885ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein } 4690ec03e8d930a58c9138b5ebe84468f99c332c504James Lemieux }; 47085ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein } 47185ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein 4729b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira @Override 473f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang public void onResume() { 474f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang super.onResume(); 475f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang if (mWebView != null) { 476f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang mWebView.onResume(); 477f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang } 478f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang } 479f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang 480f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang @Override 481f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang public void onPause() { 482f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang super.onPause(); 483f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang if (mWebView != null) { 484f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang mWebView.onPause(); 485f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang } 486f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang } 487f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang 488f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang @Override 4899b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira public void onDestroyView() { 4909b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira super.onDestroyView(); 49146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang mConversationContainer.setOverlayAdapter(null); 49246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang mAdapter = null; 4939d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang resetLoadWaiting(); // be sure to unregister any active load observer 49451067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang mViewsCreated = false; 4959b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira } 4969b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira 497e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang @Override 498e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang public void onSaveInstanceState(Bundle outState) { 499e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang super.onSaveInstanceState(outState); 500e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang 501e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang outState.putFloat(BUNDLE_KEY_WEBVIEW_Y_PERCENT, calculateScrollYPercent()); 502e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang } 503e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang 504e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang private float calculateScrollYPercent() { 5051b56a67396e40b65e15743bcf8ccce9a623b2f2cPaul Westbrook final float p; 5061b56a67396e40b65e15743bcf8ccce9a623b2f2cPaul Westbrook if (mWebView == null) { 5071b56a67396e40b65e15743bcf8ccce9a623b2f2cPaul Westbrook // onCreateView hasn't been called, return 0 as the user hasn't scrolled the view. 5081b56a67396e40b65e15743bcf8ccce9a623b2f2cPaul Westbrook return 0; 5091b56a67396e40b65e15743bcf8ccce9a623b2f2cPaul Westbrook } 5101b56a67396e40b65e15743bcf8ccce9a623b2f2cPaul Westbrook 5111b56a67396e40b65e15743bcf8ccce9a623b2f2cPaul Westbrook final int scrollY = mWebView.getScrollY(); 5121b56a67396e40b65e15743bcf8ccce9a623b2f2cPaul Westbrook final int viewH = mWebView.getHeight(); 5131b56a67396e40b65e15743bcf8ccce9a623b2f2cPaul Westbrook final int webH = (int) (mWebView.getContentHeight() * mWebView.getScale()); 514e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang 515e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang if (webH == 0 || webH <= viewH) { 516e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang p = 0; 517e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang } else if (scrollY + viewH >= webH) { 518e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang // The very bottom is a special case, it acts as a stronger anchor than the scroll top 519e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang // at that point. 520e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang p = 1.0f; 521e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang } else { 522e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang p = (float) scrollY / webH; 523e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang } 524e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang return p; 525e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang } 526e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang 5279d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang private void resetLoadWaiting() { 5289d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang if (mLoadWaitReason == LOAD_WAIT_FOR_INITIAL_CONVERSATION) { 5299d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang getListController().unregisterConversationLoadedObserver(mLoadedObserver); 5309d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang } 5319d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang mLoadWaitReason = LOAD_NOW; 5329d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang } 5339d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang 5345ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang @Override 535f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp protected void markUnread() { 536d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal super.markUnread(); 537839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang // Ignore unsafe calls made after a fragment is detached from an activity 538839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang final ControllableActivity activity = (ControllableActivity) getActivity(); 539839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang if (activity == null) { 540839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang LogUtils.w(LOG_TAG, "ignoring markUnread for conv=%s", mConversation.id); 541839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang return; 542839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang } 543839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang 54428e31e2417d775b343919cf6018f85871e40a294Andy Huang if (mViewState == null) { 54528e31e2417d775b343919cf6018f85871e40a294Andy Huang LogUtils.i(LOG_TAG, "ignoring markUnread for conv with no view state (%d)", 54628e31e2417d775b343919cf6018f85871e40a294Andy Huang mConversation.id); 54728e31e2417d775b343919cf6018f85871e40a294Andy Huang return; 54828e31e2417d775b343919cf6018f85871e40a294Andy Huang } 549839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang activity.getConversationUpdater().markConversationMessagesUnread(mConversation, 5504a878b606961825fb4fd412b460df75779f09ad2Vikram Aggarwal mViewState.getUnreadMessageUris(), mViewState.getConversationInfo()); 551839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang } 552839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang 553f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp @Override 554f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp public void onUserVisibleHintChanged() { 5559d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang final boolean userVisible = isUserVisible(); 55658192e5202d379ff62bf99995b08a0a7cf6646d1Scott Kennedy LogUtils.d(LOG_TAG, "ConversationViewFragment#onUserVisibleHintChanged(), userVisible = %b", 55758192e5202d379ff62bf99995b08a0a7cf6646d1Scott Kennedy userVisible); 5589d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang 5599d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang if (!userVisible) { 560376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein mProgressController.dismissLoadingStatus(); 5619d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang } else if (mViewsCreated) { 562e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang String loadTag = null; 563eeb4a35d7c596192666152d7115ac3c842b901c4Andy Huang final boolean isInitialLoading; 564eeb4a35d7c596192666152d7115ac3c842b901c4Andy Huang if (mActivity != null) { 565eeb4a35d7c596192666152d7115ac3c842b901c4Andy Huang isInitialLoading = mActivity.getConversationUpdater() 566e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang .isInitialConversationLoading(); 567eeb4a35d7c596192666152d7115ac3c842b901c4Andy Huang } else { 568eeb4a35d7c596192666152d7115ac3c842b901c4Andy Huang isInitialLoading = true; 569eeb4a35d7c596192666152d7115ac3c842b901c4Andy Huang } 570e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang 5719d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang if (getMessageCursor() != null) { 5729d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang LogUtils.d(LOG_TAG, "Fragment is now user-visible, onConversationSeen: %s", this); 573e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang if (!isInitialLoading) { 574e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang loadTag = "preloaded"; 575e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang } 576f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp onConversationSeen(); 5779d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang } else if (isLoadWaiting()) { 5789d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang LogUtils.d(LOG_TAG, "Fragment is now user-visible, showing conversation: %s", this); 579e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang if (!isInitialLoading) { 580e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang loadTag = "load_deferred"; 581e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang } 5829d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang handleDelayedConversationLoad(); 583632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang } 584e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang 585e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang if (loadTag != null) { 586e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang // pager swipes are visibility transitions to 'visible' except during initial 587e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang // pager load on A) enter conversation mode B) rotate C) 2-pane conv-mode list-tap 588e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang Analytics.getInstance().sendEvent("pager_swipe", loadTag, 589e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang getCurrentFolderTypeDesc(), 0); 590e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang } 591632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang } 592f8cf5468500677fceac3025727daba8155d319d6Andy Huang 59330bcfe7b69f9d8e0cad2ee62dae9d3571cd0d88bAndy Huang if (mWebView != null) { 59430bcfe7b69f9d8e0cad2ee62dae9d3571cd0d88bAndy Huang mWebView.onUserVisibilityChanged(userVisible); 59530bcfe7b69f9d8e0cad2ee62dae9d3571cd0d88bAndy Huang } 596f8cf5468500677fceac3025727daba8155d319d6Andy Huang } 597f8cf5468500677fceac3025727daba8155d319d6Andy Huang 5989d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang /** 5999d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang * Will either call initLoader now to begin loading, or set {@link #mLoadWaitReason} and do 6009d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang * nothing (in which case you should later call {@link #handleDelayedConversationLoad()}). 6019d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang */ 6029b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira private void showConversation() { 6039d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang final int reason; 6049d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang 6059d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang if (isUserVisible()) { 6069d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang LogUtils.i(LOG_TAG, 6079d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang "SHOWCONV: CVF is user-visible, immediately loading conversation (%s)", this); 6089d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang reason = LOAD_NOW; 609243c23618b066bcdcd0ab9e36d8c01f50db2cbd0Andy Huang timerMark("CVF.showConversation"); 6109d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang } else { 6119d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang final boolean disableOffscreenLoading = DISABLE_OFFSCREEN_LOADING 6120b8c080491f6884c2cfb7fcc473d970a9f4b97b9Alice Yang || Utils.isLowRamDevice(getContext()) 613606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein || (mConversation != null && (mConversation.isRemote 614606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein || mConversation.getNumMessages() > mMaxAutoLoadMessages)); 6159d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang 6169d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang // When not visible, we should not immediately load if either this conversation is 6179d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang // too heavyweight, or if the main/initial conversation is busy loading. 6189d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang if (disableOffscreenLoading) { 6199d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang reason = LOAD_WAIT_UNTIL_VISIBLE; 6209d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang LogUtils.i(LOG_TAG, "SHOWCONV: CVF waiting until visible to load (%s)", this); 6219d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang } else if (getListController().isInitialConversationLoading()) { 6229d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang reason = LOAD_WAIT_FOR_INITIAL_CONVERSATION; 6239d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang LogUtils.i(LOG_TAG, "SHOWCONV: CVF waiting for initial to finish (%s)", this); 6249d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang getListController().registerConversationLoadedObserver(mLoadedObserver); 6259d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang } else { 6269d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang LogUtils.i(LOG_TAG, 6279d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang "SHOWCONV: CVF is not visible, but no reason to wait. loading now. (%s)", 6289d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang this); 6299d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang reason = LOAD_NOW; 6309d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang } 631632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang } 6329d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang 6339d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang mLoadWaitReason = reason; 6349d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang if (mLoadWaitReason == LOAD_NOW) { 6359d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang startConversationLoad(); 6369d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang } 6379d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang } 6389d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang 6399d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang private void handleDelayedConversationLoad() { 6409d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang resetLoadWaiting(); 6419d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang startConversationLoad(); 6429d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang } 6439d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang 6449d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang private void startConversationLoad() { 6453bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp mWebView.setVisibility(View.VISIBLE); 646606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein loadContent(); 6473bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp // TODO(mindyp): don't show loading status for a previously rendered 6483bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp // conversation. Ielieve this is better done by making sure don't show loading status 6493bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp // until XX ms have passed without loading completed. 650376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein mProgressController.showLoadingStatus(isUserVisible()); 6518e915724b6e4374da9b70161ee0a55f0c763e563Mindy Pereira } 6528e915724b6e4374da9b70161ee0a55f0c763e563Mindy Pereira 653606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein /** 654606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein * Can be overridden in case a subclass needs to load something other than 655606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein * the messages of a conversation. 656606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein */ 657606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein protected void loadContent() { 658606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein getLoaderManager().initLoader(MESSAGE_LOADER, Bundle.EMPTY, getMessageLoaderCallbacks()); 659606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein } 660606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein 6617d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang private void revealConversation() { 662243c23618b066bcdcd0ab9e36d8c01f50db2cbd0Andy Huang timerMark("revealing conversation"); 663376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein mProgressController.dismissLoadingStatus(mOnProgressDismiss); 66472953f25e00ee8e7b5c7682148430dc82f00a77dJin Cao if (isUserVisible()) { 66572953f25e00ee8e7b5c7682148430dc82f00a77dJin Cao AnalyticsTimer.getInstance().logDuration(AnalyticsTimer.OPEN_CONV_VIEW_FROM_LIST, 66672953f25e00ee8e7b5c7682148430dc82f00a77dJin Cao true /* isDestructive */, "open_conversation", "from_list", null); 66772953f25e00ee8e7b5c7682148430dc82f00a77dJin Cao } 6687d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang } 6697d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang 6709d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang private boolean isLoadWaiting() { 6719d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang return mLoadWaitReason != LOAD_NOW; 6729d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang } 6739d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang 67451067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang private void renderConversation(MessageCursor messageCursor) { 6753bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp final String convHtml = renderMessageBodies(messageCursor, mEnableContentReadySignal); 676243c23618b066bcdcd0ab9e36d8c01f50db2cbd0Andy Huang timerMark("rendered conversation"); 677bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang 678bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang if (DEBUG_DUMP_CONVERSATION_HTML) { 679bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang java.io.FileWriter fw = null; 680bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang try { 681f1566b1e77e9d7ac66ebb47897b5c652e2b18943Andrew Sapperstein fw = new java.io.FileWriter(getSdCardFilePath()); 682bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang fw.write(convHtml); 683bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang } catch (java.io.IOException e) { 684bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang e.printStackTrace(); 685bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang } finally { 686bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang if (fw != null) { 687bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang try { 688bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang fw.close(); 689bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang } catch (java.io.IOException e) { 690bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang e.printStackTrace(); 691bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang } 692bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang } 693bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang } 694bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang } 695bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang 696e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang // save off existing scroll position before re-rendering 697e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang if (mWebViewLoadedData) { 698e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang mWebViewYPercent = calculateScrollYPercent(); 699e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang } 700e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang 701bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang mWebView.loadDataWithBaseURL(mBaseUri, convHtml, "text/html", "utf-8", null); 702e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang mWebViewLoadedData = true; 70363b3c6725d60386f564ab53e3ba6495f0c158d9bAndy Huang mWebViewLoadStartMs = SystemClock.uptimeMillis(); 70451067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang } 70551067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang 706f1566b1e77e9d7ac66ebb47897b5c652e2b18943Andrew Sapperstein protected String getSdCardFilePath() { 707f1566b1e77e9d7ac66ebb47897b5c652e2b18943Andrew Sapperstein return "/sdcard/conv" + mConversation.id + ".html"; 708f1566b1e77e9d7ac66ebb47897b5c652e2b18943Andrew Sapperstein } 709f1566b1e77e9d7ac66ebb47897b5c652e2b18943Andrew Sapperstein 7107bdc3750454efe59617b7df945eadd7e59bee954Andy Huang /** 7117bdc3750454efe59617b7df945eadd7e59bee954Andy Huang * Populate the adapter with overlay views (message headers, super-collapsed blocks, a 7127bdc3750454efe59617b7df945eadd7e59bee954Andy Huang * conversation header), and return an HTML document with spacer divs inserted for all overlays. 7137bdc3750454efe59617b7df945eadd7e59bee954Andy Huang * 7147bdc3750454efe59617b7df945eadd7e59bee954Andy Huang */ 7159f957f3463fd149d33c209409e2bba500b539177Andrew Sapperstein protected String renderMessageBodies(MessageCursor messageCursor, 7163bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp boolean enableContentReadySignal) { 717f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang int pos = -1; 718632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang 7191ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang LogUtils.d(LOG_TAG, "IN renderMessageBodies, fragment=%s", this); 7207bdc3750454efe59617b7df945eadd7e59bee954Andy Huang boolean allowNetworkImages = false; 7217bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 722c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang // TODO: re-use any existing adapter item state (expanded, details expanded, show pics) 72328b7aee7fa1f7d096d33fc823a88a64f7a3fa79dAndy Huang 7247bdc3750454efe59617b7df945eadd7e59bee954Andy Huang // Walk through the cursor and build up an overlay adapter as you go. 7257bdc3750454efe59617b7df945eadd7e59bee954Andy Huang // Each overlay has an entry in the adapter for easy scroll handling in the container. 7267bdc3750454efe59617b7df945eadd7e59bee954Andy Huang // Items are not necessarily 1:1 in cursor and adapter because of super-collapsed blocks. 7277bdc3750454efe59617b7df945eadd7e59bee954Andy Huang // When adding adapter items, also add their heights to help the container later determine 7287bdc3750454efe59617b7df945eadd7e59bee954Andy Huang // overlay dimensions. 7297bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 730db620fe42dcf1909468822b61238a23103d15376Andy Huang // When re-rendering, prevent ConversationContainer from laying out overlays until after 731db620fe42dcf1909468822b61238a23103d15376Andy Huang // the new spacers are positioned by WebView. 732db620fe42dcf1909468822b61238a23103d15376Andy Huang mConversationContainer.invalidateSpacerGeometry(); 733db620fe42dcf1909468822b61238a23103d15376Andy Huang 7347bdc3750454efe59617b7df945eadd7e59bee954Andy Huang mAdapter.clear(); 7357bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 73647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang // re-evaluate the message parts of the view state, since the messages may have changed 73747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang // since the previous render 73847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang final ConversationViewState prevState = mViewState; 73947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang mViewState = new ConversationViewState(prevState); 74047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 7415ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang // N.B. the units of height for spacers are actually dp and not px because WebView assumes 7422e9acfe527426c00b5df2ca66189262dc40c8575Andy Huang // a pixel is an mdpi pixel, unless you set device-dpi. 7435ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang 7447bdc3750454efe59617b7df945eadd7e59bee954Andy Huang // add a single conversation header item 7457bdc3750454efe59617b7df945eadd7e59bee954Andy Huang final int convHeaderPos = mAdapter.addConversationHeader(mConversation); 74623014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang final int convHeaderPx = measureOverlayHeight(convHeaderPos); 7473233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang 7484dc732387454eef3ee6d89f9fa393630eb6213f9Andy Huang mTemplates.startConversation(mWebView.getViewportWidth(), 7494dc732387454eef3ee6d89f9fa393630eb6213f9Andy Huang mWebView.screenPxToWebPx(mSideMarginPx), mWebView.screenPxToWebPx(convHeaderPx)); 7503233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang 75146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang int collapsedStart = -1; 752839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang ConversationMessage prevCollapsedMsg = null; 753e8221483ce5f013f02a4ef63d6682cc36313cda7Andrew Sapperstein 7548ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein final boolean alwaysShowImages = shouldAlwaysShowImages(); 755f323c046034b4658a80438575d8e9f01d92e57e6Alice Yang 756e8221483ce5f013f02a4ef63d6682cc36313cda7Andrew Sapperstein boolean prevSafeForImages = alwaysShowImages; 75746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 758735a22a197215ec4787ad9f3cbaf465cce54f4d0Andrew Sapperstein boolean hasDraft = false; 759f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang while (messageCursor.moveToPosition(++pos)) { 760839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang final ConversationMessage msg = messageCursor.getMessage(); 76146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 762e8221483ce5f013f02a4ef63d6682cc36313cda7Andrew Sapperstein final boolean safeForImages = alwaysShowImages || 763202738427ede23dd5863583aa3df17e4abfbf5e2Scott Kennedy msg.alwaysShowImages || prevState.getShouldShowImages(msg); 7643233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang allowNetworkImages |= safeForImages; 7652405528bb15245f594277fe7b6efacd6a18ce69eAndy Huang 76608098ec4c894d9a15dfe800ad2397494e7e0a79aPaul Westbrook final Integer savedExpanded = prevState.getExpansionState(msg); 76708098ec4c894d9a15dfe800ad2397494e7e0a79aPaul Westbrook final int expandedState; 768839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang if (savedExpanded != null) { 7691ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang if (ExpansionState.isSuperCollapsed(savedExpanded) && messageCursor.isLast()) { 7701ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang // override saved state when this is now the new last message 7711ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang // this happens to the second-to-last message when you discard a draft 7721ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang expandedState = ExpansionState.EXPANDED; 7731ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang } else { 7741ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang expandedState = savedExpanded; 7751ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang } 776839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang } else { 777cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang // new messages that are not expanded default to being eligible for super-collapse 77857f82d24a7f7105800eff71083e41db5b27ba183Andrew Sapperstein if (msg.starred || !msg.read || messageCursor.isLast()) { 779605dcfcefab6b222db6178f9c64a9d7a1c464da9Andrew Sapperstein expandedState = ExpansionState.EXPANDED; 780605dcfcefab6b222db6178f9c64a9d7a1c464da9Andrew Sapperstein } else if (messageCursor.isFirst()) { 781605dcfcefab6b222db6178f9c64a9d7a1c464da9Andrew Sapperstein expandedState = ExpansionState.COLLAPSED; 782605dcfcefab6b222db6178f9c64a9d7a1c464da9Andrew Sapperstein } else { 783605dcfcefab6b222db6178f9c64a9d7a1c464da9Andrew Sapperstein expandedState = ExpansionState.SUPER_COLLAPSED; 784735a22a197215ec4787ad9f3cbaf465cce54f4d0Andrew Sapperstein hasDraft |= msg.isDraft(); 785605dcfcefab6b222db6178f9c64a9d7a1c464da9Andrew Sapperstein } 786839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang } 787202738427ede23dd5863583aa3df17e4abfbf5e2Scott Kennedy mViewState.setShouldShowImages(msg, prevState.getShouldShowImages(msg)); 78808098ec4c894d9a15dfe800ad2397494e7e0a79aPaul Westbrook mViewState.setExpansionState(msg, expandedState); 789839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang 790839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang // save off "read" state from the cursor 791839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang // later, the view may not match the cursor (e.g. conversation marked read on open) 792423bea25992492efea7d414819729f9eae7ce72eAndy Huang // however, if a previous state indicated this message was unread, trust that instead 793423bea25992492efea7d414819729f9eae7ce72eAndy Huang // so "mark unread" marks all originally unread messages 794423bea25992492efea7d414819729f9eae7ce72eAndy Huang mViewState.setReadState(msg, msg.read && !prevState.isUnread(msg)); 795c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang 796cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang // We only want to consider this for inclusion in the super collapsed block if 797cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang // 1) The we don't have previous state about this message (The first time that the 798cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang // user opens a conversation) 799cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang // 2) The previously saved state for this message indicates that this message is 800cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang // in the super collapsed block. 801cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang if (ExpansionState.isSuperCollapsed(expandedState)) { 802cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang // contribute to a super-collapsed block that will be emitted just before the 803cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang // next expanded header 804cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang if (collapsedStart < 0) { 805cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang collapsedStart = pos; 80646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 807cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang prevCollapsedMsg = msg; 808cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang prevSafeForImages = safeForImages; 8097dc7fa00a6bea16b1142dc4e9ef8aabb73747fb7Andrew Sapperstein 8107dc7fa00a6bea16b1142dc4e9ef8aabb73747fb7Andrew Sapperstein // This line puts the from address in the address cache so that 8117dc7fa00a6bea16b1142dc4e9ef8aabb73747fb7Andrew Sapperstein // we get the sender image for it if it's in a super-collapsed block. 8127dc7fa00a6bea16b1142dc4e9ef8aabb73747fb7Andrew Sapperstein getAddress(msg.getFrom()); 813cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang continue; 81446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 8157bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 81646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang // resolve any deferred decisions on previous collapsed items 81746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang if (collapsedStart >= 0) { 81846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang if (pos - collapsedStart == 1) { 819cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein // Special-case for a single collapsed message: no need to super-collapse it. 820e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein renderMessage(prevCollapsedMsg, false /* expanded */, prevSafeForImages); 82146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } else { 822735a22a197215ec4787ad9f3cbaf465cce54f4d0Andrew Sapperstein renderSuperCollapsedBlock(collapsedStart, pos - 1, hasDraft); 82346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 824735a22a197215ec4787ad9f3cbaf465cce54f4d0Andrew Sapperstein hasDraft = false; // reset hasDraft 82546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang prevCollapsedMsg = null; 82646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang collapsedStart = -1; 82746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 8282405528bb15245f594277fe7b6efacd6a18ce69eAndy Huang 829e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein renderMessage(msg, ExpansionState.isExpanded(expandedState), safeForImages); 830f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang } 8313233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang 832e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein final MessageHeaderItem lastHeaderItem = getLastMessageHeaderItem(); 833e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein final int convFooterPos = mAdapter.addConversationFooter(lastHeaderItem); 834e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein final int convFooterPx = measureOverlayHeight(convFooterPos); 835e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein 8363233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang mWebView.getSettings().setBlockNetworkImage(!allowNetworkImages); 8373233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang 8382fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein final boolean applyTransforms = shouldApplyTransforms(); 83957f354c02dbf56ebc893a570564906e61c31e09eAndy Huang 840c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang // If the conversation has specified a base uri, use it here, otherwise use mBaseUri 8412b7cf14bbed4893981243fc13f79d1d66e2e1181Andrew Sapperstein return mTemplates.endConversation(mWebView.screenPxToWebPx(convFooterPx), mBaseUri, 842e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein mConversation.getBaseUri(mBaseUri), 8432160d53e6ae0bfb797569d616e735e46c21522ffAndy Huang mWebView.getViewportWidth(), mWebView.getWidthInDp(mSideMarginPx), 8442160d53e6ae0bfb797569d616e735e46c21522ffAndy Huang enableContentReadySignal, isOverviewMode(mAccount), applyTransforms, 8452160d53e6ae0bfb797569d616e735e46c21522ffAndy Huang applyTransforms); 846f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang } 847f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 848e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein private MessageHeaderItem getLastMessageHeaderItem() { 849a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao int pos = mAdapter.getCount(); 850a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao while (--pos >= 0) { 851a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao final ConversationOverlayItem item = mAdapter.getItem(pos); 852a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao if (item instanceof MessageHeaderItem) { 853a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao return (MessageHeaderItem) item; 854a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao } 855e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein } 856a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao LogUtils.wtf(LOG_TAG, "No message header found"); 857a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao return null; 858e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein } 859e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein 860735a22a197215ec4787ad9f3cbaf465cce54f4d0Andrew Sapperstein private void renderSuperCollapsedBlock(int start, int end, boolean hasDraft) { 861735a22a197215ec4787ad9f3cbaf465cce54f4d0Andrew Sapperstein final int blockPos = mAdapter.addSuperCollapsedBlock(start, end, hasDraft); 86223014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang final int blockPx = measureOverlayHeight(blockPos); 86323014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang mTemplates.appendSuperCollapsedHtml(start, mWebView.screenPxToWebPx(blockPx)); 86446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 86546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 866e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein private void renderMessage(ConversationMessage msg, boolean expanded, boolean safeForImages) { 86714f937408fe2451a91b44d3cd7d141347e716775Andrew Sapperstein 868202738427ede23dd5863583aa3df17e4abfbf5e2Scott Kennedy final int headerPos = mAdapter.addMessageHeader(msg, expanded, 869202738427ede23dd5863583aa3df17e4abfbf5e2Scott Kennedy mViewState.getShouldShowImages(msg)); 87046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang final MessageHeaderItem headerItem = (MessageHeaderItem) mAdapter.getItem(headerPos); 87146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 87246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang final int footerPos = mAdapter.addMessageFooter(headerItem); 87346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 87446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang // Measure item header and footer heights to allocate spacers in HTML 87546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang // But since the views themselves don't exist yet, render each item temporarily into 87646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang // a host view for measurement. 87723014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang final int headerPx = measureOverlayHeight(headerPos); 87823014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang final int footerPx = measureOverlayHeight(footerPos); 87946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 880256b35c0a8287f48c28e0d1ba3fae65790063295Andy Huang mTemplates.appendMessageHtml(msg, expanded, safeForImages, 88123014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang mWebView.screenPxToWebPx(headerPx), mWebView.screenPxToWebPx(footerPx)); 882243c23618b066bcdcd0ab9e36d8c01f50db2cbd0Andy Huang timerMark("rendered message"); 88346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 88446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 88546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang private String renderCollapsedHeaders(MessageCursor cursor, 88646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang SuperCollapsedBlockItem blockToReplace) { 88746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang final List<ConversationOverlayItem> replacements = Lists.newArrayList(); 88846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 88946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang mTemplates.reset(); 89046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 891f323c046034b4658a80438575d8e9f01d92e57e6Alice Yang final boolean alwaysShowImages = (mAccount != null) && 892f323c046034b4658a80438575d8e9f01d92e57e6Alice Yang (mAccount.settings.showImages == Settings.ShowImages.ALWAYS); 893e8221483ce5f013f02a4ef63d6682cc36313cda7Andrew Sapperstein 8942b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei // In devices with non-integral density multiplier, screen pixels translate to non-integral 8952b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei // web pixels. Keep track of the error that occurs when we cast all heights to int 8962b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei float error = 0f; 89714f937408fe2451a91b44d3cd7d141347e716775Andrew Sapperstein boolean first = true; 89846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang for (int i = blockToReplace.getStart(), end = blockToReplace.getEnd(); i <= end; i++) { 89946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang cursor.moveToPosition(i); 900839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang final ConversationMessage msg = cursor.getMessage(); 90114f937408fe2451a91b44d3cd7d141347e716775Andrew Sapperstein 9024ddda2f0a4ee5381a90779a6939b05b064ce5d11Andrew Sapperstein final MessageHeaderItem header = ConversationViewAdapter.newMessageHeaderItem( 90314f937408fe2451a91b44d3cd7d141347e716775Andrew Sapperstein mAdapter, mAdapter.getDateBuilder(), msg, false /* expanded */, 904e8221483ce5f013f02a4ef63d6682cc36313cda7Andrew Sapperstein alwaysShowImages || mViewState.getShouldShowImages(msg)); 905381c322eb30c39f63a2bb82812d63262eb3c1c1cAndrew Sapperstein final MessageFooterItem footer = mAdapter.newMessageFooterItem(mAdapter, header); 90646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 90723014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang final int headerPx = measureOverlayHeight(header); 90823014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang final int footerPx = measureOverlayHeight(footer); 9092b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei error += mWebView.screenPxToWebPxError(headerPx) 91059ccec3db4710f2aea6a4a9a30160ad19331367dAndrew Sapperstein + mWebView.screenPxToWebPxError(footerPx); 9112b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei 9122b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei // When the error becomes greater than 1 pixel, make the next header 1 pixel taller 9132b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei int correction = 0; 9142b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei if (error >= 1) { 9152b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei correction = 1; 9162b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei error -= 1; 9172b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei } 91846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 919e8221483ce5f013f02a4ef63d6682cc36313cda7Andrew Sapperstein mTemplates.appendMessageHtml(msg, false /* expanded */, 920e8221483ce5f013f02a4ef63d6682cc36313cda7Andrew Sapperstein alwaysShowImages || msg.alwaysShowImages, 9212b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei mWebView.screenPxToWebPx(headerPx) + correction, 9222b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei mWebView.screenPxToWebPx(footerPx)); 92346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang replacements.add(header); 92446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang replacements.add(footer); 925839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang 92608098ec4c894d9a15dfe800ad2397494e7e0a79aPaul Westbrook mViewState.setExpansionState(msg, ExpansionState.COLLAPSED); 92746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 92846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 92946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang mAdapter.replaceSuperCollapsedBlock(blockToReplace, replacements); 93006c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang mAdapter.notifyDataSetChanged(); 93146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 93246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang return mTemplates.emit(); 93346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 93446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 9359f957f3463fd149d33c209409e2bba500b539177Andrew Sapperstein protected int measureOverlayHeight(int position) { 93646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang return measureOverlayHeight(mAdapter.getItem(position)); 93746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 93846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 9397bdc3750454efe59617b7df945eadd7e59bee954Andy Huang /** 940b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang * Measure the height of an adapter view by rendering an adapter item into a temporary 94146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang * host view, and asking the view to immediately measure itself. This method will reuse 9427bdc3750454efe59617b7df945eadd7e59bee954Andy Huang * a previous adapter view from {@link ConversationContainer}'s scrap views if one was generated 9437bdc3750454efe59617b7df945eadd7e59bee954Andy Huang * earlier. 9447bdc3750454efe59617b7df945eadd7e59bee954Andy Huang * <p> 94546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang * After measuring the height, this method also saves the height in the 94646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang * {@link ConversationOverlayItem} for later use in overlay positioning. 9477bdc3750454efe59617b7df945eadd7e59bee954Andy Huang * 94846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang * @param convItem adapter item with data to render and measure 94923014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang * @return height of the rendered view in screen px 9507bdc3750454efe59617b7df945eadd7e59bee954Andy Huang */ 951afaab1752ab5b507cdaad7b3619ffc1c9728368fAndrew Sapperstein private int measureOverlayHeight(ConversationOverlayItem convItem) { 9527bdc3750454efe59617b7df945eadd7e59bee954Andy Huang final int type = convItem.getType(); 9537bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 9547bdc3750454efe59617b7df945eadd7e59bee954Andy Huang final View convertView = mConversationContainer.getScrapView(type); 955b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang final View hostView = mAdapter.getView(convItem, convertView, mConversationContainer, 956b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang true /* measureOnly */); 9577bdc3750454efe59617b7df945eadd7e59bee954Andy Huang if (convertView == null) { 9587bdc3750454efe59617b7df945eadd7e59bee954Andy Huang mConversationContainer.addScrapView(type, hostView); 9597bdc3750454efe59617b7df945eadd7e59bee954Andy Huang } 9607bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 9619875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang final int heightPx = mConversationContainer.measureOverlay(hostView); 9627bdc3750454efe59617b7df945eadd7e59bee954Andy Huang convItem.setHeight(heightPx); 9639875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang convItem.markMeasurementValid(); 9647bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 96523014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang return heightPx; 9667bdc3750454efe59617b7df945eadd7e59bee954Andy Huang } 9677bdc3750454efe59617b7df945eadd7e59bee954Andy Huang 9685ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang @Override 9695ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang public void onConversationViewHeaderHeightChange(int newHeight) { 970ab2d998506c83e82ddae25b6ba1419414e1e8122Mark Wei final int h = mWebView.screenPxToWebPx(newHeight); 971ab2d998506c83e82ddae25b6ba1419414e1e8122Mark Wei 972ab2d998506c83e82ddae25b6ba1419414e1e8122Mark Wei mWebView.loadUrl(String.format("javascript:setConversationHeaderSpacerHeight(%s);", h)); 9735ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang } 9745ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang 9753233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang // END conversation header callbacks 9763233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang 977735a22a197215ec4787ad9f3cbaf465cce54f4d0Andrew Sapperstein // START conversation footer callbacks 978735a22a197215ec4787ad9f3cbaf465cce54f4d0Andrew Sapperstein 979735a22a197215ec4787ad9f3cbaf465cce54f4d0Andrew Sapperstein @Override 980735a22a197215ec4787ad9f3cbaf465cce54f4d0Andrew Sapperstein public void onConversationFooterHeightChange(int newHeight) { 981735a22a197215ec4787ad9f3cbaf465cce54f4d0Andrew Sapperstein final int h = mWebView.screenPxToWebPx(newHeight); 982735a22a197215ec4787ad9f3cbaf465cce54f4d0Andrew Sapperstein 983735a22a197215ec4787ad9f3cbaf465cce54f4d0Andrew Sapperstein mWebView.loadUrl(String.format("javascript:setConversationFooterSpacerHeight(%s);", h)); 984735a22a197215ec4787ad9f3cbaf465cce54f4d0Andrew Sapperstein } 985735a22a197215ec4787ad9f3cbaf465cce54f4d0Andrew Sapperstein 986735a22a197215ec4787ad9f3cbaf465cce54f4d0Andrew Sapperstein // END conversation footer callbacks 987735a22a197215ec4787ad9f3cbaf465cce54f4d0Andrew Sapperstein 9883233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang // START message header callbacks 9893233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang @Override 990c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang public void setMessageSpacerHeight(MessageHeaderItem item, int newSpacerHeightPx) { 991c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang mConversationContainer.invalidateSpacerGeometry(); 992c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang 993c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang // update message HTML spacer height 99423014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang final int h = mWebView.screenPxToWebPx(newSpacerHeightPx); 99523014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang LogUtils.i(LAYOUT_TAG, "setting HTML spacer h=%dwebPx (%dscreenPx)", h, 99623014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang newSpacerHeightPx); 9975349ce14695dc98615108eb26f96806921b0abbaVikram Aggarwal mWebView.loadUrl(String.format("javascript:setMessageHeaderSpacerHeight('%s', %s);", 998014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang mTemplates.getMessageDomId(item.getMessage()), h)); 9993233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang } 10003233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang 10013233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang @Override 100259ccec3db4710f2aea6a4a9a30160ad19331367dAndrew Sapperstein public void setMessageExpanded(MessageHeaderItem item, int newSpacerHeightPx) { 1003c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang mConversationContainer.invalidateSpacerGeometry(); 1004c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang 1005c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang // show/hide the HTML message body and update the spacer height 100623014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang final int h = mWebView.screenPxToWebPx(newSpacerHeightPx); 100723014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang LogUtils.i(LAYOUT_TAG, "setting HTML spacer expanded=%s h=%dwebPx (%dscreenPx)", 100823014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang item.isExpanded(), h, newSpacerHeightPx); 100959ccec3db4710f2aea6a4a9a30160ad19331367dAndrew Sapperstein mWebView.loadUrl(String.format("javascript:setMessageBodyVisible('%s', %s, %s);", 101059ccec3db4710f2aea6a4a9a30160ad19331367dAndrew Sapperstein mTemplates.getMessageDomId(item.getMessage()), item.isExpanded(), h)); 1011839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang 1012014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang mViewState.setExpansionState(item.getMessage(), 101308098ec4c894d9a15dfe800ad2397494e7e0a79aPaul Westbrook item.isExpanded() ? ExpansionState.EXPANDED : ExpansionState.COLLAPSED); 10143233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang } 10153233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang 10163233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang @Override 1017eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy public void showExternalResources(final Message msg) { 1018202738427ede23dd5863583aa3df17e4abfbf5e2Scott Kennedy mViewState.setShouldShowImages(msg, true); 10193233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang mWebView.getSettings().setBlockNetworkImage(false); 1020eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy mWebView.loadUrl("javascript:unblockImages(['" + mTemplates.getMessageDomId(msg) + "']);"); 1021eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy } 1022eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy 1023eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy @Override 1024eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy public void showExternalResources(final String senderRawAddress) { 1025eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy mWebView.getSettings().setBlockNetworkImage(false); 1026eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy 1027eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy final Address sender = getAddress(senderRawAddress); 1028fa674294e4ccfae7bd1317d87065b842bdc46181Tony Mantler if (sender == null) { 1029fa674294e4ccfae7bd1317d87065b842bdc46181Tony Mantler // Don't need to unblock any images 1030fa674294e4ccfae7bd1317d87065b842bdc46181Tony Mantler return; 1031fa674294e4ccfae7bd1317d87065b842bdc46181Tony Mantler } 1032eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy final MessageCursor cursor = getMessageCursor(); 1033eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy 1034fa674294e4ccfae7bd1317d87065b842bdc46181Tony Mantler final List<String> messageDomIds = new ArrayList<>(); 1035eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy 1036eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy int pos = -1; 1037eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy while (cursor.moveToPosition(++pos)) { 1038eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy final ConversationMessage message = cursor.getMessage(); 1039eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy if (sender.equals(getAddress(message.getFrom()))) { 1040eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy message.alwaysShowImages = true; 1041eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy 1042eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy mViewState.setShouldShowImages(message, true); 1043eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy messageDomIds.add(mTemplates.getMessageDomId(message)); 1044eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy } 1045eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy } 1046eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy 1047eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy final String url = String.format( 1048eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy "javascript:unblockImages(['%s']);", TextUtils.join("','", messageDomIds)); 1049eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy mWebView.loadUrl(url); 10503233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang } 10511ebc2db723ed29093d724eb5da906a496ee57224Alice Yang 10521ebc2db723ed29093d724eb5da906a496ee57224Alice Yang @Override 105375b52a50a7b8382d9046d48ea8cda97b5471cbc8Andy Huang public boolean supportsMessageTransforms() { 105475b52a50a7b8382d9046d48ea8cda97b5471cbc8Andy Huang return true; 105575b52a50a7b8382d9046d48ea8cda97b5471cbc8Andy Huang } 105675b52a50a7b8382d9046d48ea8cda97b5471cbc8Andy Huang 105775b52a50a7b8382d9046d48ea8cda97b5471cbc8Andy Huang @Override 10581ebc2db723ed29093d724eb5da906a496ee57224Alice Yang public String getMessageTransforms(final Message msg) { 10591ebc2db723ed29093d724eb5da906a496ee57224Alice Yang final String domId = mTemplates.getMessageDomId(msg); 10601ebc2db723ed29093d724eb5da906a496ee57224Alice Yang return (domId == null) ? null : mMessageTransforms.get(domId); 10611ebc2db723ed29093d724eb5da906a496ee57224Alice Yang } 10621ebc2db723ed29093d724eb5da906a496ee57224Alice Yang 10638e1ffbf042a23824a97a9f47cfc81cf6f14603beJames Lemieux @Override 10648e1ffbf042a23824a97a9f47cfc81cf6f14603beJames Lemieux public boolean isSecure() { 10658e1ffbf042a23824a97a9f47cfc81cf6f14603beJames Lemieux return false; 10668e1ffbf042a23824a97a9f47cfc81cf6f14603beJames Lemieux } 10678e1ffbf042a23824a97a9f47cfc81cf6f14603beJames Lemieux 10683233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang // END message header callbacks 10695ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang 107046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang @Override 10712fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein public void showUntransformedConversation() { 10722fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein super.showUntransformedConversation(); 10738c601e4ff01a6443d8fa6504956d0734b75778d1Andrew Sapperstein final MessageCursor cursor = getMessageCursor(); 10748c601e4ff01a6443d8fa6504956d0734b75778d1Andrew Sapperstein if (cursor != null) { 10758c601e4ff01a6443d8fa6504956d0734b75778d1Andrew Sapperstein renderConversation(cursor); 10768c601e4ff01a6443d8fa6504956d0734b75778d1Andrew Sapperstein } 10772fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein } 10782fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein 10792fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein @Override 108046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang public void onSuperCollapsedClick(SuperCollapsedBlockItem item) { 1081f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp MessageCursor cursor = getMessageCursor(); 1082f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp if (cursor == null || !mViewsCreated) { 108346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang return; 108446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 108546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 1086f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp mTempBodiesHtml = renderCollapsedHeaders(cursor, item); 108746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang mWebView.loadUrl("javascript:replaceSuperCollapsedBlock(" + item.getStart() + ")"); 10887ee90b37599cb6717782d40a6d085849918c29dfJin Cao mConversationContainer.focusFirstMessageHeader(); 108946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 109046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 109147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang private void showNewMessageNotification(NewMessagesInfo info) { 10920ec03e8d930a58c9138b5ebe84468f99c332c504James Lemieux mNewMessageBar.show(mNewMessageBarActionListener, info.getNotificationText(), R.string.show, 10930ec03e8d930a58c9138b5ebe84468f99c332c504James Lemieux true /* replaceVisibleToast */, false /* autohide */, null /* ToastBarOperation */); 109447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang } 109547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 109647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang private void onNewMessageBarClick() { 10970ec03e8d930a58c9138b5ebe84468f99c332c504James Lemieux mNewMessageBar.hide(true, true); 109847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 1099f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp renderConversation(getMessageCursor()); // mCursor is already up-to-date 1100f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp // per onLoadFinished() 11015fbda023f1b0570e192e03a33834244a05edf200Andy Huang } 11025fbda023f1b0570e192e03a33834244a05edf200Andy Huang 1103e2b2eb7ef18ab1ba0260eed4a22930cea195c240Andrew Sapperstein private static OverlayPosition[] parsePositions(final int[] topArray, final int[] bottomArray) { 1104adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang final int len = topArray.length; 1105adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang final OverlayPosition[] positions = new OverlayPosition[len]; 1106b5078b287b1cec38817e342ff054ea901d199329Andy Huang for (int i = 0; i < len; i++) { 1107e2b2eb7ef18ab1ba0260eed4a22930cea195c240Andrew Sapperstein positions[i] = new OverlayPosition(topArray[i], bottomArray[i]); 1108b5078b287b1cec38817e342ff054ea901d199329Andy Huang } 1109adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang return positions; 1110b5078b287b1cec38817e342ff054ea901d199329Andy Huang } 1111b5078b287b1cec38817e342ff054ea901d199329Andy Huang 1112fa674294e4ccfae7bd1317d87065b842bdc46181Tony Mantler protected @Nullable Address getAddress(String rawFrom) { 11130dfae6942a834f32f031c466017539d43d06e466Paul Westbrook return Utils.getAddress(mAddressCache, rawFrom); 11141617481de7c1f9b8dfcd25ba9828e933cf9d5490Andy Huang } 11151617481de7c1f9b8dfcd25ba9828e933cf9d5490Andy Huang 11169d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang private void ensureContentSizeChangeListener() { 11179d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang if (mWebViewSizeChangeListener == null) { 1118c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang mWebViewSizeChangeListener = new ContentSizeChangeListener() { 11199d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang @Override 11209d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang public void onHeightChange(int h) { 11219d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang // When WebKit says the DOM height has changed, re-measure 11229d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang // bodies and re-position their headers. 11239d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang // This is separate from the typical JavaScript DOM change 11249d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang // listeners because cases like NARROW_COLUMNS text reflow do not trigger DOM 11259d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang // events. 11269d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang mWebView.loadUrl("javascript:measurePositions();"); 11279d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang } 11289d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang }; 11299d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang } 11309d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang mWebView.setContentSizeChangeListener(mWebViewSizeChangeListener); 11319d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang } 11329d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang 11339f957f3463fd149d33c209409e2bba500b539177Andrew Sapperstein public static boolean isOverviewMode(Account acct) { 1134ccf6780d9c80070beca4ade6e4084c3fb719af51Andy Huang return acct.settings.isOverviewMode(); 1135adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang } 1136adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang 1137adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang private void setupOverviewMode() { 113802f9d18a54072db8d86c524f9c09e508092ddd7cAndy Huang // for now, overview mode means use the built-in WebView zoom and disable custom scale 113902f9d18a54072db8d86c524f9c09e508092ddd7cAndy Huang // gesture handling 1140adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang final boolean overviewMode = isOverviewMode(mAccount); 1141adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang final WebSettings settings = mWebView.getSettings(); 11424dc732387454eef3ee6d89f9fa393630eb6213f9Andy Huang final WebSettings.LayoutAlgorithm layout; 114306def56be1f03acd2e5706ecf207ab147ce6f5c4Andy Huang settings.setUseWideViewPort(overviewMode); 114457f354c02dbf56ebc893a570564906e61c31e09eAndy Huang settings.setSupportZoom(overviewMode); 114557f354c02dbf56ebc893a570564906e61c31e09eAndy Huang settings.setBuiltInZoomControls(overviewMode); 11464dc732387454eef3ee6d89f9fa393630eb6213f9Andy Huang settings.setLoadWithOverviewMode(overviewMode); 114757f354c02dbf56ebc893a570564906e61c31e09eAndy Huang if (overviewMode) { 114857f354c02dbf56ebc893a570564906e61c31e09eAndy Huang settings.setDisplayZoomControls(false); 11494dc732387454eef3ee6d89f9fa393630eb6213f9Andy Huang layout = WebSettings.LayoutAlgorithm.NORMAL; 11504dc732387454eef3ee6d89f9fa393630eb6213f9Andy Huang } else { 11514dc732387454eef3ee6d89f9fa393630eb6213f9Andy Huang layout = WebSettings.LayoutAlgorithm.NARROW_COLUMNS; 1152adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang } 11534dc732387454eef3ee6d89f9fa393630eb6213f9Andy Huang settings.setLayoutAlgorithm(layout); 1154adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang } 1155adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang 11563c6fd44f9ae0cf60248dc64ee74d46afed633c45Andy Huang @Override 1157833123d9c31b0b2dd23f7f74738c5bccf8a546d3Andrew Sapperstein public ConversationMessage getMessageForClickedUrl(String url) { 11583c6fd44f9ae0cf60248dc64ee74d46afed633c45Andy Huang final String domMessageId = mUrlToMessageIdMap.get(url); 11593c6fd44f9ae0cf60248dc64ee74d46afed633c45Andy Huang if (domMessageId == null) { 11603c6fd44f9ae0cf60248dc64ee74d46afed633c45Andy Huang return null; 11613c6fd44f9ae0cf60248dc64ee74d46afed633c45Andy Huang } 11623f6111cf87080fe7adba512c7b9dfad2e4cd8154James Lemieux final MessageCursor messageCursor = getMessageCursor(); 11633f6111cf87080fe7adba512c7b9dfad2e4cd8154James Lemieux if (messageCursor == null) { 11643f6111cf87080fe7adba512c7b9dfad2e4cd8154James Lemieux return null; 11653f6111cf87080fe7adba512c7b9dfad2e4cd8154James Lemieux } 11663c6fd44f9ae0cf60248dc64ee74d46afed633c45Andy Huang final String messageId = mTemplates.getMessageIdForDomId(domMessageId); 11673f6111cf87080fe7adba512c7b9dfad2e4cd8154James Lemieux return messageCursor.getMessageForId(Long.parseLong(messageId)); 11683c6fd44f9ae0cf60248dc64ee74d46afed633c45Andy Huang } 11693c6fd44f9ae0cf60248dc64ee74d46afed633c45Andy Huang 1170c966a8a65a75559f677574c2a53cb9d43490f04eJin Cao /** 1171c966a8a65a75559f677574c2a53cb9d43490f04eJin Cao * Determines if we should intercept the left/right key event generated by the hardware 1172c966a8a65a75559f677574c2a53cb9d43490f04eJin Cao * keyboard so the framework won't handle directional navigation for us. 1173c966a8a65a75559f677574c2a53cb9d43490f04eJin Cao */ 1174c966a8a65a75559f677574c2a53cb9d43490f04eJin Cao private boolean shouldInterceptLeftRightEvents(@IdRes int id, boolean isLeft, boolean isRight, 1175c966a8a65a75559f677574c2a53cb9d43490f04eJin Cao boolean twoPaneLand) { 1176c966a8a65a75559f677574c2a53cb9d43490f04eJin Cao return twoPaneLand && (id == R.id.conversation_topmost_overlay || 1177c966a8a65a75559f677574c2a53cb9d43490f04eJin Cao id == R.id.upper_header || 1178c966a8a65a75559f677574c2a53cb9d43490f04eJin Cao id == R.id.super_collapsed_block || 1179c966a8a65a75559f677574c2a53cb9d43490f04eJin Cao id == R.id.message_footer || 1180c966a8a65a75559f677574c2a53cb9d43490f04eJin Cao (id == R.id.overflow && isRight) || 1181c966a8a65a75559f677574c2a53cb9d43490f04eJin Cao (id == R.id.reply_button && isLeft) || 1182c966a8a65a75559f677574c2a53cb9d43490f04eJin Cao (id == R.id.forward_button && isRight)); 1183c966a8a65a75559f677574c2a53cb9d43490f04eJin Cao } 1184c966a8a65a75559f677574c2a53cb9d43490f04eJin Cao 1185c966a8a65a75559f677574c2a53cb9d43490f04eJin Cao /** 1186c966a8a65a75559f677574c2a53cb9d43490f04eJin Cao * Indicates if the direction with the provided id should navigate away from the conversation 1187c966a8a65a75559f677574c2a53cb9d43490f04eJin Cao * view. Note that this is only applicable in two-pane landscape mode. 1188c966a8a65a75559f677574c2a53cb9d43490f04eJin Cao */ 1189c966a8a65a75559f677574c2a53cb9d43490f04eJin Cao private boolean shouldNavigateAway(@IdRes int id, boolean isLeft, boolean twoPaneLand) { 1190c966a8a65a75559f677574c2a53cb9d43490f04eJin Cao return twoPaneLand && isLeft && (id == R.id.conversation_topmost_overlay || 1191c966a8a65a75559f677574c2a53cb9d43490f04eJin Cao id == R.id.upper_header || 1192c966a8a65a75559f677574c2a53cb9d43490f04eJin Cao id == R.id.super_collapsed_block || 1193c966a8a65a75559f677574c2a53cb9d43490f04eJin Cao id == R.id.message_footer || 1194c966a8a65a75559f677574c2a53cb9d43490f04eJin Cao id == R.id.reply_button); 1195c966a8a65a75559f677574c2a53cb9d43490f04eJin Cao } 1196c966a8a65a75559f677574c2a53cb9d43490f04eJin Cao 1197a7404589b03ac9dd0d07b3f7d0a1ec92ac9acb62Jin Cao @Override 1198a7404589b03ac9dd0d07b3f7d0a1ec92ac9acb62Jin Cao public boolean onKey(View view, int keyCode, KeyEvent keyEvent) { 11990b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) { 12000b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao mOriginalKeyedView = view; 12010b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao } 12020b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao 12030b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao if (mOriginalKeyedView != null) { 12040b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao final int id = mOriginalKeyedView.getId(); 12058da4dc852b131e7516ad1ab888e2d601d3f51bedJin Cao final boolean isRtl = ViewUtils.isViewRtl(mOriginalKeyedView); 12060b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao final boolean isActionUp = keyEvent.getAction() == KeyEvent.ACTION_UP; 12078da4dc852b131e7516ad1ab888e2d601d3f51bedJin Cao final boolean isStart = KeyboardUtils.isKeycodeDirectionStart(keyCode, isRtl); 12088da4dc852b131e7516ad1ab888e2d601d3f51bedJin Cao final boolean isEnd = KeyboardUtils.isKeycodeDirectionEnd(keyCode, isRtl); 12090b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao final boolean isUp = keyCode == KeyEvent.KEYCODE_DPAD_UP; 12100b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao final boolean isDown = keyCode == KeyEvent.KEYCODE_DPAD_DOWN; 12110b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao 12120b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao // First we run the event by the controller 12130b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao // We manually check if the view+direction combination should shift focus away from the 12140b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao // conversation view to the thread list in two-pane landscape mode. 12150b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao final boolean isTwoPaneLand = mNavigationController.isTwoPaneLandscape(); 121652a40d5900b66bb06e7df3a40e338760d69e8ce6Jin Cao final boolean navigateAway = shouldNavigateAway(id, isStart, isTwoPaneLand); 12170b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao if (mNavigationController.onInterceptKeyFromCV(keyCode, keyEvent, navigateAway)) { 12180b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao return true; 12190b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao } 12200b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao 12210b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao // If controller didn't handle the event, check directional interception. 122252a40d5900b66bb06e7df3a40e338760d69e8ce6Jin Cao if ((isStart || isEnd) && shouldInterceptLeftRightEvents( 122352a40d5900b66bb06e7df3a40e338760d69e8ce6Jin Cao id, isStart, isEnd, isTwoPaneLand)) { 12240b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao return true; 12250b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao } else if (isUp || isDown) { 12267ee90b37599cb6717782d40a6d085849918c29dfJin Cao // We don't do anything on up/down for overlay 12277ee90b37599cb6717782d40a6d085849918c29dfJin Cao if (id == R.id.conversation_topmost_overlay) { 12287ee90b37599cb6717782d40a6d085849918c29dfJin Cao return true; 12297ee90b37599cb6717782d40a6d085849918c29dfJin Cao } 12307ee90b37599cb6717782d40a6d085849918c29dfJin Cao 12310b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao // We manually handle up/down navigation through the overlay items because the 12320b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao // system's default isn't optimal for two-pane landscape since it's not a real list. 1233ea7a5dbc54ccaaf858e63177f1c1b66bdc361d54Jin Cao final View next = mConversationContainer.getNextOverlayView(mOriginalKeyedView, 1234ea7a5dbc54ccaaf858e63177f1c1b66bdc361d54Jin Cao isDown); 12350b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao if (next != null) { 1236ea7a5dbc54ccaaf858e63177f1c1b66bdc361d54Jin Cao focusAndScrollToView(next); 1237ea7a5dbc54ccaaf858e63177f1c1b66bdc361d54Jin Cao } else if (!isActionUp) { 1238ea7a5dbc54ccaaf858e63177f1c1b66bdc361d54Jin Cao // Scroll in the direction of the arrow if next view isn't found. 1239ea7a5dbc54ccaaf858e63177f1c1b66bdc361d54Jin Cao final int currentY = mWebView.getScrollY(); 1240ea7a5dbc54ccaaf858e63177f1c1b66bdc361d54Jin Cao if (isUp && currentY > 0) { 1241ea7a5dbc54ccaaf858e63177f1c1b66bdc361d54Jin Cao mWebView.scrollBy(0, 1242ea7a5dbc54ccaaf858e63177f1c1b66bdc361d54Jin Cao -Math.min(currentY, DEFAULT_VERTICAL_SCROLL_DISTANCE_PX)); 1243ea7a5dbc54ccaaf858e63177f1c1b66bdc361d54Jin Cao } else if (isDown) { 1244ea7a5dbc54ccaaf858e63177f1c1b66bdc361d54Jin Cao final int webviewEnd = (int) (mWebView.getContentHeight() * 1245ea7a5dbc54ccaaf858e63177f1c1b66bdc361d54Jin Cao mWebView.getScale()); 1246ea7a5dbc54ccaaf858e63177f1c1b66bdc361d54Jin Cao final int currentEnd = currentY + mWebView.getHeight(); 1247ea7a5dbc54ccaaf858e63177f1c1b66bdc361d54Jin Cao if (currentEnd < webviewEnd) { 1248ea7a5dbc54ccaaf858e63177f1c1b66bdc361d54Jin Cao mWebView.scrollBy(0, Math.min(webviewEnd - currentEnd, 1249ea7a5dbc54ccaaf858e63177f1c1b66bdc361d54Jin Cao DEFAULT_VERTICAL_SCROLL_DISTANCE_PX)); 12500b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao } 12510b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao } 12520b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao } 1253c966a8a65a75559f677574c2a53cb9d43490f04eJin Cao return true; 1254a7404589b03ac9dd0d07b3f7d0a1ec92ac9acb62Jin Cao } 12550b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao 12560b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao // Finally we handle the special keys 12570b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao if (keyCode == KeyEvent.KEYCODE_BACK && id != R.id.conversation_topmost_overlay) { 12580b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao if (isActionUp) { 12590b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao mTopmostOverlay.requestFocus(); 12600b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao } 12610b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao return true; 12620b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao } else if (keyCode == KeyEvent.KEYCODE_ENTER && 12630b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao id == R.id.conversation_topmost_overlay) { 12640b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao if (isActionUp) { 12657ee90b37599cb6717782d40a6d085849918c29dfJin Cao mWebView.scrollTo(0, 0); 1266c966a8a65a75559f677574c2a53cb9d43490f04eJin Cao mConversationContainer.focusFirstMessageHeader(); 12670b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao } 12680b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao return true; 1269a7404589b03ac9dd0d07b3f7d0a1ec92ac9acb62Jin Cao } 1270a7404589b03ac9dd0d07b3f7d0a1ec92ac9acb62Jin Cao } 1271a7404589b03ac9dd0d07b3f7d0a1ec92ac9acb62Jin Cao return false; 1272a7404589b03ac9dd0d07b3f7d0a1ec92ac9acb62Jin Cao } 1273a7404589b03ac9dd0d07b3f7d0a1ec92ac9acb62Jin Cao 1274ea7a5dbc54ccaaf858e63177f1c1b66bdc361d54Jin Cao private void focusAndScrollToView(View v) { 1275ea7a5dbc54ccaaf858e63177f1c1b66bdc361d54Jin Cao // Make sure that v is in view 1276ea7a5dbc54ccaaf858e63177f1c1b66bdc361d54Jin Cao final int[] coords = new int[2]; 1277ea7a5dbc54ccaaf858e63177f1c1b66bdc361d54Jin Cao v.getLocationOnScreen(coords); 1278ea7a5dbc54ccaaf858e63177f1c1b66bdc361d54Jin Cao final int bottom = coords[1] + v.getHeight(); 1279ea7a5dbc54ccaaf858e63177f1c1b66bdc361d54Jin Cao if (bottom > mMaxScreenHeight) { 1280ea7a5dbc54ccaaf858e63177f1c1b66bdc361d54Jin Cao mWebView.scrollBy(0, bottom - mMaxScreenHeight); 1281ea7a5dbc54ccaaf858e63177f1c1b66bdc361d54Jin Cao } else if (coords[1] < mTopOfVisibleScreen) { 1282ea7a5dbc54ccaaf858e63177f1c1b66bdc361d54Jin Cao mWebView.scrollBy(0, coords[1] - mTopOfVisibleScreen); 1283ea7a5dbc54ccaaf858e63177f1c1b66bdc361d54Jin Cao } 1284ea7a5dbc54ccaaf858e63177f1c1b66bdc361d54Jin Cao v.requestFocus(); 1285ea7a5dbc54ccaaf858e63177f1c1b66bdc361d54Jin Cao } 1286ea7a5dbc54ccaaf858e63177f1c1b66bdc361d54Jin Cao 1287b1d184d164303d83d342c60e4bffc732aa10ac2cAndrew Sapperstein public class ConversationWebViewClient extends AbstractConversationWebViewClient { 12884ddda2f0a4ee5381a90779a6939b05b064ce5d11Andrew Sapperstein public ConversationWebViewClient(Account account) { 12894ddda2f0a4ee5381a90779a6939b05b064ce5d11Andrew Sapperstein super(account); 1290376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein } 1291376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein 129217a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huang @Override 1293e1302c3bdc0db5e104d10e8ad5de4c76424fb45aJames Lemieux public WebResourceResponse shouldInterceptRequest(WebView view, String url) { 1294e1302c3bdc0db5e104d10e8ad5de4c76424fb45aJames Lemieux // try to locate the message associated with the url 1295e1302c3bdc0db5e104d10e8ad5de4c76424fb45aJames Lemieux final ConversationMessage cm = getMessageForClickedUrl(url); 1296e1302c3bdc0db5e104d10e8ad5de4c76424fb45aJames Lemieux if (cm != null) { 1297e1302c3bdc0db5e104d10e8ad5de4c76424fb45aJames Lemieux // try to load the url assuming it is a cid url 1298e1302c3bdc0db5e104d10e8ad5de4c76424fb45aJames Lemieux final Uri uri = Uri.parse(url); 1299e1302c3bdc0db5e104d10e8ad5de4c76424fb45aJames Lemieux final WebResourceResponse response = loadCIDUri(uri, cm); 1300e1302c3bdc0db5e104d10e8ad5de4c76424fb45aJames Lemieux if (response != null) { 1301e1302c3bdc0db5e104d10e8ad5de4c76424fb45aJames Lemieux return response; 1302e1302c3bdc0db5e104d10e8ad5de4c76424fb45aJames Lemieux } 1303e1302c3bdc0db5e104d10e8ad5de4c76424fb45aJames Lemieux } 1304e1302c3bdc0db5e104d10e8ad5de4c76424fb45aJames Lemieux 1305e1302c3bdc0db5e104d10e8ad5de4c76424fb45aJames Lemieux // otherwise, attempt the default handling 1306e1302c3bdc0db5e104d10e8ad5de4c76424fb45aJames Lemieux return super.shouldInterceptRequest(view, url); 1307e1302c3bdc0db5e104d10e8ad5de4c76424fb45aJames Lemieux } 1308e1302c3bdc0db5e104d10e8ad5de4c76424fb45aJames Lemieux 1309e1302c3bdc0db5e104d10e8ad5de4c76424fb45aJames Lemieux @Override 131017a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huang public void onPageFinished(WebView view, String url) { 13119a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang // Ignore unsafe calls made after a fragment is detached from an activity. 13129a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang // This method needs to, for example, get at the loader manager, which needs 13139a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang // the fragment to be added. 13149a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang if (!isAdded() || !mViewsCreated) { 1315006e13c4943d83ce3277d502f531236b903e94fdPaul Westbrook LogUtils.d(LOG_TAG, "ignoring CVF.onPageFinished, url=%s fragment=%s", url, 1316b95da853ffbff343c8ca11470de2f0047e0807a6Andy Huang ConversationViewFragment.this); 1317b95da853ffbff343c8ca11470de2f0047e0807a6Andy Huang return; 1318b95da853ffbff343c8ca11470de2f0047e0807a6Andy Huang } 1319b95da853ffbff343c8ca11470de2f0047e0807a6Andy Huang 1320006e13c4943d83ce3277d502f531236b903e94fdPaul Westbrook LogUtils.d(LOG_TAG, "IN CVF.onPageFinished, url=%s fragment=%s wv=%s t=%sms", url, 132130bcfe7b69f9d8e0cad2ee62dae9d3571cd0d88bAndy Huang ConversationViewFragment.this, view, 132263b3c6725d60386f564ab53e3ba6495f0c158d9bAndy Huang (SystemClock.uptimeMillis() - mWebViewLoadStartMs)); 1323632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang 13249d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang ensureContentSizeChangeListener(); 13259d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang 13263bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp if (!mEnableContentReadySignal) { 13277d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang revealConversation(); 13283bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp } 13299d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang 13309a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang final Set<String> emailAddresses = Sets.newHashSet(); 1331543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang final List<Address> cacheCopy; 1332543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang synchronized (mAddressCache) { 1333543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang cacheCopy = ImmutableList.copyOf(mAddressCache.values()); 1334543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang } 1335543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang for (Address addr : cacheCopy) { 13369a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang emailAddresses.add(addr.getAddress()); 1337b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang } 13384ddda2f0a4ee5381a90779a6939b05b064ce5d11Andrew Sapperstein final ContactLoaderCallbacks callbacks = getContactInfoSource(); 13394ddda2f0a4ee5381a90779a6939b05b064ce5d11Andrew Sapperstein callbacks.setSenders(emailAddresses); 13409a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang getLoaderManager().restartLoader(CONTACT_LOADER, Bundle.EMPTY, callbacks); 134117a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huang } 134217a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huang 1343af5d4e0e2a5ce3300fffa0c484431d83adb89ee8Andy Huang @Override 1344af5d4e0e2a5ce3300fffa0c484431d83adb89ee8Andy Huang public boolean shouldOverrideUrlLoading(WebView view, String url) { 1345542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook return mViewsCreated && super.shouldOverrideUrlLoading(view, url); 1346af5d4e0e2a5ce3300fffa0c484431d83adb89ee8Andy Huang } 134717a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huang } 134817a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huang 1349f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang /** 1350f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang * NOTE: all public methods must be listed in the proguard flags so that they can be accessed 1351f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang * via reflection and not stripped. 1352f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang * 1353f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang */ 1354f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang private class MailJsBridge { 1355974c966c14000d42f089e2021b098d5cf61f9245Mindy Pereira @JavascriptInterface 1356e2b2eb7ef18ab1ba0260eed4a22930cea195c240Andrew Sapperstein public void onWebContentGeometryChange(final int[] overlayTopStrs, 1357e2b2eb7ef18ab1ba0260eed4a22930cea195c240Andrew Sapperstein final int[] overlayBottomStrs) { 13588ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein try { 13598ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein getHandler().post(new FragmentRunnable("onWebContentGeometryChange", 13608ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein ConversationViewFragment.this) { 13618ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein @Override 13628ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein public void go() { 136346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang if (!mViewsCreated) { 13641b3cc47f54072105c161d6ed557550e0e149b8bbmindyp LogUtils.d(LOG_TAG, "ignoring webContentGeometryChange because views" 13651b3cc47f54072105c161d6ed557550e0e149b8bbmindyp + " are gone, %s", ConversationViewFragment.this); 136646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang return; 136746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 1368adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang mConversationContainer.onGeometryChange( 1369adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang parsePositions(overlayTopStrs, overlayBottomStrs)); 13701b3cc47f54072105c161d6ed557550e0e149b8bbmindyp if (mDiff != 0) { 13711b3cc47f54072105c161d6ed557550e0e149b8bbmindyp // SCROLL! 13721b3cc47f54072105c161d6ed557550e0e149b8bbmindyp int scale = (int) (mWebView.getScale() / mWebView.getInitialScale()); 13731b3cc47f54072105c161d6ed557550e0e149b8bbmindyp if (scale > 1) { 13741b3cc47f54072105c161d6ed557550e0e149b8bbmindyp mWebView.scrollBy(0, (mDiff * (scale - 1))); 13751b3cc47f54072105c161d6ed557550e0e149b8bbmindyp } 13761b3cc47f54072105c161d6ed557550e0e149b8bbmindyp mDiff = 0; 13771b3cc47f54072105c161d6ed557550e0e149b8bbmindyp } 137851067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang } 13798ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein }); 13808ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein } catch (Throwable t) { 13818ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein LogUtils.e(LOG_TAG, t, "Error in MailJsBridge.onWebContentGeometryChange"); 13828ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein } 138346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 138451067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang 1385974c966c14000d42f089e2021b098d5cf61f9245Mindy Pereira @JavascriptInterface 138646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang public String getTempMessageBodies() { 138746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang try { 138846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang if (!mViewsCreated) { 138946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang return ""; 1390f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang } 139146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang 139246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang final String s = mTempBodiesHtml; 139346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang mTempBodiesHtml = null; 139446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang return s; 139546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } catch (Throwable t) { 139646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang LogUtils.e(LOG_TAG, t, "Error in MailJsBridge.getTempMessageBodies"); 139746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang return ""; 139846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang } 1399f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang } 1400f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 1401014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang @JavascriptInterface 1402014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang public String getMessageBody(String domId) { 1403014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang try { 1404014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang final MessageCursor cursor = getMessageCursor(); 1405014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang if (!mViewsCreated || cursor == null) { 1406014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang return ""; 1407014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang } 1408014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang 1409014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang int pos = -1; 1410014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang while (cursor.moveToPosition(++pos)) { 1411014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang final ConversationMessage msg = cursor.getMessage(); 1412014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang if (TextUtils.equals(domId, mTemplates.getMessageDomId(msg))) { 1413986776bbd046c9569a4abb67501819bee61e7194Andy Huang return HtmlConversationTemplates.wrapMessageBody(msg.getBodyAsHtml()); 1414014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang } 1415014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang } 1416014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang 1417014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang return ""; 1418014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang 1419014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang } catch (Throwable t) { 1420014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang LogUtils.e(LOG_TAG, t, "Error in MailJsBridge.getMessageBody"); 1421014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang return ""; 1422014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang } 1423014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang } 1424014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang 1425974c966c14000d42f089e2021b098d5cf61f9245Mindy Pereira @JavascriptInterface 1426543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang public String getMessageSender(String domId) { 1427543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang try { 1428543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang final MessageCursor cursor = getMessageCursor(); 1429543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang if (!mViewsCreated || cursor == null) { 1430543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang return ""; 1431543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang } 1432543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang 1433543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang int pos = -1; 1434543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang while (cursor.moveToPosition(++pos)) { 1435543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang final ConversationMessage msg = cursor.getMessage(); 1436543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang if (TextUtils.equals(domId, mTemplates.getMessageDomId(msg))) { 1437fa674294e4ccfae7bd1317d87065b842bdc46181Tony Mantler final Address address = getAddress(msg.getFrom()); 1438fa674294e4ccfae7bd1317d87065b842bdc46181Tony Mantler if (address != null) { 1439fa674294e4ccfae7bd1317d87065b842bdc46181Tony Mantler return address.getAddress(); 1440fa674294e4ccfae7bd1317d87065b842bdc46181Tony Mantler } else { 1441fa674294e4ccfae7bd1317d87065b842bdc46181Tony Mantler // Fall through to return an empty string 1442fa674294e4ccfae7bd1317d87065b842bdc46181Tony Mantler break; 1443fa674294e4ccfae7bd1317d87065b842bdc46181Tony Mantler } 1444543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang } 1445543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang } 1446543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang 1447543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang return ""; 1448543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang 1449543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang } catch (Throwable t) { 1450543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang LogUtils.e(LOG_TAG, t, "Error in MailJsBridge.getMessageSender"); 1451543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang return ""; 1452543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang } 1453543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang } 1454543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang 1455543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang @JavascriptInterface 14563bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp public void onContentReady() { 14578ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein try { 14588ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein getHandler().post(new FragmentRunnable("onContentReady", 14598ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein ConversationViewFragment.this) { 14608ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein @Override 14618ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein public void go() { 14628ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein try { 14638ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein if (mWebViewLoadStartMs != 0) { 14648ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein LogUtils.i(LOG_TAG, "IN CVF.onContentReady, f=%s vis=%s t=%sms", 14658ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein ConversationViewFragment.this, 14668ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein isUserVisible(), 14678ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein (SystemClock.uptimeMillis() - mWebViewLoadStartMs)); 14688ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein } 14698ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein revealConversation(); 14708ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein } catch (Throwable t) { 14718ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein LogUtils.e(LOG_TAG, t, "Error in MailJsBridge.onContentReady"); 14728ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein // Still try to show the conversation. 14738ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein revealConversation(); 147463b3c6725d60386f564ab53e3ba6495f0c158d9bAndy Huang } 14753bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp } 14768ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein }); 14778ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein } catch (Throwable t) { 14788ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein LogUtils.e(LOG_TAG, t, "Error in MailJsBridge.onContentReady"); 14798ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein } 14803bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp } 1481e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang 1482e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang @JavascriptInterface 1483e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang public float getScrollYPercent() { 1484e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang try { 1485e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang return mWebViewYPercent; 1486e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang } catch (Throwable t) { 1487e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang LogUtils.e(LOG_TAG, t, "Error in MailJsBridge.getScrollYPercent"); 1488e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang return 0f; 1489e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang } 1490e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang } 149105c70c88e6b421bedce134799a0ea928dbf44cd5Andy Huang 149205c70c88e6b421bedce134799a0ea928dbf44cd5Andy Huang @JavascriptInterface 149305c70c88e6b421bedce134799a0ea928dbf44cd5Andy Huang public void onMessageTransform(String messageDomId, String transformText) { 1494ae92e15b48a00613e06ea4c6b274bd73a4e6ec40Andrew Sapperstein try { 1495ae92e15b48a00613e06ea4c6b274bd73a4e6ec40Andrew Sapperstein LogUtils.i(LOG_TAG, "TRANSFORM: (%s) %s", messageDomId, transformText); 1496ae92e15b48a00613e06ea4c6b274bd73a4e6ec40Andrew Sapperstein mMessageTransforms.put(messageDomId, transformText); 1497ae92e15b48a00613e06ea4c6b274bd73a4e6ec40Andrew Sapperstein onConversationTransformed(); 1498ae92e15b48a00613e06ea4c6b274bd73a4e6ec40Andrew Sapperstein } catch (Throwable t) { 1499ae92e15b48a00613e06ea4c6b274bd73a4e6ec40Andrew Sapperstein LogUtils.e(LOG_TAG, t, "Error in MailJsBridge.onMessageTransform"); 15008ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein } 15018ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein } 15028ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein 15038ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein @JavascriptInterface 15048ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein public void onInlineAttachmentsParsed(final String[] urls, final String[] messageIds) { 15058ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein try { 15068ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein getHandler().post(new FragmentRunnable("onInlineAttachmentsParsed", 15078ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein ConversationViewFragment.this) { 15088ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein @Override 15098ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein public void go() { 15108ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein try { 15118ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein for (int i = 0, size = urls.length; i < size; i++) { 15128ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein mUrlToMessageIdMap.put(urls[i], messageIds[i]); 15138ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein } 15148ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein } catch (ArrayIndexOutOfBoundsException e) { 15158ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein LogUtils.e(LOG_TAG, e, 15168ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein "Number of urls does not match number of message ids - %s:%s", 15178ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein urls.length, messageIds.length); 15188ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein } 15198ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein } 15208ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein }); 15218ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein } catch (Throwable t) { 15228ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein LogUtils.e(LOG_TAG, t, "Error in MailJsBridge.onInlineAttachmentsParsed"); 1523ae92e15b48a00613e06ea4c6b274bd73a4e6ec40Andrew Sapperstein } 152405c70c88e6b421bedce134799a0ea928dbf44cd5Andy Huang } 1525674afa42682908640167fc6109b76f6f843e6fbeMindy Pereira } 1526f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang 152747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang private class NewMessagesInfo { 152847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang int count; 152906c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang int countFromSelf; 153047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 153147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang /** 153247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang * Return the display text for the new message notification overlay. It will be formatted 153347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang * appropriately for a single new message vs. multiple new messages. 153447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang * 153547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang * @return display text 153647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang */ 153747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang public String getNotificationText() { 15380ec03e8d930a58c9138b5ebe84468f99c332c504James Lemieux return getResources().getQuantityString(R.plurals.new_incoming_messages, count, count); 153947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang } 154047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang } 154147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang 1542f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp @Override 1543c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook public void onMessageCursorLoadFinished(Loader<ObjectCursor<ConversationMessage>> loader, 1544c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook MessageCursor newCursor, MessageCursor oldCursor) { 1545f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp /* 1546f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * what kind of changes affect the MessageCursor? 1. new message(s) 2. 1547f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * read/unread state change 3. deleted message, either regular or draft 1548f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * 4. updated message, either from self or from others, updated in 1549f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * content or state or sender 5. star/unstar of message (technically 1550f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * similar to #1) 6. other label change Use MessageCursor.hashCode() to 1551f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp * sort out interesting vs. no-op cursor updates. 1552f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp */ 1553014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang 1554233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang if (oldCursor != null && !oldCursor.isClosed()) { 1555014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang final NewMessagesInfo info = getNewIncomingMessagesInfo(newCursor); 1556014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang 1557014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang if (info.count > 0) { 1558014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang // don't immediately render new incoming messages from other 1559014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang // senders 1560014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang // (to avoid a new message from losing the user's focus) 1561014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang LogUtils.i(LOG_TAG, "CONV RENDER: conversation updated" 15629d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang + ", holding cursor for new incoming message (%s)", this); 1563014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang showNewMessageNotification(info); 1564014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang return; 1565014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang } 1566014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang 156706c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang final int oldState = oldCursor.getStateHashCode(); 156806c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang final boolean changed = newCursor.getStateHashCode() != oldState; 1569233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang 1570014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang if (!changed) { 1571014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang final boolean processedInPlace = processInPlaceUpdates(newCursor, oldCursor); 1572014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang if (processedInPlace) { 15739d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang LogUtils.i(LOG_TAG, "CONV RENDER: processed update(s) in place (%s)", this); 15741ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang } else { 1575f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp LogUtils.i(LOG_TAG, "CONV RENDER: uninteresting update" 15769d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang + ", ignoring this conversation update (%s)", this); 15771ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang } 15781ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang return; 157906c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang } else if (info.countFromSelf == 1) { 158006c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang // Special-case the very common case of a new cursor that is the same as the old 158106c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang // one, except that there is a new message from yourself. This happens upon send. 158206c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang final boolean sameExceptNewLast = newCursor.getStateHashCode(1) == oldState; 158306c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang if (sameExceptNewLast) { 158406c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang LogUtils.i(LOG_TAG, "CONV RENDER: update is a single new message from self" 158506c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang + " (%s)", this); 158606c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang newCursor.moveToLast(); 158706c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang processNewOutgoingMessage(newCursor.getMessage()); 158806c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang return; 158906c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang } 15901ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang } 15916766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang // cursors are different, and not due to an incoming message. fall 15926766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang // through and render. 15936766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang LogUtils.i(LOG_TAG, "CONV RENDER: conversation updated" 15946766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang + ", but not due to incoming message. rendering. (%s)", this); 159506c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang 159606c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang if (DEBUG_DUMP_CURSOR_CONTENTS) { 159706c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang LogUtils.i(LOG_TAG, "old cursor: %s", oldCursor.getDebugDump()); 159806c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang LogUtils.i(LOG_TAG, "new cursor: %s", newCursor.getDebugDump()); 159906c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang } 16006766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang } else { 16016766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang LogUtils.i(LOG_TAG, "CONV RENDER: initial render. (%s)", this); 1602243c23618b066bcdcd0ab9e36d8c01f50db2cbd0Andy Huang timerMark("message cursor load finished"); 1603b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang } 1604b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang 1605606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein renderContent(newCursor); 1606606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein } 1607606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein 1608606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein protected void renderContent(MessageCursor messageCursor) { 16094071c2f73218ce75750345557bb31a9110737841Mark Wei // if layout hasn't happened, delay render 16104071c2f73218ce75750345557bb31a9110737841Mark Wei // This is needed in addition to the showConversation() delay to speed 16114071c2f73218ce75750345557bb31a9110737841Mark Wei // up rotation and restoration. 16124071c2f73218ce75750345557bb31a9110737841Mark Wei if (mConversationContainer.getWidth() == 0) { 16134071c2f73218ce75750345557bb31a9110737841Mark Wei mNeedRender = true; 16144071c2f73218ce75750345557bb31a9110737841Mark Wei mConversationContainer.addOnLayoutChangeListener(this); 16154071c2f73218ce75750345557bb31a9110737841Mark Wei } else { 1616606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein renderConversation(messageCursor); 16174071c2f73218ce75750345557bb31a9110737841Mark Wei } 1618b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang } 1619b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang 1620f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp private NewMessagesInfo getNewIncomingMessagesInfo(MessageCursor newCursor) { 1621f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp final NewMessagesInfo info = new NewMessagesInfo(); 1622b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang 1623f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp int pos = -1; 1624f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp while (newCursor.moveToPosition(++pos)) { 1625f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp final Message m = newCursor.getMessage(); 1626f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp if (!mViewState.contains(m)) { 1627f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp LogUtils.i(LOG_TAG, "conversation diff: found new msg: %s", m.uri); 1628f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp 16298960f0af431bc164003e09b3c8981aab808d9ec1Scott Kennedy final Address from = getAddress(m.getFrom()); 1630f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp // distinguish ours from theirs 1631f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp // new messages from the account owner should not trigger a 1632f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp // notification 16331353da4b3075af7d5bb8f6bcd0dcb5bb3f8afdabPaul Westbrook if (from == null || mAccount.ownsFromAddress(from.getAddress())) { 1634f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp LogUtils.i(LOG_TAG, "found message from self: %s", m.uri); 163506c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang info.countFromSelf++; 1636f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp continue; 1637f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp } 1638b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang 1639f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp info.count++; 1640b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang } 1641b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang } 1642f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp return info; 1643b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang } 1644b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang 1645014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang private boolean processInPlaceUpdates(MessageCursor newCursor, MessageCursor oldCursor) { 1646014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang final Set<String> idsOfChangedBodies = Sets.newHashSet(); 16476b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang final List<Integer> changedOverlayPositions = Lists.newArrayList(); 16486b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang 1649014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang boolean changed = false; 1650014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang 1651014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang int pos = 0; 1652014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang while (true) { 1653014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang if (!newCursor.moveToPosition(pos) || !oldCursor.moveToPosition(pos)) { 1654014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang break; 1655014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang } 1656014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang 1657014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang final ConversationMessage newMsg = newCursor.getMessage(); 1658014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang final ConversationMessage oldMsg = oldCursor.getMessage(); 1659014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang 16602ef5f08719a9981d73f7954ce82a78ea6842f187Jin Cao // We are going to update the data in the adapter whenever any input fields change. 16612ef5f08719a9981d73f7954ce82a78ea6842f187Jin Cao // This ensures that the Message object that ComposeActivity uses will be correctly 16622ef5f08719a9981d73f7954ce82a78ea6842f187Jin Cao // aligned with the most up-to-date data. 16639ac224f701c9ff8bf684c81c4c140cde6532f8d6Tony Mantler if (!newMsg.isEqual(oldMsg)) { 16646b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang mAdapter.updateItemsForMessage(newMsg, changedOverlayPositions); 16656a2df258316b267151296556dbbdba20200ecb1fJin Cao LogUtils.i(LOG_TAG, "msg #%d (%d): detected field(s) change. sendingState=%s", 16666a2df258316b267151296556dbbdba20200ecb1fJin Cao pos, newMsg.id, newMsg.sendingState); 1667014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang } 1668014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang 1669014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang // update changed message bodies in-place 1670014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang if (!TextUtils.equals(newMsg.bodyHtml, oldMsg.bodyHtml) || 1671014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang !TextUtils.equals(newMsg.bodyText, oldMsg.bodyText)) { 1672014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang // maybe just set a flag to notify JS to re-request changed bodies 1673014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang idsOfChangedBodies.add('"' + mTemplates.getMessageDomId(newMsg) + '"'); 1674014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang LogUtils.i(LOG_TAG, "msg #%d (%d): detected body change", pos, newMsg.id); 1675014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang } 1676014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang 1677014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang pos++; 1678014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang } 1679014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang 16806b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang 16816b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang if (!changedOverlayPositions.isEmpty()) { 168206c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang // notify once after the entire adapter is updated 16836b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang mConversationContainer.onOverlayModelUpdate(changedOverlayPositions); 16846b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang changed = true; 168506c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang } 168606c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang 1687735a22a197215ec4787ad9f3cbaf465cce54f4d0Andrew Sapperstein final ConversationFooterItem footerItem = mAdapter.getFooterItem(); 1688735a22a197215ec4787ad9f3cbaf465cce54f4d0Andrew Sapperstein if (footerItem != null) { 1689735a22a197215ec4787ad9f3cbaf465cce54f4d0Andrew Sapperstein footerItem.invalidateMeasurement(); 1690735a22a197215ec4787ad9f3cbaf465cce54f4d0Andrew Sapperstein } 1691014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang if (!idsOfChangedBodies.isEmpty()) { 1692014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang mWebView.loadUrl(String.format("javascript:replaceMessageBodies([%s]);", 1693014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang TextUtils.join(",", idsOfChangedBodies))); 1694014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang changed = true; 1695014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang } 1696014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang 1697014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang return changed; 1698014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang } 1699014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang 170006c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang private void processNewOutgoingMessage(ConversationMessage msg) { 1701e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein // Temporarily remove the ConversationFooterItem and its view. 1702e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein // It will get re-added right after the new message is added. 1703e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein final ConversationFooterItem footerItem = mAdapter.removeFooterItem(); 17049fe12e998ed5891ad460ac1e09d6c78d1d600bfeAndrew Sapperstein // if no footer, just skip the work for it. The rest should be fine to do. 170538689a8aa6ac84ca90027f206709cdb09f9b1e10Andrew Sapperstein if (footerItem != null) { 170638689a8aa6ac84ca90027f206709cdb09f9b1e10Andrew Sapperstein mConversationContainer.removeViewAtAdapterIndex(footerItem.getPosition()); 17071e191e34733699149a9e662fd2540ae4b6ec9b0dAndrew Sapperstein } else { 17081e191e34733699149a9e662fd2540ae4b6ec9b0dAndrew Sapperstein LogUtils.i(LOG_TAG, "footer item not found"); 170938689a8aa6ac84ca90027f206709cdb09f9b1e10Andrew Sapperstein } 17109fe12e998ed5891ad460ac1e09d6c78d1d600bfeAndrew Sapperstein 171106c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang mTemplates.reset(); 171206c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang // this method will add some items to mAdapter, but we deliberately want to avoid notifying 171306c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang // adapter listeners (i.e. ConversationContainer) until onWebContentGeometryChange is next 171406c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang // called, to prevent N+1 headers rendering with N message bodies. 1715e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein renderMessage(msg, true /* expanded */, msg.alwaysShowImages); 171606c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang mTempBodiesHtml = mTemplates.emit(); 171706c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang 1718e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein if (footerItem != null) { 1719e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein footerItem.setLastMessageHeaderItem(getLastMessageHeaderItem()); 1720735a22a197215ec4787ad9f3cbaf465cce54f4d0Andrew Sapperstein footerItem.invalidateMeasurement(); 1721e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein mAdapter.addItem(footerItem); 1722e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein } 1723e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein 172406c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang mViewState.setExpansionState(msg, ExpansionState.EXPANDED); 172506c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang // FIXME: should the provider set this as initial state? 172606c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang mViewState.setReadState(msg, false /* read */); 172706c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang 172891d782abc8015bd651fb5d0252b4d1ef369ec57bAndy Huang // From now until the updated spacer geometry is returned, the adapter items are mismatched 172991d782abc8015bd651fb5d0252b4d1ef369ec57bAndy Huang // with the existing spacers. Do not let them layout. 173091d782abc8015bd651fb5d0252b4d1ef369ec57bAndy Huang mConversationContainer.invalidateSpacerGeometry(); 173191d782abc8015bd651fb5d0252b4d1ef369ec57bAndy Huang 173206c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang mWebView.loadUrl("javascript:appendMessageHtml();"); 173306c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang } 173406c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang 1735f59d01c3116dc2adde97a5b52aa6094144c2d315Andrew Sapperstein private static class SetCookieTask extends AsyncTask<Void, Void, Void> { 1736f59d01c3116dc2adde97a5b52aa6094144c2d315Andrew Sapperstein private final Context mContext; 1737f59d01c3116dc2adde97a5b52aa6094144c2d315Andrew Sapperstein private final String mUri; 1738f59d01c3116dc2adde97a5b52aa6094144c2d315Andrew Sapperstein private final Uri mAccountCookieQueryUri; 1739f59d01c3116dc2adde97a5b52aa6094144c2d315Andrew Sapperstein private final ContentResolver mResolver; 1740cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook 1741f59d01c3116dc2adde97a5b52aa6094144c2d315Andrew Sapperstein /* package */ SetCookieTask(Context context, String baseUri, Uri accountCookieQueryUri) { 1742f59d01c3116dc2adde97a5b52aa6094144c2d315Andrew Sapperstein mContext = context; 1743f59d01c3116dc2adde97a5b52aa6094144c2d315Andrew Sapperstein mUri = baseUri; 1744b8361c9f8938b74977316319885998aae09aab77Paul Westbrook mAccountCookieQueryUri = accountCookieQueryUri; 1745b8361c9f8938b74977316319885998aae09aab77Paul Westbrook mResolver = context.getContentResolver(); 1746cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook } 1747cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook 1748cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook @Override 1749cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook public Void doInBackground(Void... args) { 1750f59d01c3116dc2adde97a5b52aa6094144c2d315Andrew Sapperstein // First query for the cookie string from the UI provider 1751b8361c9f8938b74977316319885998aae09aab77Paul Westbrook final Cursor cookieCursor = mResolver.query(mAccountCookieQueryUri, 1752b8361c9f8938b74977316319885998aae09aab77Paul Westbrook UIProvider.ACCOUNT_COOKIE_PROJECTION, null, null, null); 1753b8361c9f8938b74977316319885998aae09aab77Paul Westbrook if (cookieCursor == null) { 1754b8361c9f8938b74977316319885998aae09aab77Paul Westbrook return null; 1755b8361c9f8938b74977316319885998aae09aab77Paul Westbrook } 1756b8361c9f8938b74977316319885998aae09aab77Paul Westbrook 1757b8361c9f8938b74977316319885998aae09aab77Paul Westbrook try { 1758b8361c9f8938b74977316319885998aae09aab77Paul Westbrook if (cookieCursor.moveToFirst()) { 1759b8361c9f8938b74977316319885998aae09aab77Paul Westbrook final String cookie = cookieCursor.getString( 1760b8361c9f8938b74977316319885998aae09aab77Paul Westbrook cookieCursor.getColumnIndex(UIProvider.AccountCookieColumns.COOKIE)); 1761b8361c9f8938b74977316319885998aae09aab77Paul Westbrook 1762b8361c9f8938b74977316319885998aae09aab77Paul Westbrook if (cookie != null) { 1763b8361c9f8938b74977316319885998aae09aab77Paul Westbrook final CookieSyncManager csm = 1764f59d01c3116dc2adde97a5b52aa6094144c2d315Andrew Sapperstein CookieSyncManager.createInstance(mContext); 1765b8361c9f8938b74977316319885998aae09aab77Paul Westbrook CookieManager.getInstance().setCookie(mUri, cookie); 1766b8361c9f8938b74977316319885998aae09aab77Paul Westbrook csm.sync(); 1767b8361c9f8938b74977316319885998aae09aab77Paul Westbrook } 1768b8361c9f8938b74977316319885998aae09aab77Paul Westbrook } 1769b8361c9f8938b74977316319885998aae09aab77Paul Westbrook 1770b8361c9f8938b74977316319885998aae09aab77Paul Westbrook } finally { 1771b8361c9f8938b74977316319885998aae09aab77Paul Westbrook cookieCursor.close(); 1772b8361c9f8938b74977316319885998aae09aab77Paul Westbrook } 1773b8361c9f8938b74977316319885998aae09aab77Paul Westbrook 1774b8361c9f8938b74977316319885998aae09aab77Paul Westbrook 1775cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook return null; 1776cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook } 1777cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook } 177836280f3c2939bb2dacb4077bd528900346ff4bb9mindyp 177926d4d2d9c43c499f458808f050ec73ea3c28dec4mindyp @Override 178036280f3c2939bb2dacb4077bd528900346ff4bb9mindyp public void onConversationUpdated(Conversation conv) { 178136280f3c2939bb2dacb4077bd528900346ff4bb9mindyp final ConversationViewHeader headerView = (ConversationViewHeader) mConversationContainer 178236280f3c2939bb2dacb4077bd528900346ff4bb9mindyp .findViewById(R.id.conversation_header); 1783b2b98ba97983f225eec19dc9bc5333e6ef80ee15mindyp mConversation = conv; 17849e0b236be55cf9dab8f1670a9d91ed16e055a9d0mindyp if (headerView != null) { 17859e0b236be55cf9dab8f1670a9d91ed16e055a9d0mindyp headerView.onConversationUpdated(conv); 17869e0b236be55cf9dab8f1670a9d91ed16e055a9d0mindyp } 178736280f3c2939bb2dacb4077bd528900346ff4bb9mindyp } 17884071c2f73218ce75750345557bb31a9110737841Mark Wei 17894071c2f73218ce75750345557bb31a9110737841Mark Wei @Override 17904071c2f73218ce75750345557bb31a9110737841Mark Wei public void onLayoutChange(View v, int left, int top, int right, 17914071c2f73218ce75750345557bb31a9110737841Mark Wei int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { 17924071c2f73218ce75750345557bb31a9110737841Mark Wei boolean sizeChanged = mNeedRender 17934071c2f73218ce75750345557bb31a9110737841Mark Wei && mConversationContainer.getWidth() != 0; 17944071c2f73218ce75750345557bb31a9110737841Mark Wei if (sizeChanged) { 17954071c2f73218ce75750345557bb31a9110737841Mark Wei mNeedRender = false; 17964071c2f73218ce75750345557bb31a9110737841Mark Wei mConversationContainer.removeOnLayoutChangeListener(this); 17974071c2f73218ce75750345557bb31a9110737841Mark Wei renderConversation(getMessageCursor()); 17984071c2f73218ce75750345557bb31a9110737841Mark Wei } 17994071c2f73218ce75750345557bb31a9110737841Mark Wei } 18001b3cc47f54072105c161d6ed557550e0e149b8bbmindyp 18011b3cc47f54072105c161d6ed557550e0e149b8bbmindyp @Override 18027cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux public void setMessageDetailsExpanded(MessageHeaderItem i, boolean expanded, int heightBefore) { 18031b3cc47f54072105c161d6ed557550e0e149b8bbmindyp mDiff = (expanded ? 1 : -1) * Math.abs(i.getHeight() - heightBefore); 18041b3cc47f54072105c161d6ed557550e0e149b8bbmindyp } 180502f9d18a54072db8d86c524f9c09e508092ddd7cAndy Huang 18067cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux /** 18077cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux * @return {@code true} because either the Print or Print All menu item is shown in GMail 18087cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux */ 18097cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux @Override 18107cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux protected boolean shouldShowPrintInOverflow() { 18117cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux return true; 18127cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux } 18137cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux 18147cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux @Override 18155c1692a5faeab220881a17a3427a8986ef874403Andrew Sapperstein protected void printConversation() { 1816234d35352eeaaa8d3928e31028be49112607bd29Andrew Sapperstein PrintUtils.printConversation(mActivity.getActivityContext(), getMessageCursor(), 1817234d35352eeaaa8d3928e31028be49112607bd29Andrew Sapperstein mAddressCache, mConversation.getBaseUri(mBaseUri), true /* useJavascript */); 18185c1692a5faeab220881a17a3427a8986ef874403Andrew Sapperstein } 1819a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao 1820a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao @Override 1821a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao protected void handleReply() { 1822a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao final MessageHeaderItem item = getLastMessageHeaderItem(); 1823a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao if (item != null) { 1824a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao final ConversationMessage msg = item.getMessage(); 1825a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao if (msg != null) { 1826a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao ComposeActivity.reply(getActivity(), mAccount, msg); 1827a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao } 1828a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao } 1829a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao } 1830a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao 1831a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao @Override 1832a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao protected void handleReplyAll() { 1833a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao final MessageHeaderItem item = getLastMessageHeaderItem(); 1834a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao if (item != null) { 1835a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao final ConversationMessage msg = item.getMessage(); 1836a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao if (msg != null) { 1837a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao ComposeActivity.replyAll(getActivity(), mAccount, msg); 1838a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao } 1839a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao } 1840a6b45218b8d90d7fc3a5ca0901293c71df404553Jin Cao } 18419b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira} 1842