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