ConversationViewFragment.java revision 91fa034d624d8690905f30a13ae3ffb9601cf948
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;
313af481c20edd84419d0d4a8476994ac008c0b97aAndrew Sappersteinimport android.support.v4.text.BidiFormatter;
328ec43e877a9c1925514f066655984e21fbd255e8Andrew Sappersteinimport android.support.v4.util.ArrayMap;
3347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huangimport android.text.TextUtils;
34a7404589b03ac9dd0d07b3f7d0a1ec92ac9acb62Jin Caoimport android.view.KeyEvent;
359b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereiraimport android.view.LayoutInflater;
36c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huangimport android.view.View;
374071c2f73218ce75750345557bb31a9110737841Mark Weiimport android.view.View.OnLayoutChangeListener;
389b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereiraimport android.view.ViewGroup;
39f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport android.webkit.ConsoleMessage;
40cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrookimport android.webkit.CookieManager;
41cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrookimport android.webkit.CookieSyncManager;
42974c966c14000d42f089e2021b098d5cf61f9245Mindy Pereiraimport android.webkit.JavascriptInterface;
43f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport android.webkit.WebChromeClient;
44f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport android.webkit.WebSettings;
4517a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huangimport android.webkit.WebView;
46821fa87279d590e682effbdb652c8a92e805eec8Andrew Sappersteinimport android.widget.Button;
479b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira
48821e578a71c7015646522e729600618f0ec16fc0Tony Mantlerimport com.android.emailcommon.mail.Address;
4959e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huangimport com.android.mail.FormattedDateBuilder;
509b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereiraimport com.android.mail.R;
51e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huangimport com.android.mail.analytics.Analytics;
5272953f25e00ee8e7b5c7682148430dc82f00a77dJin Caoimport com.android.mail.analytics.AnalyticsTimer;
535ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huangimport com.android.mail.browse.ConversationContainer;
54adbf3e8cadb66666f307352b72537fbac57b916fAndy Huangimport com.android.mail.browse.ConversationContainer.OverlayPosition;
55735a22a197215ec4787ad9f3cbaf465cce54f4d0Andrew Sappersteinimport com.android.mail.browse.ConversationFooterView.ConversationFooterCallbacks;
568812d3c50e35c4f2a02d29c35c76082c4ebec0cdAndrew Sappersteinimport com.android.mail.browse.ConversationMessage;
5746dfba6160b55a582b344328067e3dafeb881dd9Andy Huangimport com.android.mail.browse.ConversationOverlayItem;
587bdc3750454efe59617b7df945eadd7e59bee954Andy Huangimport com.android.mail.browse.ConversationViewAdapter;
59e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sappersteinimport com.android.mail.browse.ConversationViewAdapter.ConversationFooterItem;
6046dfba6160b55a582b344328067e3dafeb881dd9Andy Huangimport com.android.mail.browse.ConversationViewAdapter.MessageFooterItem;
617bdc3750454efe59617b7df945eadd7e59bee954Andy Huangimport com.android.mail.browse.ConversationViewAdapter.MessageHeaderItem;
6246dfba6160b55a582b344328067e3dafeb881dd9Andy Huangimport com.android.mail.browse.ConversationViewAdapter.SuperCollapsedBlockItem;
635ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huangimport com.android.mail.browse.ConversationViewHeader;
645ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huangimport com.android.mail.browse.ConversationWebView;
658ec43e877a9c1925514f066655984e21fbd255e8Andrew Sappersteinimport com.android.mail.browse.InlineAttachmentViewIntentBuilderCreator;
668ec43e877a9c1925514f066655984e21fbd255e8Andrew Sappersteinimport com.android.mail.browse.InlineAttachmentViewIntentBuilderCreatorHolder;
67c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huangimport com.android.mail.browse.MailWebView.ContentSizeChangeListener;
687bdc3750454efe59617b7df945eadd7e59bee954Andy Huangimport com.android.mail.browse.MessageCursor;
69381c322eb30c39f63a2bb82812d63262eb3c1c1cAndrew Sappersteinimport com.android.mail.browse.MessageFooterView;
7059e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huangimport com.android.mail.browse.MessageHeaderView;
71adbf3e8cadb66666f307352b72537fbac57b916fAndy Huangimport com.android.mail.browse.ScrollIndicatorsView;
7246dfba6160b55a582b344328067e3dafeb881dd9Andy Huangimport com.android.mail.browse.SuperCollapsedBlock;
730b7ed6fae6e36f2abc4ca916764177d1879731b4Andy Huangimport com.android.mail.browse.WebViewContextMenu;
74c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrookimport com.android.mail.content.ObjectCursor;
75562c5ba7235948cf1d20a9afa40e67cd62f43cf7Andrew Sappersteinimport com.android.mail.print.PrintUtils;
769b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereiraimport com.android.mail.providers.Account;
779b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereiraimport com.android.mail.providers.Conversation;
78f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport com.android.mail.providers.Message;
79f323c046034b4658a80438575d8e9f01d92e57e6Alice Yangimport com.android.mail.providers.Settings;
80b8361c9f8938b74977316319885998aae09aab77Paul Westbrookimport com.android.mail.providers.UIProvider;
81cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huangimport com.android.mail.ui.ConversationViewState.ExpansionState;
82376294bbb5ded471ad577fdb492875a837021d08Andrew Sappersteinimport com.android.mail.utils.ConversationViewUtils;
83b334c9035e9b7a38766bb66c29da2208525d1e11Paul Westbrookimport com.android.mail.utils.LogTag;
849b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereiraimport com.android.mail.utils.LogUtils;
852e9acfe527426c00b5df2ca66189262dc40c8575Andy Huangimport com.android.mail.utils.Utils;
86543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huangimport com.google.common.collect.ImmutableList;
8746dfba6160b55a582b344328067e3dafeb881dd9Andy Huangimport com.google.common.collect.Lists;
8805c70c88e6b421bedce134799a0ea928dbf44cd5Andy Huangimport com.google.common.collect.Maps;
89b8331b4565566ca733997398e8c07a26cd2bee98Andy Huangimport com.google.common.collect.Sets;
9065fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huang
91eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedyimport java.util.ArrayList;
9246dfba6160b55a582b344328067e3dafeb881dd9Andy Huangimport java.util.List;
9305c70c88e6b421bedce134799a0ea928dbf44cd5Andy Huangimport java.util.Map;
94b8331b4565566ca733997398e8c07a26cd2bee98Andy Huangimport java.util.Set;
959b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira
969b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira/**
979b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira * The conversation view UI component.
989b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira */
999f957f3463fd149d33c209409e2bba500b539177Andrew Sappersteinpublic class ConversationViewFragment extends AbstractConversationViewFragment implements
1004ddda2f0a4ee5381a90779a6939b05b064ce5d11Andrew Sapperstein        SuperCollapsedBlock.OnClickListener, OnLayoutChangeListener,
101735a22a197215ec4787ad9f3cbaf465cce54f4d0Andrew Sapperstein        MessageHeaderView.MessageHeaderViewCallbacks, MessageFooterView.MessageFooterCallbacks,
102a7404589b03ac9dd0d07b3f7d0a1ec92ac9acb62Jin Cao        WebViewContextMenu.Callbacks, ConversationFooterCallbacks, View.OnKeyListener {
1038e915724b6e4374da9b70161ee0a55f0c763e563Mindy Pereira
104b334c9035e9b7a38766bb66c29da2208525d1e11Paul Westbrook    private static final String LOG_TAG = LogTag.getLogTag();
105632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang    public static final String LAYOUT_TAG = "ConvLayout";
1069b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira
1079d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    /**
1081b3cc47f54072105c161d6ed557550e0e149b8bbmindyp     * Difference in the height of the message header whose details have been expanded/collapsed
1091b3cc47f54072105c161d6ed557550e0e149b8bbmindyp     */
1101b3cc47f54072105c161d6ed557550e0e149b8bbmindyp    private int mDiff = 0;
1111b3cc47f54072105c161d6ed557550e0e149b8bbmindyp
1121b3cc47f54072105c161d6ed557550e0e149b8bbmindyp    /**
1139d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     * Default value for {@link #mLoadWaitReason}. Conversation load will happen immediately.
1149d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     */
1159d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    private final int LOAD_NOW = 0;
1169d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    /**
1179d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     * Value for {@link #mLoadWaitReason} that means we are offscreen and waiting for the visible
1189d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     * conversation to finish loading before beginning our load.
1199d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     * <p>
1209d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     * When this value is set, the fragment should register with {@link ConversationListCallbacks}
1219d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     * to know when the visible conversation is loaded. When it is unset, it should unregister.
1229d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     */
1239d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    private final int LOAD_WAIT_FOR_INITIAL_CONVERSATION = 1;
1249d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    /**
1259d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     * Value for {@link #mLoadWaitReason} used when a conversation is too heavyweight to load at
1269d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     * all when not visible (e.g. requires network fetch, or too complex). Conversation load will
1279d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     * wait until this fragment is visible.
1289d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     */
1299d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    private final int LOAD_WAIT_UNTIL_VISIBLE = 2;
1303bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp
1310b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao    // Keyboard navigation
1320b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao    private KeyboardNavigationController mNavigationController;
1330b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao    // Since we manually control navigation for most of the conversation view due to problems
1340b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao    // with two-pane layout but still rely on the system for SOME navigation, we need to keep track
1350b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao    // of the view that had focus when KeyEvent.ACTION_DOWN was fired. This is because we only
1360b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao    // manually change focus on KeyEvent.ACTION_UP (to prevent holding down the DOWN button and
1370b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao    // lagging the app), however, the view in focus might have changed between ACTION_UP and
1380b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao    // ACTION_DOWN since the system might have handled the ACTION_DOWN and moved focus.
1390b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao    private View mOriginalKeyedView;
1400b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao    private int mMaxScreenHeight;
1410b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao    private int mTopOfVisibleScreen;
1420b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao
1439f957f3463fd149d33c209409e2bba500b539177Andrew Sapperstein    protected ConversationContainer mConversationContainer;
1449b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira
1459f957f3463fd149d33c209409e2bba500b539177Andrew Sapperstein    protected ConversationWebView mWebView;
1469b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira
147a7404589b03ac9dd0d07b3f7d0a1ec92ac9acb62Jin Cao    private ViewGroup mTopmostOverlay;
148a7404589b03ac9dd0d07b3f7d0a1ec92ac9acb62Jin Cao
149a7fa9bfc60682e376d7ee10701ed59fe66add1beAndrew Sapperstein    private ConversationViewProgressController mProgressController;
150376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein
151821fa87279d590e682effbdb652c8a92e805eec8Andrew Sapperstein    private Button mNewMessageBar;
15247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
1539f957f3463fd149d33c209409e2bba500b539177Andrew Sapperstein    protected HtmlConversationTemplates mTemplates;
154f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
155f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    private final MailJsBridge mJsBridge = new MailJsBridge();
156f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
1579f957f3463fd149d33c209409e2bba500b539177Andrew Sapperstein    protected ConversationViewAdapter mAdapter;
15851067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang
159b1d184d164303d83d342c60e4bffc732aa10ac2cAndrew Sapperstein    protected boolean mViewsCreated;
1604071c2f73218ce75750345557bb31a9110737841Mark Wei    // True if we attempted to render before the views were laid out
1614071c2f73218ce75750345557bb31a9110737841Mark Wei    // We will render immediately once layout is done
1624071c2f73218ce75750345557bb31a9110737841Mark Wei    private boolean mNeedRender;
16351067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang
16446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    /**
16546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang     * Temporary string containing the message bodies of the messages within a super-collapsed
16646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang     * block, for one-time use during block expansion. We cannot easily pass the body HTML
16746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang     * into JS without problematic escaping, so hold onto it momentarily and signal JS to fetch it
16846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang     * using {@link MailJsBridge}.
16946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang     */
17046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    private String mTempBodiesHtml;
17146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
172632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang    private int  mMaxAutoLoadMessages;
173632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang
1749f957f3463fd149d33c209409e2bba500b539177Andrew Sapperstein    protected int mSideMarginPx;
17502f9d18a54072db8d86c524f9c09e508092ddd7cAndy Huang
1769d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    /**
1779d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     * If this conversation fragment is not visible, and it's inappropriate to load up front,
1789d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     * this is the reason we are waiting. This flag should be cleared once it's okay to load
1799d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     * the conversation.
1809d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     */
1819d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    private int mLoadWaitReason = LOAD_NOW;
182632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang
1833bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp    private boolean mEnableContentReadySignal;
18428b7aee7fa1f7d096d33fc823a88a64f7a3fa79dAndy Huang
185dde3f9ff76c1a7f9a6e9fa959a6422a8a8d82e81mindyp    private ContentSizeChangeListener mWebViewSizeChangeListener;
186dde3f9ff76c1a7f9a6e9fa959a6422a8a8d82e81mindyp
187e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang    private float mWebViewYPercent;
188e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang
189e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang    /**
190e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang     * Has loadData been called on the WebView yet?
191e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang     */
192e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang    private boolean mWebViewLoadedData;
193e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang
19463b3c6725d60386f564ab53e3ba6495f0c158d9bAndy Huang    private long mWebViewLoadStartMs;
19563b3c6725d60386f564ab53e3ba6495f0c158d9bAndy Huang
19605c70c88e6b421bedce134799a0ea928dbf44cd5Andy Huang    private final Map<String, String> mMessageTransforms = Maps.newHashMap();
19705c70c88e6b421bedce134799a0ea928dbf44cd5Andy Huang
1989d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    private final DataSetObserver mLoadedObserver = new DataSetObserver() {
1999d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        @Override
2009d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        public void onChanged() {
201376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein            getHandler().post(new FragmentRunnable("delayedConversationLoad",
202376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein                    ConversationViewFragment.this) {
2039d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                @Override
2049d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                public void go() {
2059d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                    LogUtils.d(LOG_TAG, "CVF load observer fired, this=%s",
2069d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                            ConversationViewFragment.this);
2079d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                    handleDelayedConversationLoad();
2089d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                }
2099d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            });
2109d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        }
2119d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    };
212f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
213376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein    private final Runnable mOnProgressDismiss = new FragmentRunnable("onProgressDismiss", this) {
2147d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang        @Override
2157d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang        public void go() {
21658192e5202d379ff62bf99995b08a0a7cf6646d1Scott Kennedy            LogUtils.d(LOG_TAG, "onProgressDismiss go() - isUserVisible() = %b", isUserVisible());
2177d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang            if (isUserVisible()) {
2187d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang                onConversationSeen();
2197d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang            }
22030bcfe7b69f9d8e0cad2ee62dae9d3571cd0d88bAndy Huang            mWebView.onRenderComplete();
2217d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang        }
2227d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang    };
2237d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang
224bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang    private static final boolean DEBUG_DUMP_CONVERSATION_HTML = false;
22547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    private static final boolean DISABLE_OFFSCREEN_LOADING = false;
22606c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang    private static final boolean DEBUG_DUMP_CURSOR_CONTENTS = false;
227e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang
228e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang    private static final String BUNDLE_KEY_WEBVIEW_Y_PERCENT =
229e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang            ConversationViewFragment.class.getName() + "webview-y-percent";
230bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang
2312fd167d6131482da984768b5ee75cefa32ed3e32Andrew Sapperstein    private BidiFormatter mBidiFormatter;
2323af481c20edd84419d0d4a8476994ac008c0b97aAndrew Sapperstein
2336c51158ad3269f157424e6c7bd488425c98da08fVikram Aggarwal    /**
2348ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein     * Contains a mapping between inline image attachments and their local message id.
2358ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein     */
2368ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein    private Map<String, String> mUrlToMessageIdMap;
2378ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein
2388ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein    /**
2396c51158ad3269f157424e6c7bd488425c98da08fVikram Aggarwal     * Constructor needs to be public to handle orientation changes and activity lifecycle events.
2406c51158ad3269f157424e6c7bd488425c98da08fVikram Aggarwal     */
241f0ea4849bf7a2c11f99ca0b42307ae8ba665b1dcPaul Westbrook    public ConversationViewFragment() {}
2429b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira
2439b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira    /**
2449b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira     * Creates a new instance of {@link ConversationViewFragment}, initialized
245632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang     * to display a conversation with other parameters inherited/copied from an existing bundle,
246632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang     * typically one created using {@link #makeBasicArgs}.
247632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang     */
248632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang    public static ConversationViewFragment newInstance(Bundle existingArgs,
249632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang            Conversation conversation) {
250632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang        ConversationViewFragment f = new ConversationViewFragment();
251632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang        Bundle args = new Bundle(existingArgs);
252632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang        args.putParcelable(ARG_CONVERSATION, conversation);
253632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang        f.setArguments(args);
254632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang        return f;
255632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang    }
256632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang
257f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
258adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang    public void onAccountChanged(Account newAccount, Account oldAccount) {
259adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang        // if overview mode has changed, re-render completely (no need to also update headers)
260adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang        if (isOverviewMode(newAccount) != isOverviewMode(oldAccount)) {
261adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang            setupOverviewMode();
262adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang            final MessageCursor c = getMessageCursor();
263adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang            if (c != null) {
264adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang                renderConversation(c);
265adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang            } else {
266adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang                // Null cursor means this fragment is either waiting to load or in the middle of
267adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang                // loading. Either way, a future render will happen anyway, and the new setting
268adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang                // will take effect when that happens.
269adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang            }
270adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang            return;
271adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang        }
272adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang
273f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        // settings may have been updated; refresh views that are known to
274f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        // depend on settings
275f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mAdapter.notifyDataSetChanged();
276632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang    }
277632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang
2789b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira    @Override
2799b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira    public void onActivityCreated(Bundle savedInstanceState) {
2809d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        LogUtils.d(LOG_TAG, "IN CVF.onActivityCreated, this=%s visible=%s", this, isUserVisible());
2819b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira        super.onActivityCreated(savedInstanceState);
2821abfcaf0ee345acee28f0b4892314119082b28a2Mark Wei
2831abfcaf0ee345acee28f0b4892314119082b28a2Mark Wei        if (mActivity == null || mActivity.isFinishing()) {
2841abfcaf0ee345acee28f0b4892314119082b28a2Mark Wei            // Activity is finishing, just bail.
2851abfcaf0ee345acee28f0b4892314119082b28a2Mark Wei            return;
2861abfcaf0ee345acee28f0b4892314119082b28a2Mark Wei        }
2871abfcaf0ee345acee28f0b4892314119082b28a2Mark Wei
288f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        Context context = getContext();
289f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mTemplates = new HtmlConversationTemplates(context);
29059e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang
291f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        final FormattedDateBuilder dateBuilder = new FormattedDateBuilder(context);
29259e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang
2930b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao        mNavigationController = mActivity.getKeyboardNavigationController();
2940b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao
2958081df46ef5a7794374e41cd1836e778a2da9b31Paul Westbrook        mAdapter = new ConversationViewAdapter(mActivity, this,
296735a22a197215ec4787ad9f3cbaf465cce54f4d0Andrew Sapperstein                getLoaderManager(), this, this, getContactInfoSource(), this, this,
297a7404589b03ac9dd0d07b3f7d0a1ec92ac9acb62Jin Cao                getListController(), this, mAddressCache, dateBuilder, mBidiFormatter, this);
29851067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang        mConversationContainer.setOverlayAdapter(mAdapter);
29951067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang
30059e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang        // set up snap header (the adapter usually does this with the other ones)
30185ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein        mConversationContainer.getSnapHeader().initialize(
30285ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein                this, mAddressCache, this, getContactInfoSource(),
30385ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein                mActivity.getAccountController().getVeiledAddressMatcher());
304c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang
30590eccf969d691abb34f9c6f3d854091adb0c18d1Andrew Sapperstein        final Resources resources = getResources();
30690eccf969d691abb34f9c6f3d854091adb0c18d1Andrew Sapperstein        mMaxAutoLoadMessages = resources.getInteger(R.integer.max_auto_load_messages);
307632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang
30890eccf969d691abb34f9c6f3d854091adb0c18d1Andrew Sapperstein        mSideMarginPx = resources.getDimensionPixelOffset(
30902f9d18a54072db8d86c524f9c09e508092ddd7cAndy Huang                R.dimen.conversation_message_content_margin_side);
31002f9d18a54072db8d86c524f9c09e508092ddd7cAndy Huang
3118ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein        mUrlToMessageIdMap = new ArrayMap<String, String>();
3128ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein        final InlineAttachmentViewIntentBuilderCreator creator =
3138ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                InlineAttachmentViewIntentBuilderCreatorHolder.
3148ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                getInlineAttachmentViewIntentCreator();
3153c6fd44f9ae0cf60248dc64ee74d46afed633c45Andy Huang        final WebViewContextMenu contextMenu = new WebViewContextMenu(getActivity(),
31691fa034d624d8690905f30a13ae3ffb9601cf948Paul Westbrook                creator.createInlineAttachmentViewIntentBuilder(mAccount,
3173c6fd44f9ae0cf60248dc64ee74d46afed633c45Andy Huang                mConversation != null ? mConversation.id : -1));
3183c6fd44f9ae0cf60248dc64ee74d46afed633c45Andy Huang        contextMenu.setCallbacks(this);
3193c6fd44f9ae0cf60248dc64ee74d46afed633c45Andy Huang        mWebView.setOnCreateContextMenuListener(contextMenu);
3200b7ed6fae6e36f2abc4ca916764177d1879731b4Andy Huang
321adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang        // set this up here instead of onCreateView to ensure the latest Account is loaded
322adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang        setupOverviewMode();
323adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang
3249d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        // Defer the call to initLoader with a Handler.
3259d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        // We want to wait until we know which fragments are present and their final visibility
3269d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        // states before going off and doing work. This prevents extraneous loading from occurring
3279d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        // as the ViewPager shifts about before the initial position is set.
3289d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        //
3299d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        // e.g. click on item #10
3309d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        // ViewPager.setAdapter() actually first loads #0 and #1 under the assumption that #0 is
3319d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        // the initial primary item
3329d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        // Then CPC immediately sets the primary item to #10, which tears down #0/#1 and sets up
3339d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        // #9/#10/#11.
334376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein        getHandler().post(new FragmentRunnable("showConversation", this) {
3359d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            @Override
3369d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            public void go() {
3379d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                showConversation();
3389d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            }
3399d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        });
340cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook
341606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein        if (mConversation != null && mConversation.conversationBaseUri != null &&
342f59d01c3116dc2adde97a5b52aa6094144c2d315Andrew Sapperstein                !Utils.isEmpty(mAccount.accountCookieQueryUri)) {
343cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook            // Set the cookie for this base url
344f59d01c3116dc2adde97a5b52aa6094144c2d315Andrew Sapperstein            new SetCookieTask(getContext(), mConversation.conversationBaseUri.toString(),
345f59d01c3116dc2adde97a5b52aa6094144c2d315Andrew Sapperstein                    mAccount.accountCookieQueryUri).execute();
346cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook        }
3470b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao
3480b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao        // Find the height of the screen for manually scrolling the webview via keyboard.
3490b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao        final Rect screen = new Rect();
3500b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao        mActivity.getWindow().getDecorView().getWindowVisibleDisplayFrame(screen);
3510b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao        mMaxScreenHeight = screen.bottom;
3520b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao        mTopOfVisibleScreen = screen.top + mActivity.getSupportActionBar().getHeight();
3539b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira    }
3549b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira
3559b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira    @Override
356e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang    public void onCreate(Bundle savedState) {
357e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang        super.onCreate(savedState);
358e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang
359b1d184d164303d83d342c60e4bffc732aa10ac2cAndrew Sapperstein        mWebViewClient = createConversationWebViewClient();
360376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein
361e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang        if (savedState != null) {
362e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang            mWebViewYPercent = savedState.getFloat(BUNDLE_KEY_WEBVIEW_Y_PERCENT);
363e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang        }
3643af481c20edd84419d0d4a8476994ac008c0b97aAndrew Sapperstein
3652fd167d6131482da984768b5ee75cefa32ed3e32Andrew Sapperstein        mBidiFormatter = BidiFormatter.getInstance();
366e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang    }
367e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang
368b1d184d164303d83d342c60e4bffc732aa10ac2cAndrew Sapperstein    protected ConversationWebViewClient createConversationWebViewClient() {
369b1d184d164303d83d342c60e4bffc732aa10ac2cAndrew Sapperstein        return new ConversationWebViewClient(mAccount);
370b1d184d164303d83d342c60e4bffc732aa10ac2cAndrew Sapperstein    }
371b1d184d164303d83d342c60e4bffc732aa10ac2cAndrew Sapperstein
372e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang    @Override
3739b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira    public View onCreateView(LayoutInflater inflater,
3749b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira            ViewGroup container, Bundle savedInstanceState) {
375839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang
376632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang        View rootView = inflater.inflate(R.layout.conversation_view, container, false);
377f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        mConversationContainer = (ConversationContainer) rootView
378f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang                .findViewById(R.id.conversation_container);
3798f1877832b0f3bc55e6d63ccf70dfae7dd8328c9Andy Huang        mConversationContainer.setAccountController(this);
38047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
381a7404589b03ac9dd0d07b3f7d0a1ec92ac9acb62Jin Cao        mTopmostOverlay =
38285ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein                (ViewGroup) mConversationContainer.findViewById(R.id.conversation_topmost_overlay);
383a7404589b03ac9dd0d07b3f7d0a1ec92ac9acb62Jin Cao        mTopmostOverlay.setOnKeyListener(this);
384a7404589b03ac9dd0d07b3f7d0a1ec92ac9acb62Jin Cao        inflateSnapHeader(mTopmostOverlay, inflater);
38585ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein        mConversationContainer.setupSnapHeader();
38685ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein
38785ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein        setupNewMessageBar();
38847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
389376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein        mProgressController = new ConversationViewProgressController(this, getHandler());
390376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein        mProgressController.instantiateProgressIndicators(rootView);
3913bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp
392e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein        mWebView = (ConversationWebView)
393e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein                mConversationContainer.findViewById(R.id.conversation_webview);
394f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
395f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        mWebView.addJavascriptInterface(mJsBridge, "mail");
3963bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp        // On JB or newer, we use the 'webkitAnimationStart' DOM event to signal load complete
3973bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp        // Below JB, try to speed up initial render by having the webview do supplemental draws to
3983bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp        // custom a software canvas.
399b941fdb95f61cdde4e1775ac9034343951e8b075mindyp        // TODO(mindyp):
400b941fdb95f61cdde4e1775ac9034343951e8b075mindyp        //PAGE READINESS SIGNAL FOR JELLYBEAN AND NEWER
401b941fdb95f61cdde4e1775ac9034343951e8b075mindyp        // Notify the app on 'webkitAnimationStart' of a simple dummy element with a simple no-op
402b941fdb95f61cdde4e1775ac9034343951e8b075mindyp        // animation that immediately runs on page load. The app uses this as a signal that the
403b941fdb95f61cdde4e1775ac9034343951e8b075mindyp        // content is loaded and ready to draw, since WebView delays firing this event until the
404b941fdb95f61cdde4e1775ac9034343951e8b075mindyp        // layers are composited and everything is ready to draw.
405b941fdb95f61cdde4e1775ac9034343951e8b075mindyp        // This signal does not seem to be reliable, so just use the old method for now.
406f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang        final boolean isJBOrLater = Utils.isRunningJellybeanOrLater();
407f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang        final boolean isUserVisible = isUserVisible();
408f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang        mWebView.setUseSoftwareLayer(!isJBOrLater);
409f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang        mEnableContentReadySignal = isJBOrLater && isUserVisible;
410f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang        mWebView.onUserVisibilityChanged(isUserVisible);
41117a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huang        mWebView.setWebViewClient(mWebViewClient);
412c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang        final WebChromeClient wcc = new WebChromeClient() {
413f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang            @Override
414f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang            public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
4158ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                if (consoleMessage.messageLevel() == ConsoleMessage.MessageLevel.ERROR) {
4168ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                    LogUtils.wtf(LOG_TAG, "JS: %s (%s:%d) f=%s", consoleMessage.message(),
4178ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                            consoleMessage.sourceId(), consoleMessage.lineNumber(),
4188ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                            ConversationViewFragment.this);
4198ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                } else {
4208ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                    LogUtils.i(LOG_TAG, "JS: %s (%s:%d) f=%s", consoleMessage.message(),
4218ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                            consoleMessage.sourceId(), consoleMessage.lineNumber(),
4228ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                            ConversationViewFragment.this);
4238ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                }
424f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang                return true;
425f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang            }
426c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang        };
427c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang        mWebView.setWebChromeClient(wcc);
428f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
4293233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang        final WebSettings settings = mWebView.getSettings();
430f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
431f50aafa8f98545b1a0f88523c253bc2b94117783Greg Bullock        final ScrollIndicatorsView scrollIndicators =
43282d9f637491dcdfbb13dfc33c9cef6543d5a74d6Greg Bullock                (ScrollIndicatorsView) rootView.findViewById(R.id.scroll_indicators);
433f50aafa8f98545b1a0f88523c253bc2b94117783Greg Bullock        scrollIndicators.setSourceView(mWebView);
43456d83850db72592a16f4e6ee9e0d59b60ec0824aMark Wei
435f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        settings.setJavaScriptEnabled(true);
436f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
437376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein        ConversationViewUtils.setTextZoom(getResources(), settings);
438c319b551bebe40b9607acf0ee1d26a880fde9212Andy Huang
43951067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang        mViewsCreated = true;
440e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang        mWebViewLoadedData = false;
44151067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang
4429b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira        return rootView;
4439b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira    }
4449b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira
44585ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein    protected void inflateSnapHeader(ViewGroup topmostOverlay, LayoutInflater inflater) {
44685ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein        inflater.inflate(R.layout.conversation_topmost_overlay_items, topmostOverlay, true);
44785ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein    }
44885ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein
44985ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein    protected void setupNewMessageBar() {
45085ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein        mNewMessageBar = (Button) mConversationContainer.findViewById(
45185ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein                R.id.new_message_notification_bar);
45285ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein        mNewMessageBar.setOnClickListener(new View.OnClickListener() {
45385ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein            @Override
45485ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein            public void onClick(View v) {
45585ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein                onNewMessageBarClick();
45685ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein            }
45785ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein        });
45885ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein    }
45985ea6188b3c62ede2eb0427379f050717f79562cAndrew Sapperstein
4609b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira    @Override
461f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang    public void onResume() {
462f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang        super.onResume();
463f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang        if (mWebView != null) {
464f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang            mWebView.onResume();
465f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang        }
466f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang    }
467f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang
468f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang    @Override
469f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang    public void onPause() {
470f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang        super.onPause();
471f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang        if (mWebView != null) {
472f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang            mWebView.onPause();
473f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang        }
474f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang    }
475f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang
476f7ac83f2fbf5192e2156b42d9e5e4110e1cb07b0Andy Huang    @Override
4779b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira    public void onDestroyView() {
4789b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira        super.onDestroyView();
47946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        mConversationContainer.setOverlayAdapter(null);
48046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        mAdapter = null;
4819d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        resetLoadWaiting(); // be sure to unregister any active load observer
48251067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang        mViewsCreated = false;
4839b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira    }
4849b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira
485e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang    @Override
486e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang    public void onSaveInstanceState(Bundle outState) {
487e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang        super.onSaveInstanceState(outState);
488e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang
489e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang        outState.putFloat(BUNDLE_KEY_WEBVIEW_Y_PERCENT, calculateScrollYPercent());
490e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang    }
491e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang
492e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang    private float calculateScrollYPercent() {
4931b56a67396e40b65e15743bcf8ccce9a623b2f2cPaul Westbrook        final float p;
4941b56a67396e40b65e15743bcf8ccce9a623b2f2cPaul Westbrook        if (mWebView == null) {
4951b56a67396e40b65e15743bcf8ccce9a623b2f2cPaul Westbrook            // onCreateView hasn't been called, return 0 as the user hasn't scrolled the view.
4961b56a67396e40b65e15743bcf8ccce9a623b2f2cPaul Westbrook            return 0;
4971b56a67396e40b65e15743bcf8ccce9a623b2f2cPaul Westbrook        }
4981b56a67396e40b65e15743bcf8ccce9a623b2f2cPaul Westbrook
4991b56a67396e40b65e15743bcf8ccce9a623b2f2cPaul Westbrook        final int scrollY = mWebView.getScrollY();
5001b56a67396e40b65e15743bcf8ccce9a623b2f2cPaul Westbrook        final int viewH = mWebView.getHeight();
5011b56a67396e40b65e15743bcf8ccce9a623b2f2cPaul Westbrook        final int webH = (int) (mWebView.getContentHeight() * mWebView.getScale());
502e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang
503e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang        if (webH == 0 || webH <= viewH) {
504e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang            p = 0;
505e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang        } else if (scrollY + viewH >= webH) {
506e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang            // The very bottom is a special case, it acts as a stronger anchor than the scroll top
507e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang            // at that point.
508e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang            p = 1.0f;
509e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang        } else {
510e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang            p = (float) scrollY / webH;
511e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang        }
512e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang        return p;
513e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang    }
514e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang
5159d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    private void resetLoadWaiting() {
5169d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        if (mLoadWaitReason == LOAD_WAIT_FOR_INITIAL_CONVERSATION) {
5179d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            getListController().unregisterConversationLoadedObserver(mLoadedObserver);
5189d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        }
5199d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        mLoadWaitReason = LOAD_NOW;
5209d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    }
5219d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
5225ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang    @Override
523f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    protected void markUnread() {
524d82a31f04f2a4cce27c8023d5330ff13aad198b0Vikram Aggarwal        super.markUnread();
525839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang        // Ignore unsafe calls made after a fragment is detached from an activity
526839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang        final ControllableActivity activity = (ControllableActivity) getActivity();
527839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang        if (activity == null) {
528839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang            LogUtils.w(LOG_TAG, "ignoring markUnread for conv=%s", mConversation.id);
529839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang            return;
530839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang        }
531839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang
53228e31e2417d775b343919cf6018f85871e40a294Andy Huang        if (mViewState == null) {
53328e31e2417d775b343919cf6018f85871e40a294Andy Huang            LogUtils.i(LOG_TAG, "ignoring markUnread for conv with no view state (%d)",
53428e31e2417d775b343919cf6018f85871e40a294Andy Huang                    mConversation.id);
53528e31e2417d775b343919cf6018f85871e40a294Andy Huang            return;
53628e31e2417d775b343919cf6018f85871e40a294Andy Huang        }
537839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang        activity.getConversationUpdater().markConversationMessagesUnread(mConversation,
5384a878b606961825fb4fd412b460df75779f09ad2Vikram Aggarwal                mViewState.getUnreadMessageUris(), mViewState.getConversationInfo());
539839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang    }
540839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang
541f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
542f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public void onUserVisibleHintChanged() {
5439d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        final boolean userVisible = isUserVisible();
54458192e5202d379ff62bf99995b08a0a7cf6646d1Scott Kennedy        LogUtils.d(LOG_TAG, "ConversationViewFragment#onUserVisibleHintChanged(), userVisible = %b",
54558192e5202d379ff62bf99995b08a0a7cf6646d1Scott Kennedy                userVisible);
5469d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
5479d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        if (!userVisible) {
548376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein            mProgressController.dismissLoadingStatus();
5499d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        } else if (mViewsCreated) {
550e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang            String loadTag = null;
551eeb4a35d7c596192666152d7115ac3c842b901c4Andy Huang            final boolean isInitialLoading;
552eeb4a35d7c596192666152d7115ac3c842b901c4Andy Huang            if (mActivity != null) {
553eeb4a35d7c596192666152d7115ac3c842b901c4Andy Huang                isInitialLoading = mActivity.getConversationUpdater()
554e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang                    .isInitialConversationLoading();
555eeb4a35d7c596192666152d7115ac3c842b901c4Andy Huang            } else {
556eeb4a35d7c596192666152d7115ac3c842b901c4Andy Huang                isInitialLoading = true;
557eeb4a35d7c596192666152d7115ac3c842b901c4Andy Huang            }
558e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang
5599d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            if (getMessageCursor() != null) {
5609d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                LogUtils.d(LOG_TAG, "Fragment is now user-visible, onConversationSeen: %s", this);
561e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang                if (!isInitialLoading) {
562e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang                    loadTag = "preloaded";
563e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang                }
564f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                onConversationSeen();
5659d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            } else if (isLoadWaiting()) {
5669d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                LogUtils.d(LOG_TAG, "Fragment is now user-visible, showing conversation: %s", this);
567e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang                if (!isInitialLoading) {
568e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang                    loadTag = "load_deferred";
569e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang                }
5709d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                handleDelayedConversationLoad();
571632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang            }
572e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang
573e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang            if (loadTag != null) {
574e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang                // pager swipes are visibility transitions to 'visible' except during initial
575e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang                // pager load on A) enter conversation mode B) rotate C) 2-pane conv-mode list-tap
576e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang              Analytics.getInstance().sendEvent("pager_swipe", loadTag,
577e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang                      getCurrentFolderTypeDesc(), 0);
578e6c9fb6835247d98898e2af581ad9449ad7f3184Andy Huang            }
579632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang        }
580f8cf5468500677fceac3025727daba8155d319d6Andy Huang
58130bcfe7b69f9d8e0cad2ee62dae9d3571cd0d88bAndy Huang        if (mWebView != null) {
58230bcfe7b69f9d8e0cad2ee62dae9d3571cd0d88bAndy Huang            mWebView.onUserVisibilityChanged(userVisible);
58330bcfe7b69f9d8e0cad2ee62dae9d3571cd0d88bAndy Huang        }
584f8cf5468500677fceac3025727daba8155d319d6Andy Huang    }
585f8cf5468500677fceac3025727daba8155d319d6Andy Huang
5869d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    /**
5879d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     * Will either call initLoader now to begin loading, or set {@link #mLoadWaitReason} and do
5889d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     * nothing (in which case you should later call {@link #handleDelayedConversationLoad()}).
5899d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     */
5909b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira    private void showConversation() {
5919d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        final int reason;
5929d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
5939d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        if (isUserVisible()) {
5949d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            LogUtils.i(LOG_TAG,
5959d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                    "SHOWCONV: CVF is user-visible, immediately loading conversation (%s)", this);
5969d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            reason = LOAD_NOW;
597243c23618b066bcdcd0ab9e36d8c01f50db2cbd0Andy Huang            timerMark("CVF.showConversation");
5989d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        } else {
5999d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            final boolean disableOffscreenLoading = DISABLE_OFFSCREEN_LOADING
6000b8c080491f6884c2cfb7fcc473d970a9f4b97b9Alice Yang                    || Utils.isLowRamDevice(getContext())
601606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein                    || (mConversation != null && (mConversation.isRemote
602606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein                            || mConversation.getNumMessages() > mMaxAutoLoadMessages));
6039d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
6049d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            // When not visible, we should not immediately load if either this conversation is
6059d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            // too heavyweight, or if the main/initial conversation is busy loading.
6069d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            if (disableOffscreenLoading) {
6079d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                reason = LOAD_WAIT_UNTIL_VISIBLE;
6089d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                LogUtils.i(LOG_TAG, "SHOWCONV: CVF waiting until visible to load (%s)", this);
6099d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            } else if (getListController().isInitialConversationLoading()) {
6109d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                reason = LOAD_WAIT_FOR_INITIAL_CONVERSATION;
6119d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                LogUtils.i(LOG_TAG, "SHOWCONV: CVF waiting for initial to finish (%s)", this);
6129d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                getListController().registerConversationLoadedObserver(mLoadedObserver);
6139d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            } else {
6149d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                LogUtils.i(LOG_TAG,
6159d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                        "SHOWCONV: CVF is not visible, but no reason to wait. loading now. (%s)",
6169d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                        this);
6179d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                reason = LOAD_NOW;
6189d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            }
619632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang        }
6209d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
6219d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        mLoadWaitReason = reason;
6229d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        if (mLoadWaitReason == LOAD_NOW) {
6239d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            startConversationLoad();
6249d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        }
6259d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    }
6269d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
6279d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    private void handleDelayedConversationLoad() {
6289d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        resetLoadWaiting();
6299d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        startConversationLoad();
6309d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    }
6319d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
6329d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    private void startConversationLoad() {
6333bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp        mWebView.setVisibility(View.VISIBLE);
634606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein        loadContent();
6353bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp        // TODO(mindyp): don't show loading status for a previously rendered
6363bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp        // conversation. Ielieve this is better done by making sure don't show loading status
6373bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp        // until XX ms have passed without loading completed.
638376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein        mProgressController.showLoadingStatus(isUserVisible());
6398e915724b6e4374da9b70161ee0a55f0c763e563Mindy Pereira    }
6408e915724b6e4374da9b70161ee0a55f0c763e563Mindy Pereira
641606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein    /**
642606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein     * Can be overridden in case a subclass needs to load something other than
643606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein     * the messages of a conversation.
644606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein     */
645606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein    protected void loadContent() {
646606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein        getLoaderManager().initLoader(MESSAGE_LOADER, Bundle.EMPTY, getMessageLoaderCallbacks());
647606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein    }
648606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein
6497d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang    private void revealConversation() {
650243c23618b066bcdcd0ab9e36d8c01f50db2cbd0Andy Huang        timerMark("revealing conversation");
651376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein        mProgressController.dismissLoadingStatus(mOnProgressDismiss);
65272953f25e00ee8e7b5c7682148430dc82f00a77dJin Cao        if (isUserVisible()) {
65372953f25e00ee8e7b5c7682148430dc82f00a77dJin Cao            AnalyticsTimer.getInstance().logDuration(AnalyticsTimer.OPEN_CONV_VIEW_FROM_LIST,
65472953f25e00ee8e7b5c7682148430dc82f00a77dJin Cao                    true /* isDestructive */, "open_conversation", "from_list", null);
65572953f25e00ee8e7b5c7682148430dc82f00a77dJin Cao        }
6567d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang    }
6577d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang
6589d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    private boolean isLoadWaiting() {
6599d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        return mLoadWaitReason != LOAD_NOW;
6609d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    }
6619d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
66251067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang    private void renderConversation(MessageCursor messageCursor) {
6633bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp        final String convHtml = renderMessageBodies(messageCursor, mEnableContentReadySignal);
664243c23618b066bcdcd0ab9e36d8c01f50db2cbd0Andy Huang        timerMark("rendered conversation");
665bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang
666bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang        if (DEBUG_DUMP_CONVERSATION_HTML) {
667bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang            java.io.FileWriter fw = null;
668bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang            try {
669f1566b1e77e9d7ac66ebb47897b5c652e2b18943Andrew Sapperstein                fw = new java.io.FileWriter(getSdCardFilePath());
670bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang                fw.write(convHtml);
671bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang            } catch (java.io.IOException e) {
672bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang                e.printStackTrace();
673bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang            } finally {
674bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang                if (fw != null) {
675bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang                    try {
676bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang                        fw.close();
677bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang                    } catch (java.io.IOException e) {
678bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang                        e.printStackTrace();
679bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang                    }
680bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang                }
681bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang            }
682bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang        }
683bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang
684e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang        // save off existing scroll position before re-rendering
685e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang        if (mWebViewLoadedData) {
686e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang            mWebViewYPercent = calculateScrollYPercent();
687e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang        }
688e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang
689bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang        mWebView.loadDataWithBaseURL(mBaseUri, convHtml, "text/html", "utf-8", null);
690e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang        mWebViewLoadedData = true;
69163b3c6725d60386f564ab53e3ba6495f0c158d9bAndy Huang        mWebViewLoadStartMs = SystemClock.uptimeMillis();
69251067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang    }
69351067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang
694f1566b1e77e9d7ac66ebb47897b5c652e2b18943Andrew Sapperstein    protected String getSdCardFilePath() {
695f1566b1e77e9d7ac66ebb47897b5c652e2b18943Andrew Sapperstein        return "/sdcard/conv" + mConversation.id + ".html";
696f1566b1e77e9d7ac66ebb47897b5c652e2b18943Andrew Sapperstein    }
697f1566b1e77e9d7ac66ebb47897b5c652e2b18943Andrew Sapperstein
6987bdc3750454efe59617b7df945eadd7e59bee954Andy Huang    /**
6997bdc3750454efe59617b7df945eadd7e59bee954Andy Huang     * Populate the adapter with overlay views (message headers, super-collapsed blocks, a
7007bdc3750454efe59617b7df945eadd7e59bee954Andy Huang     * conversation header), and return an HTML document with spacer divs inserted for all overlays.
7017bdc3750454efe59617b7df945eadd7e59bee954Andy Huang     *
7027bdc3750454efe59617b7df945eadd7e59bee954Andy Huang     */
7039f957f3463fd149d33c209409e2bba500b539177Andrew Sapperstein    protected String renderMessageBodies(MessageCursor messageCursor,
7043bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp            boolean enableContentReadySignal) {
705f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        int pos = -1;
706632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang
7071ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang        LogUtils.d(LOG_TAG, "IN renderMessageBodies, fragment=%s", this);
7087bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        boolean allowNetworkImages = false;
7097bdc3750454efe59617b7df945eadd7e59bee954Andy Huang
710c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang        // TODO: re-use any existing adapter item state (expanded, details expanded, show pics)
71128b7aee7fa1f7d096d33fc823a88a64f7a3fa79dAndy Huang
7127bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        // Walk through the cursor and build up an overlay adapter as you go.
7137bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        // Each overlay has an entry in the adapter for easy scroll handling in the container.
7147bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        // Items are not necessarily 1:1 in cursor and adapter because of super-collapsed blocks.
7157bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        // When adding adapter items, also add their heights to help the container later determine
7167bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        // overlay dimensions.
7177bdc3750454efe59617b7df945eadd7e59bee954Andy Huang
718db620fe42dcf1909468822b61238a23103d15376Andy Huang        // When re-rendering, prevent ConversationContainer from laying out overlays until after
719db620fe42dcf1909468822b61238a23103d15376Andy Huang        // the new spacers are positioned by WebView.
720db620fe42dcf1909468822b61238a23103d15376Andy Huang        mConversationContainer.invalidateSpacerGeometry();
721db620fe42dcf1909468822b61238a23103d15376Andy Huang
7227bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        mAdapter.clear();
7237bdc3750454efe59617b7df945eadd7e59bee954Andy Huang
72447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        // re-evaluate the message parts of the view state, since the messages may have changed
72547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        // since the previous render
72647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        final ConversationViewState prevState = mViewState;
72747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        mViewState = new ConversationViewState(prevState);
72847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
7295ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang        // N.B. the units of height for spacers are actually dp and not px because WebView assumes
7302e9acfe527426c00b5df2ca66189262dc40c8575Andy Huang        // a pixel is an mdpi pixel, unless you set device-dpi.
7315ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang
7327bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        // add a single conversation header item
7337bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        final int convHeaderPos = mAdapter.addConversationHeader(mConversation);
73423014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang        final int convHeaderPx = measureOverlayHeight(convHeaderPos);
7353233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang
7364dc732387454eef3ee6d89f9fa393630eb6213f9Andy Huang        mTemplates.startConversation(mWebView.getViewportWidth(),
7374dc732387454eef3ee6d89f9fa393630eb6213f9Andy Huang                mWebView.screenPxToWebPx(mSideMarginPx), mWebView.screenPxToWebPx(convHeaderPx));
7383233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang
73946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        int collapsedStart = -1;
740839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang        ConversationMessage prevCollapsedMsg = null;
741e8221483ce5f013f02a4ef63d6682cc36313cda7Andrew Sapperstein
7428ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein        final boolean alwaysShowImages = shouldAlwaysShowImages();
743f323c046034b4658a80438575d8e9f01d92e57e6Alice Yang
744e8221483ce5f013f02a4ef63d6682cc36313cda7Andrew Sapperstein        boolean prevSafeForImages = alwaysShowImages;
74546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
746735a22a197215ec4787ad9f3cbaf465cce54f4d0Andrew Sapperstein        boolean hasDraft = false;
747f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        while (messageCursor.moveToPosition(++pos)) {
748839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang            final ConversationMessage msg = messageCursor.getMessage();
74946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
750e8221483ce5f013f02a4ef63d6682cc36313cda7Andrew Sapperstein            final boolean safeForImages = alwaysShowImages ||
751202738427ede23dd5863583aa3df17e4abfbf5e2Scott Kennedy                    msg.alwaysShowImages || prevState.getShouldShowImages(msg);
7523233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang            allowNetworkImages |= safeForImages;
7532405528bb15245f594277fe7b6efacd6a18ce69eAndy Huang
75408098ec4c894d9a15dfe800ad2397494e7e0a79aPaul Westbrook            final Integer savedExpanded = prevState.getExpansionState(msg);
75508098ec4c894d9a15dfe800ad2397494e7e0a79aPaul Westbrook            final int expandedState;
756839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang            if (savedExpanded != null) {
7571ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang                if (ExpansionState.isSuperCollapsed(savedExpanded) && messageCursor.isLast()) {
7581ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang                    // override saved state when this is now the new last message
7591ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang                    // this happens to the second-to-last message when you discard a draft
7601ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang                    expandedState = ExpansionState.EXPANDED;
7611ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang                } else {
7621ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang                    expandedState = savedExpanded;
7631ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang                }
764839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang            } else {
765cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang                // new messages that are not expanded default to being eligible for super-collapse
766605dcfcefab6b222db6178f9c64a9d7a1c464da9Andrew Sapperstein                if (!msg.read || messageCursor.isLast()) {
767605dcfcefab6b222db6178f9c64a9d7a1c464da9Andrew Sapperstein                    expandedState = ExpansionState.EXPANDED;
768605dcfcefab6b222db6178f9c64a9d7a1c464da9Andrew Sapperstein                } else if (messageCursor.isFirst()) {
769605dcfcefab6b222db6178f9c64a9d7a1c464da9Andrew Sapperstein                    expandedState = ExpansionState.COLLAPSED;
770605dcfcefab6b222db6178f9c64a9d7a1c464da9Andrew Sapperstein                } else {
771605dcfcefab6b222db6178f9c64a9d7a1c464da9Andrew Sapperstein                    expandedState = ExpansionState.SUPER_COLLAPSED;
772735a22a197215ec4787ad9f3cbaf465cce54f4d0Andrew Sapperstein                    hasDraft |= msg.isDraft();
773605dcfcefab6b222db6178f9c64a9d7a1c464da9Andrew Sapperstein                }
774839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang            }
775202738427ede23dd5863583aa3df17e4abfbf5e2Scott Kennedy            mViewState.setShouldShowImages(msg, prevState.getShouldShowImages(msg));
77608098ec4c894d9a15dfe800ad2397494e7e0a79aPaul Westbrook            mViewState.setExpansionState(msg, expandedState);
777839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang
778839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang            // save off "read" state from the cursor
779839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang            // later, the view may not match the cursor (e.g. conversation marked read on open)
780423bea25992492efea7d414819729f9eae7ce72eAndy Huang            // however, if a previous state indicated this message was unread, trust that instead
781423bea25992492efea7d414819729f9eae7ce72eAndy Huang            // so "mark unread" marks all originally unread messages
782423bea25992492efea7d414819729f9eae7ce72eAndy Huang            mViewState.setReadState(msg, msg.read && !prevState.isUnread(msg));
783c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang
784cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang            // We only want to consider this for inclusion in the super collapsed block if
785cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang            // 1) The we don't have previous state about this message  (The first time that the
786cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang            //    user opens a conversation)
787cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang            // 2) The previously saved state for this message indicates that this message is
788cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang            //    in the super collapsed block.
789cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang            if (ExpansionState.isSuperCollapsed(expandedState)) {
790cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang                // contribute to a super-collapsed block that will be emitted just before the
791cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang                // next expanded header
792cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang                if (collapsedStart < 0) {
793cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang                    collapsedStart = pos;
79446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                }
795cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang                prevCollapsedMsg = msg;
796cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang                prevSafeForImages = safeForImages;
7977dc7fa00a6bea16b1142dc4e9ef8aabb73747fb7Andrew Sapperstein
7987dc7fa00a6bea16b1142dc4e9ef8aabb73747fb7Andrew Sapperstein                // This line puts the from address in the address cache so that
7997dc7fa00a6bea16b1142dc4e9ef8aabb73747fb7Andrew Sapperstein                // we get the sender image for it if it's in a super-collapsed block.
8007dc7fa00a6bea16b1142dc4e9ef8aabb73747fb7Andrew Sapperstein                getAddress(msg.getFrom());
801cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang                continue;
80246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            }
8037bdc3750454efe59617b7df945eadd7e59bee954Andy Huang
80446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            // resolve any deferred decisions on previous collapsed items
80546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            if (collapsedStart >= 0) {
80646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                if (pos - collapsedStart == 1) {
807cee3c90574b48ccaa0f8b9f9341383c231ed41d2Andrew Sapperstein                    // Special-case for a single collapsed message: no need to super-collapse it.
808e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein                    renderMessage(prevCollapsedMsg, false /* expanded */, prevSafeForImages);
80946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                } else {
810735a22a197215ec4787ad9f3cbaf465cce54f4d0Andrew Sapperstein                    renderSuperCollapsedBlock(collapsedStart, pos - 1, hasDraft);
81146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                }
812735a22a197215ec4787ad9f3cbaf465cce54f4d0Andrew Sapperstein                hasDraft = false; // reset hasDraft
81346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                prevCollapsedMsg = null;
81446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                collapsedStart = -1;
81546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            }
8162405528bb15245f594277fe7b6efacd6a18ce69eAndy Huang
817e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein            renderMessage(msg, ExpansionState.isExpanded(expandedState), safeForImages);
818f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        }
8193233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang
820e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein        final MessageHeaderItem lastHeaderItem = getLastMessageHeaderItem();
821e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein        final int convFooterPos = mAdapter.addConversationFooter(lastHeaderItem);
822e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein        final int convFooterPx = measureOverlayHeight(convFooterPos);
823e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein
8243233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang        mWebView.getSettings().setBlockNetworkImage(!allowNetworkImages);
8253233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang
8262fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein        final boolean applyTransforms = shouldApplyTransforms();
82757f354c02dbf56ebc893a570564906e61c31e09eAndy Huang
828c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang        // If the conversation has specified a base uri, use it here, otherwise use mBaseUri
829e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein        return mTemplates.endConversation(convFooterPx, mBaseUri,
830e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein                mConversation.getBaseUri(mBaseUri),
8312160d53e6ae0bfb797569d616e735e46c21522ffAndy Huang                mWebView.getViewportWidth(), mWebView.getWidthInDp(mSideMarginPx),
8322160d53e6ae0bfb797569d616e735e46c21522ffAndy Huang                enableContentReadySignal, isOverviewMode(mAccount), applyTransforms,
8332160d53e6ae0bfb797569d616e735e46c21522ffAndy Huang                applyTransforms);
834f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    }
835f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
836e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein    private MessageHeaderItem getLastMessageHeaderItem() {
837e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein        final int count = mAdapter.getCount();
838e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein        if (count < 3) {
839e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein            LogUtils.wtf(LOG_TAG, "not enough items in the adapter. count: %s", count);
840e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein            return null;
841e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein        }
842e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein        return (MessageHeaderItem) mAdapter.getItem(count - 2);
843e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein    }
844e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein
845735a22a197215ec4787ad9f3cbaf465cce54f4d0Andrew Sapperstein    private void renderSuperCollapsedBlock(int start, int end, boolean hasDraft) {
846735a22a197215ec4787ad9f3cbaf465cce54f4d0Andrew Sapperstein        final int blockPos = mAdapter.addSuperCollapsedBlock(start, end, hasDraft);
84723014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang        final int blockPx = measureOverlayHeight(blockPos);
84823014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang        mTemplates.appendSuperCollapsedHtml(start, mWebView.screenPxToWebPx(blockPx));
84946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    }
85046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
851e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein    private void renderMessage(ConversationMessage msg, boolean expanded, boolean safeForImages) {
85214f937408fe2451a91b44d3cd7d141347e716775Andrew Sapperstein
853202738427ede23dd5863583aa3df17e4abfbf5e2Scott Kennedy        final int headerPos = mAdapter.addMessageHeader(msg, expanded,
854202738427ede23dd5863583aa3df17e4abfbf5e2Scott Kennedy                mViewState.getShouldShowImages(msg));
85546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        final MessageHeaderItem headerItem = (MessageHeaderItem) mAdapter.getItem(headerPos);
85646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
85746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        final int footerPos = mAdapter.addMessageFooter(headerItem);
85846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
85946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        // Measure item header and footer heights to allocate spacers in HTML
86046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        // But since the views themselves don't exist yet, render each item temporarily into
86146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        // a host view for measurement.
86223014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang        final int headerPx = measureOverlayHeight(headerPos);
86323014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang        final int footerPx = measureOverlayHeight(footerPos);
86446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
865256b35c0a8287f48c28e0d1ba3fae65790063295Andy Huang        mTemplates.appendMessageHtml(msg, expanded, safeForImages,
86623014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang                mWebView.screenPxToWebPx(headerPx), mWebView.screenPxToWebPx(footerPx));
867243c23618b066bcdcd0ab9e36d8c01f50db2cbd0Andy Huang        timerMark("rendered message");
86846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    }
86946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
87046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    private String renderCollapsedHeaders(MessageCursor cursor,
87146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            SuperCollapsedBlockItem blockToReplace) {
87246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        final List<ConversationOverlayItem> replacements = Lists.newArrayList();
87346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
87446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        mTemplates.reset();
87546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
876f323c046034b4658a80438575d8e9f01d92e57e6Alice Yang        final boolean alwaysShowImages = (mAccount != null) &&
877f323c046034b4658a80438575d8e9f01d92e57e6Alice Yang                (mAccount.settings.showImages == Settings.ShowImages.ALWAYS);
878e8221483ce5f013f02a4ef63d6682cc36313cda7Andrew Sapperstein
8792b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei        // In devices with non-integral density multiplier, screen pixels translate to non-integral
8802b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei        // web pixels. Keep track of the error that occurs when we cast all heights to int
8812b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei        float error = 0f;
88214f937408fe2451a91b44d3cd7d141347e716775Andrew Sapperstein        boolean first = true;
88346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        for (int i = blockToReplace.getStart(), end = blockToReplace.getEnd(); i <= end; i++) {
88446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            cursor.moveToPosition(i);
885839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang            final ConversationMessage msg = cursor.getMessage();
88614f937408fe2451a91b44d3cd7d141347e716775Andrew Sapperstein
8874ddda2f0a4ee5381a90779a6939b05b064ce5d11Andrew Sapperstein            final MessageHeaderItem header = ConversationViewAdapter.newMessageHeaderItem(
88814f937408fe2451a91b44d3cd7d141347e716775Andrew Sapperstein                    mAdapter, mAdapter.getDateBuilder(), msg, false /* expanded */,
889e8221483ce5f013f02a4ef63d6682cc36313cda7Andrew Sapperstein                    alwaysShowImages || mViewState.getShouldShowImages(msg));
890381c322eb30c39f63a2bb82812d63262eb3c1c1cAndrew Sapperstein            final MessageFooterItem footer = mAdapter.newMessageFooterItem(mAdapter, header);
89146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
89223014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang            final int headerPx = measureOverlayHeight(header);
89323014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang            final int footerPx = measureOverlayHeight(footer);
8942b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei            error += mWebView.screenPxToWebPxError(headerPx)
89559ccec3db4710f2aea6a4a9a30160ad19331367dAndrew Sapperstein                    + mWebView.screenPxToWebPxError(footerPx);
8962b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei
8972b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei            // When the error becomes greater than 1 pixel, make the next header 1 pixel taller
8982b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei            int correction = 0;
8992b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei            if (error >= 1) {
9002b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei                correction = 1;
9012b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei                error -= 1;
9022b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei            }
90346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
904e8221483ce5f013f02a4ef63d6682cc36313cda7Andrew Sapperstein            mTemplates.appendMessageHtml(msg, false /* expanded */,
905e8221483ce5f013f02a4ef63d6682cc36313cda7Andrew Sapperstein                    alwaysShowImages || msg.alwaysShowImages,
9062b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei                    mWebView.screenPxToWebPx(headerPx) + correction,
9072b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei                    mWebView.screenPxToWebPx(footerPx));
90846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            replacements.add(header);
90946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            replacements.add(footer);
910839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang
91108098ec4c894d9a15dfe800ad2397494e7e0a79aPaul Westbrook            mViewState.setExpansionState(msg, ExpansionState.COLLAPSED);
91246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        }
91346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
91446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        mAdapter.replaceSuperCollapsedBlock(blockToReplace, replacements);
91506c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang        mAdapter.notifyDataSetChanged();
91646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
91746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        return mTemplates.emit();
91846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    }
91946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
9209f957f3463fd149d33c209409e2bba500b539177Andrew Sapperstein    protected int measureOverlayHeight(int position) {
92146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        return measureOverlayHeight(mAdapter.getItem(position));
92246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    }
92346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
9247bdc3750454efe59617b7df945eadd7e59bee954Andy Huang    /**
925b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang     * Measure the height of an adapter view by rendering an adapter item into a temporary
92646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang     * host view, and asking the view to immediately measure itself. This method will reuse
9277bdc3750454efe59617b7df945eadd7e59bee954Andy Huang     * a previous adapter view from {@link ConversationContainer}'s scrap views if one was generated
9287bdc3750454efe59617b7df945eadd7e59bee954Andy Huang     * earlier.
9297bdc3750454efe59617b7df945eadd7e59bee954Andy Huang     * <p>
93046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang     * After measuring the height, this method also saves the height in the
93146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang     * {@link ConversationOverlayItem} for later use in overlay positioning.
9327bdc3750454efe59617b7df945eadd7e59bee954Andy Huang     *
93346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang     * @param convItem adapter item with data to render and measure
93423014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang     * @return height of the rendered view in screen px
9357bdc3750454efe59617b7df945eadd7e59bee954Andy Huang     */
936afaab1752ab5b507cdaad7b3619ffc1c9728368fAndrew Sapperstein    private int measureOverlayHeight(ConversationOverlayItem convItem) {
9377bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        final int type = convItem.getType();
9387bdc3750454efe59617b7df945eadd7e59bee954Andy Huang
9397bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        final View convertView = mConversationContainer.getScrapView(type);
940b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang        final View hostView = mAdapter.getView(convItem, convertView, mConversationContainer,
941b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang                true /* measureOnly */);
9427bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        if (convertView == null) {
9437bdc3750454efe59617b7df945eadd7e59bee954Andy Huang            mConversationContainer.addScrapView(type, hostView);
9447bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        }
9457bdc3750454efe59617b7df945eadd7e59bee954Andy Huang
9469875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang        final int heightPx = mConversationContainer.measureOverlay(hostView);
9477bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        convItem.setHeight(heightPx);
9489875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang        convItem.markMeasurementValid();
9497bdc3750454efe59617b7df945eadd7e59bee954Andy Huang
95023014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang        return heightPx;
9517bdc3750454efe59617b7df945eadd7e59bee954Andy Huang    }
9527bdc3750454efe59617b7df945eadd7e59bee954Andy Huang
9535ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang    @Override
9545ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang    public void onConversationViewHeaderHeightChange(int newHeight) {
955ab2d998506c83e82ddae25b6ba1419414e1e8122Mark Wei        final int h = mWebView.screenPxToWebPx(newHeight);
956ab2d998506c83e82ddae25b6ba1419414e1e8122Mark Wei
957ab2d998506c83e82ddae25b6ba1419414e1e8122Mark Wei        mWebView.loadUrl(String.format("javascript:setConversationHeaderSpacerHeight(%s);", h));
9585ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang    }
9595ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang
9603233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang    // END conversation header callbacks
9613233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang
962735a22a197215ec4787ad9f3cbaf465cce54f4d0Andrew Sapperstein    // START conversation footer callbacks
963735a22a197215ec4787ad9f3cbaf465cce54f4d0Andrew Sapperstein
964735a22a197215ec4787ad9f3cbaf465cce54f4d0Andrew Sapperstein    @Override
965735a22a197215ec4787ad9f3cbaf465cce54f4d0Andrew Sapperstein    public void onConversationFooterHeightChange(int newHeight) {
966735a22a197215ec4787ad9f3cbaf465cce54f4d0Andrew Sapperstein        final int h = mWebView.screenPxToWebPx(newHeight);
967735a22a197215ec4787ad9f3cbaf465cce54f4d0Andrew Sapperstein
968735a22a197215ec4787ad9f3cbaf465cce54f4d0Andrew Sapperstein        mWebView.loadUrl(String.format("javascript:setConversationFooterSpacerHeight(%s);", h));
969735a22a197215ec4787ad9f3cbaf465cce54f4d0Andrew Sapperstein    }
970735a22a197215ec4787ad9f3cbaf465cce54f4d0Andrew Sapperstein
971735a22a197215ec4787ad9f3cbaf465cce54f4d0Andrew Sapperstein    // END conversation footer callbacks
972735a22a197215ec4787ad9f3cbaf465cce54f4d0Andrew Sapperstein
9733233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang    // START message header callbacks
9743233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang    @Override
975c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang    public void setMessageSpacerHeight(MessageHeaderItem item, int newSpacerHeightPx) {
976c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang        mConversationContainer.invalidateSpacerGeometry();
977c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang
978c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang        // update message HTML spacer height
97923014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang        final int h = mWebView.screenPxToWebPx(newSpacerHeightPx);
98023014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang        LogUtils.i(LAYOUT_TAG, "setting HTML spacer h=%dwebPx (%dscreenPx)", h,
98123014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang                newSpacerHeightPx);
9825349ce14695dc98615108eb26f96806921b0abbaVikram Aggarwal        mWebView.loadUrl(String.format("javascript:setMessageHeaderSpacerHeight('%s', %s);",
983014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                mTemplates.getMessageDomId(item.getMessage()), h));
9843233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang    }
9853233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang
9863233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang    @Override
98759ccec3db4710f2aea6a4a9a30160ad19331367dAndrew Sapperstein    public void setMessageExpanded(MessageHeaderItem item, int newSpacerHeightPx) {
988c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang        mConversationContainer.invalidateSpacerGeometry();
989c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang
990c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang        // show/hide the HTML message body and update the spacer height
99123014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang        final int h = mWebView.screenPxToWebPx(newSpacerHeightPx);
99223014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang        LogUtils.i(LAYOUT_TAG, "setting HTML spacer expanded=%s h=%dwebPx (%dscreenPx)",
99323014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang                item.isExpanded(), h, newSpacerHeightPx);
99459ccec3db4710f2aea6a4a9a30160ad19331367dAndrew Sapperstein        mWebView.loadUrl(String.format("javascript:setMessageBodyVisible('%s', %s, %s);",
99559ccec3db4710f2aea6a4a9a30160ad19331367dAndrew Sapperstein                mTemplates.getMessageDomId(item.getMessage()), item.isExpanded(), h));
996839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang
997014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang        mViewState.setExpansionState(item.getMessage(),
99808098ec4c894d9a15dfe800ad2397494e7e0a79aPaul Westbrook                item.isExpanded() ? ExpansionState.EXPANDED : ExpansionState.COLLAPSED);
9993233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang    }
10003233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang
10013233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang    @Override
1002eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy    public void showExternalResources(final Message msg) {
1003202738427ede23dd5863583aa3df17e4abfbf5e2Scott Kennedy        mViewState.setShouldShowImages(msg, true);
10043233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang        mWebView.getSettings().setBlockNetworkImage(false);
1005eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy        mWebView.loadUrl("javascript:unblockImages(['" + mTemplates.getMessageDomId(msg) + "']);");
1006eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy    }
1007eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy
1008eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy    @Override
1009eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy    public void showExternalResources(final String senderRawAddress) {
1010eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy        mWebView.getSettings().setBlockNetworkImage(false);
1011eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy
1012eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy        final Address sender = getAddress(senderRawAddress);
1013eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy        final MessageCursor cursor = getMessageCursor();
1014eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy
1015eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy        final List<String> messageDomIds = new ArrayList<String>();
1016eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy
1017eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy        int pos = -1;
1018eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy        while (cursor.moveToPosition(++pos)) {
1019eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy            final ConversationMessage message = cursor.getMessage();
1020eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy            if (sender.equals(getAddress(message.getFrom()))) {
1021eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy                message.alwaysShowImages = true;
1022eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy
1023eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy                mViewState.setShouldShowImages(message, true);
1024eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy                messageDomIds.add(mTemplates.getMessageDomId(message));
1025eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy            }
1026eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy        }
1027eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy
1028eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy        final String url = String.format(
1029eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy                "javascript:unblockImages(['%s']);", TextUtils.join("','", messageDomIds));
1030eb9a4bdc53269ee05fe11870b9ebf03f18196585Scott Kennedy        mWebView.loadUrl(url);
10313233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang    }
10321ebc2db723ed29093d724eb5da906a496ee57224Alice Yang
10331ebc2db723ed29093d724eb5da906a496ee57224Alice Yang    @Override
103475b52a50a7b8382d9046d48ea8cda97b5471cbc8Andy Huang    public boolean supportsMessageTransforms() {
103575b52a50a7b8382d9046d48ea8cda97b5471cbc8Andy Huang        return true;
103675b52a50a7b8382d9046d48ea8cda97b5471cbc8Andy Huang    }
103775b52a50a7b8382d9046d48ea8cda97b5471cbc8Andy Huang
103875b52a50a7b8382d9046d48ea8cda97b5471cbc8Andy Huang    @Override
10391ebc2db723ed29093d724eb5da906a496ee57224Alice Yang    public String getMessageTransforms(final Message msg) {
10401ebc2db723ed29093d724eb5da906a496ee57224Alice Yang        final String domId = mTemplates.getMessageDomId(msg);
10411ebc2db723ed29093d724eb5da906a496ee57224Alice Yang        return (domId == null) ? null : mMessageTransforms.get(domId);
10421ebc2db723ed29093d724eb5da906a496ee57224Alice Yang    }
10431ebc2db723ed29093d724eb5da906a496ee57224Alice Yang
10448e1ffbf042a23824a97a9f47cfc81cf6f14603beJames Lemieux    @Override
10458e1ffbf042a23824a97a9f47cfc81cf6f14603beJames Lemieux    public boolean isSecure() {
10468e1ffbf042a23824a97a9f47cfc81cf6f14603beJames Lemieux        return false;
10478e1ffbf042a23824a97a9f47cfc81cf6f14603beJames Lemieux    }
10488e1ffbf042a23824a97a9f47cfc81cf6f14603beJames Lemieux
10493233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang    // END message header callbacks
10505ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang
105146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    @Override
10522fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein    public void showUntransformedConversation() {
10532fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein        super.showUntransformedConversation();
10542fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein        renderConversation(getMessageCursor());
10552fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein    }
10562fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein
10572fc673050f03ec632d1c84d9cc82098a8b99a1c0Andrew Sapperstein    @Override
105846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    public void onSuperCollapsedClick(SuperCollapsedBlockItem item) {
1059f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        MessageCursor cursor = getMessageCursor();
1060f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        if (cursor == null || !mViewsCreated) {
106146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            return;
106246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        }
106346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
1064f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mTempBodiesHtml = renderCollapsedHeaders(cursor, item);
106546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        mWebView.loadUrl("javascript:replaceSuperCollapsedBlock(" + item.getStart() + ")");
10667ee90b37599cb6717782d40a6d085849918c29dfJin Cao        mConversationContainer.focusFirstMessageHeader();
106746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    }
106846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
106947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    private void showNewMessageNotification(NewMessagesInfo info) {
1070821fa87279d590e682effbdb652c8a92e805eec8Andrew Sapperstein        mNewMessageBar.setText(info.getNotificationText());
107147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        mNewMessageBar.setVisibility(View.VISIBLE);
107247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    }
107347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
107447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    private void onNewMessageBarClick() {
107547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        mNewMessageBar.setVisibility(View.GONE);
107647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
1077f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        renderConversation(getMessageCursor()); // mCursor is already up-to-date
1078f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                                                // per onLoadFinished()
10795fbda023f1b0570e192e03a33834244a05edf200Andy Huang    }
10805fbda023f1b0570e192e03a33834244a05edf200Andy Huang
1081e2b2eb7ef18ab1ba0260eed4a22930cea195c240Andrew Sapperstein    private static OverlayPosition[] parsePositions(final int[] topArray, final int[] bottomArray) {
1082adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang        final int len = topArray.length;
1083adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang        final OverlayPosition[] positions = new OverlayPosition[len];
1084b5078b287b1cec38817e342ff054ea901d199329Andy Huang        for (int i = 0; i < len; i++) {
1085e2b2eb7ef18ab1ba0260eed4a22930cea195c240Andrew Sapperstein            positions[i] = new OverlayPosition(topArray[i], bottomArray[i]);
1086b5078b287b1cec38817e342ff054ea901d199329Andy Huang        }
1087adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang        return positions;
1088b5078b287b1cec38817e342ff054ea901d199329Andy Huang    }
1089b5078b287b1cec38817e342ff054ea901d199329Andy Huang
10909f957f3463fd149d33c209409e2bba500b539177Andrew Sapperstein    protected Address getAddress(String rawFrom) {
10910dfae6942a834f32f031c466017539d43d06e466Paul Westbrook        return Utils.getAddress(mAddressCache, rawFrom);
10921617481de7c1f9b8dfcd25ba9828e933cf9d5490Andy Huang    }
10931617481de7c1f9b8dfcd25ba9828e933cf9d5490Andy Huang
10949d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    private void ensureContentSizeChangeListener() {
10959d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        if (mWebViewSizeChangeListener == null) {
1096c1fb9a9c2730178105977fca629e80951bfc3cdcAndy Huang            mWebViewSizeChangeListener = new ContentSizeChangeListener() {
10979d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                @Override
10989d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                public void onHeightChange(int h) {
10999d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                    // When WebKit says the DOM height has changed, re-measure
11009d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                    // bodies and re-position their headers.
11019d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                    // This is separate from the typical JavaScript DOM change
11029d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                    // listeners because cases like NARROW_COLUMNS text reflow do not trigger DOM
11039d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                    // events.
11049d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                    mWebView.loadUrl("javascript:measurePositions();");
11059d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                }
11069d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            };
11079d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        }
11089d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        mWebView.setContentSizeChangeListener(mWebViewSizeChangeListener);
11099d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    }
11109d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
11119f957f3463fd149d33c209409e2bba500b539177Andrew Sapperstein    public static boolean isOverviewMode(Account acct) {
1112ccf6780d9c80070beca4ade6e4084c3fb719af51Andy Huang        return acct.settings.isOverviewMode();
1113adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang    }
1114adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang
1115adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang    private void setupOverviewMode() {
111602f9d18a54072db8d86c524f9c09e508092ddd7cAndy Huang        // for now, overview mode means use the built-in WebView zoom and disable custom scale
111702f9d18a54072db8d86c524f9c09e508092ddd7cAndy Huang        // gesture handling
1118adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang        final boolean overviewMode = isOverviewMode(mAccount);
1119adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang        final WebSettings settings = mWebView.getSettings();
11204dc732387454eef3ee6d89f9fa393630eb6213f9Andy Huang        final WebSettings.LayoutAlgorithm layout;
112106def56be1f03acd2e5706ecf207ab147ce6f5c4Andy Huang        settings.setUseWideViewPort(overviewMode);
112257f354c02dbf56ebc893a570564906e61c31e09eAndy Huang        settings.setSupportZoom(overviewMode);
112357f354c02dbf56ebc893a570564906e61c31e09eAndy Huang        settings.setBuiltInZoomControls(overviewMode);
11244dc732387454eef3ee6d89f9fa393630eb6213f9Andy Huang        settings.setLoadWithOverviewMode(overviewMode);
112557f354c02dbf56ebc893a570564906e61c31e09eAndy Huang        if (overviewMode) {
112657f354c02dbf56ebc893a570564906e61c31e09eAndy Huang            settings.setDisplayZoomControls(false);
11274dc732387454eef3ee6d89f9fa393630eb6213f9Andy Huang            layout = WebSettings.LayoutAlgorithm.NORMAL;
11284dc732387454eef3ee6d89f9fa393630eb6213f9Andy Huang        } else {
11294dc732387454eef3ee6d89f9fa393630eb6213f9Andy Huang            layout = WebSettings.LayoutAlgorithm.NARROW_COLUMNS;
1130adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang        }
11314dc732387454eef3ee6d89f9fa393630eb6213f9Andy Huang        settings.setLayoutAlgorithm(layout);
1132adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang    }
1133adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang
11343c6fd44f9ae0cf60248dc64ee74d46afed633c45Andy Huang    @Override
1135833123d9c31b0b2dd23f7f74738c5bccf8a546d3Andrew Sapperstein    public ConversationMessage getMessageForClickedUrl(String url) {
11363c6fd44f9ae0cf60248dc64ee74d46afed633c45Andy Huang        final String domMessageId = mUrlToMessageIdMap.get(url);
11373c6fd44f9ae0cf60248dc64ee74d46afed633c45Andy Huang        if (domMessageId == null) {
11383c6fd44f9ae0cf60248dc64ee74d46afed633c45Andy Huang            return null;
11393c6fd44f9ae0cf60248dc64ee74d46afed633c45Andy Huang        }
11403c6fd44f9ae0cf60248dc64ee74d46afed633c45Andy Huang        final String messageId = mTemplates.getMessageIdForDomId(domMessageId);
11413c6fd44f9ae0cf60248dc64ee74d46afed633c45Andy Huang        return getMessageCursor().getMessageForId(Long.parseLong(messageId));
11423c6fd44f9ae0cf60248dc64ee74d46afed633c45Andy Huang    }
11433c6fd44f9ae0cf60248dc64ee74d46afed633c45Andy Huang
1144a7404589b03ac9dd0d07b3f7d0a1ec92ac9acb62Jin Cao    @Override
1145a7404589b03ac9dd0d07b3f7d0a1ec92ac9acb62Jin Cao    public boolean onKey(View view, int keyCode, KeyEvent keyEvent) {
11460b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao        if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
11470b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao            mOriginalKeyedView = view;
11480b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao        }
11490b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao
11500b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao        if (mOriginalKeyedView != null) {
11510b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao            final int id = mOriginalKeyedView.getId();
11520b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao            final boolean isActionUp = keyEvent.getAction() == KeyEvent.ACTION_UP;
11530b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao            final boolean isLeft = keyCode == KeyEvent.KEYCODE_DPAD_LEFT;
11540b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao            final boolean isRight = keyCode == KeyEvent.KEYCODE_DPAD_RIGHT;
11550b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao            final boolean isUp = keyCode == KeyEvent.KEYCODE_DPAD_UP;
11560b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao            final boolean isDown = keyCode == KeyEvent.KEYCODE_DPAD_DOWN;
11570b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao
11580b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao            // First we run the event by the controller
11590b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao            // We manually check if the view+direction combination should shift focus away from the
11600b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao            // conversation view to the thread list in two-pane landscape mode.
11610b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao            final boolean isTwoPaneLand = mNavigationController.isTwoPaneLandscape();
11620b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao            final boolean navigateAway = mConversationContainer.shouldNavigateAway(id, isLeft,
11630b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao                    isTwoPaneLand);
11640b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao            if (mNavigationController.onInterceptKeyFromCV(keyCode, keyEvent, navigateAway)) {
11650b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao                return true;
11660b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao            }
11670b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao
11680b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao            // If controller didn't handle the event, check directional interception.
11690b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao            if ((isLeft || isRight) && mConversationContainer.shouldInterceptLeftRightEvents(
11700b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao                    id, isLeft, isRight, isTwoPaneLand)) {
11710b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao                return true;
11720b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao            } else if (isUp || isDown) {
11737ee90b37599cb6717782d40a6d085849918c29dfJin Cao                // We don't do anything on up/down for overlay
11747ee90b37599cb6717782d40a6d085849918c29dfJin Cao                if (id == R.id.conversation_topmost_overlay) {
11757ee90b37599cb6717782d40a6d085849918c29dfJin Cao                    return true;
11767ee90b37599cb6717782d40a6d085849918c29dfJin Cao                }
11777ee90b37599cb6717782d40a6d085849918c29dfJin Cao
11780b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao                // We manually handle up/down navigation through the overlay items because the
11790b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao                // system's default isn't optimal for two-pane landscape since it's not a real list.
11800b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao                final int position = mConversationContainer.getViewPosition(mOriginalKeyedView);
11810b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao                final View next = mConversationContainer.getNextOverlayView(position, isDown);
11820b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao                if (next != null) {
11830b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao                    if (isActionUp) {
11840b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao                        next.requestFocus();
11850b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao
11860b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao                        // Make sure that v is in view
11870b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao                        final int[] coords = new int[2];
11880b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao                        next.getLocationOnScreen(coords);
11890b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao                        final int bottom = coords[1] + next.getHeight();
11900b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao                        if (bottom > mMaxScreenHeight) {
11910b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao                            mWebView.scrollBy(0, bottom - mMaxScreenHeight);
11920b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao                        } else if (coords[1] < mTopOfVisibleScreen) {
11930b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao                            mWebView.scrollBy(0, coords[1] - mTopOfVisibleScreen);
11940b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao                        }
11950b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao                    }
11960b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao                    return true;
11977ee90b37599cb6717782d40a6d085849918c29dfJin Cao                } else {
11987ee90b37599cb6717782d40a6d085849918c29dfJin Cao                    // Special case two end points
11997ee90b37599cb6717782d40a6d085849918c29dfJin Cao                    // Start is marked as index 1 because we are currently not allowing focus on
12007ee90b37599cb6717782d40a6d085849918c29dfJin Cao                    // conversation view header.
12017ee90b37599cb6717782d40a6d085849918c29dfJin Cao                    if ((position == mConversationContainer.getOverlayCount() - 1 && isDown) ||
12027ee90b37599cb6717782d40a6d085849918c29dfJin Cao                            (position == 1 && isUp)) {
12037ee90b37599cb6717782d40a6d085849918c29dfJin Cao                        mTopmostOverlay.requestFocus();
12047ee90b37599cb6717782d40a6d085849918c29dfJin Cao                        // Scroll to the the top if we hit the first item
12057ee90b37599cb6717782d40a6d085849918c29dfJin Cao                        if (isUp) {
12067ee90b37599cb6717782d40a6d085849918c29dfJin Cao                            mWebView.scrollTo(0, 0);
12077ee90b37599cb6717782d40a6d085849918c29dfJin Cao                        }
12087ee90b37599cb6717782d40a6d085849918c29dfJin Cao                        return true;
12097ee90b37599cb6717782d40a6d085849918c29dfJin Cao                    }
12100b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao                }
1211a7404589b03ac9dd0d07b3f7d0a1ec92ac9acb62Jin Cao            }
12120b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao
12130b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao            // Finally we handle the special keys
12140b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao            if (keyCode == KeyEvent.KEYCODE_BACK && id != R.id.conversation_topmost_overlay) {
12150b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao                if (isActionUp) {
12160b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao                    mTopmostOverlay.requestFocus();
12170b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao                }
12180b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao                return true;
12190b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao            } else if (keyCode == KeyEvent.KEYCODE_ENTER &&
12200b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao                    id == R.id.conversation_topmost_overlay) {
12210b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao                if (isActionUp) {
12220b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao                    mConversationContainer.focusFirstMessageHeader();
12237ee90b37599cb6717782d40a6d085849918c29dfJin Cao                    mWebView.scrollTo(0, 0);
12240b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao                }
12250b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao                return true;
1226a7404589b03ac9dd0d07b3f7d0a1ec92ac9acb62Jin Cao            }
1227a7404589b03ac9dd0d07b3f7d0a1ec92ac9acb62Jin Cao        }
1228a7404589b03ac9dd0d07b3f7d0a1ec92ac9acb62Jin Cao        return false;
1229a7404589b03ac9dd0d07b3f7d0a1ec92ac9acb62Jin Cao    }
1230a7404589b03ac9dd0d07b3f7d0a1ec92ac9acb62Jin Cao
1231b1d184d164303d83d342c60e4bffc732aa10ac2cAndrew Sapperstein    public class ConversationWebViewClient extends AbstractConversationWebViewClient {
12324ddda2f0a4ee5381a90779a6939b05b064ce5d11Andrew Sapperstein        public ConversationWebViewClient(Account account) {
12334ddda2f0a4ee5381a90779a6939b05b064ce5d11Andrew Sapperstein            super(account);
1234376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein        }
1235376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein
123617a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huang        @Override
123717a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huang        public void onPageFinished(WebView view, String url) {
12389a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang            // Ignore unsafe calls made after a fragment is detached from an activity.
12399a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang            // This method needs to, for example, get at the loader manager, which needs
12409a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang            // the fragment to be added.
12419a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang            if (!isAdded() || !mViewsCreated) {
1242006e13c4943d83ce3277d502f531236b903e94fdPaul Westbrook                LogUtils.d(LOG_TAG, "ignoring CVF.onPageFinished, url=%s fragment=%s", url,
1243b95da853ffbff343c8ca11470de2f0047e0807a6Andy Huang                        ConversationViewFragment.this);
1244b95da853ffbff343c8ca11470de2f0047e0807a6Andy Huang                return;
1245b95da853ffbff343c8ca11470de2f0047e0807a6Andy Huang            }
1246b95da853ffbff343c8ca11470de2f0047e0807a6Andy Huang
1247006e13c4943d83ce3277d502f531236b903e94fdPaul Westbrook            LogUtils.d(LOG_TAG, "IN CVF.onPageFinished, url=%s fragment=%s wv=%s t=%sms", url,
124830bcfe7b69f9d8e0cad2ee62dae9d3571cd0d88bAndy Huang                    ConversationViewFragment.this, view,
124963b3c6725d60386f564ab53e3ba6495f0c158d9bAndy Huang                    (SystemClock.uptimeMillis() - mWebViewLoadStartMs));
1250632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang
12519d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            ensureContentSizeChangeListener();
12529d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
12533bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp            if (!mEnableContentReadySignal) {
12547d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang                revealConversation();
12553bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp            }
12569d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
12579a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang            final Set<String> emailAddresses = Sets.newHashSet();
1258543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang            final List<Address> cacheCopy;
1259543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang            synchronized (mAddressCache) {
1260543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang                cacheCopy = ImmutableList.copyOf(mAddressCache.values());
1261543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang            }
1262543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang            for (Address addr : cacheCopy) {
12639a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang                emailAddresses.add(addr.getAddress());
1264b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang            }
12654ddda2f0a4ee5381a90779a6939b05b064ce5d11Andrew Sapperstein            final ContactLoaderCallbacks callbacks = getContactInfoSource();
12664ddda2f0a4ee5381a90779a6939b05b064ce5d11Andrew Sapperstein            callbacks.setSenders(emailAddresses);
12679a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang            getLoaderManager().restartLoader(CONTACT_LOADER, Bundle.EMPTY, callbacks);
126817a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huang        }
126917a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huang
1270af5d4e0e2a5ce3300fffa0c484431d83adb89ee8Andy Huang        @Override
1271af5d4e0e2a5ce3300fffa0c484431d83adb89ee8Andy Huang        public boolean shouldOverrideUrlLoading(WebView view, String url) {
1272542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            return mViewsCreated && super.shouldOverrideUrlLoading(view, url);
1273af5d4e0e2a5ce3300fffa0c484431d83adb89ee8Andy Huang        }
127417a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huang    }
127517a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huang
1276f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    /**
1277f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang     * NOTE: all public methods must be listed in the proguard flags so that they can be accessed
1278f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang     * via reflection and not stripped.
1279f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang     *
1280f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang     */
1281f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    private class MailJsBridge {
1282974c966c14000d42f089e2021b098d5cf61f9245Mindy Pereira        @JavascriptInterface
1283e2b2eb7ef18ab1ba0260eed4a22930cea195c240Andrew Sapperstein        public void onWebContentGeometryChange(final int[] overlayTopStrs,
1284e2b2eb7ef18ab1ba0260eed4a22930cea195c240Andrew Sapperstein                final int[] overlayBottomStrs) {
12858ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein            try {
12868ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                getHandler().post(new FragmentRunnable("onWebContentGeometryChange",
12878ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                        ConversationViewFragment.this) {
12888ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                    @Override
12898ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                    public void go() {
129046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                        if (!mViewsCreated) {
12911b3cc47f54072105c161d6ed557550e0e149b8bbmindyp                            LogUtils.d(LOG_TAG, "ignoring webContentGeometryChange because views"
12921b3cc47f54072105c161d6ed557550e0e149b8bbmindyp                                    + " are gone, %s", ConversationViewFragment.this);
129346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                            return;
129446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                        }
1295adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang                        mConversationContainer.onGeometryChange(
1296adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang                                parsePositions(overlayTopStrs, overlayBottomStrs));
12971b3cc47f54072105c161d6ed557550e0e149b8bbmindyp                        if (mDiff != 0) {
12981b3cc47f54072105c161d6ed557550e0e149b8bbmindyp                            // SCROLL!
12991b3cc47f54072105c161d6ed557550e0e149b8bbmindyp                            int scale = (int) (mWebView.getScale() / mWebView.getInitialScale());
13001b3cc47f54072105c161d6ed557550e0e149b8bbmindyp                            if (scale > 1) {
13011b3cc47f54072105c161d6ed557550e0e149b8bbmindyp                                mWebView.scrollBy(0, (mDiff * (scale - 1)));
13021b3cc47f54072105c161d6ed557550e0e149b8bbmindyp                            }
13031b3cc47f54072105c161d6ed557550e0e149b8bbmindyp                            mDiff = 0;
13041b3cc47f54072105c161d6ed557550e0e149b8bbmindyp                        }
130551067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang                    }
13068ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                });
13078ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein            } catch (Throwable t) {
13088ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                LogUtils.e(LOG_TAG, t, "Error in MailJsBridge.onWebContentGeometryChange");
13098ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein            }
131046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        }
131151067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang
1312974c966c14000d42f089e2021b098d5cf61f9245Mindy Pereira        @JavascriptInterface
131346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        public String getTempMessageBodies() {
131446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            try {
131546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                if (!mViewsCreated) {
131646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                    return "";
1317f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang                }
131846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
131946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                final String s = mTempBodiesHtml;
132046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                mTempBodiesHtml = null;
132146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                return s;
132246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            } catch (Throwable t) {
132346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                LogUtils.e(LOG_TAG, t, "Error in MailJsBridge.getTempMessageBodies");
132446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                return "";
132546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            }
1326f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        }
1327f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
1328014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang        @JavascriptInterface
1329014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang        public String getMessageBody(String domId) {
1330014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang            try {
1331014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                final MessageCursor cursor = getMessageCursor();
1332014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                if (!mViewsCreated || cursor == null) {
1333014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                    return "";
1334014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                }
1335014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang
1336014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                int pos = -1;
1337014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                while (cursor.moveToPosition(++pos)) {
1338014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                    final ConversationMessage msg = cursor.getMessage();
1339014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                    if (TextUtils.equals(domId, mTemplates.getMessageDomId(msg))) {
1340986776bbd046c9569a4abb67501819bee61e7194Andy Huang                        return HtmlConversationTemplates.wrapMessageBody(msg.getBodyAsHtml());
1341014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                    }
1342014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                }
1343014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang
1344014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                return "";
1345014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang
1346014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang            } catch (Throwable t) {
1347014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                LogUtils.e(LOG_TAG, t, "Error in MailJsBridge.getMessageBody");
1348014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                return "";
1349014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang            }
1350014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang        }
1351014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang
1352974c966c14000d42f089e2021b098d5cf61f9245Mindy Pereira        @JavascriptInterface
1353543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang        public String getMessageSender(String domId) {
1354543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang            try {
1355543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang                final MessageCursor cursor = getMessageCursor();
1356543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang                if (!mViewsCreated || cursor == null) {
1357543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang                    return "";
1358543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang                }
1359543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang
1360543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang                int pos = -1;
1361543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang                while (cursor.moveToPosition(++pos)) {
1362543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang                    final ConversationMessage msg = cursor.getMessage();
1363543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang                    if (TextUtils.equals(domId, mTemplates.getMessageDomId(msg))) {
1364543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang                        return getAddress(msg.getFrom()).getAddress();
1365543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang                    }
1366543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang                }
1367543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang
1368543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang                return "";
1369543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang
1370543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang            } catch (Throwable t) {
1371543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang                LogUtils.e(LOG_TAG, t, "Error in MailJsBridge.getMessageSender");
1372543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang                return "";
1373543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang            }
1374543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang        }
1375543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang
1376543e709c976ce954a072020ba6f75d12f41b1fbaAndy Huang        @JavascriptInterface
13773bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp        public void onContentReady() {
13788ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein            try {
13798ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                getHandler().post(new FragmentRunnable("onContentReady",
13808ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                        ConversationViewFragment.this) {
13818ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                    @Override
13828ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                    public void go() {
13838ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                        try {
13848ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                            if (mWebViewLoadStartMs != 0) {
13858ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                                LogUtils.i(LOG_TAG, "IN CVF.onContentReady, f=%s vis=%s t=%sms",
13868ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                                        ConversationViewFragment.this,
13878ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                                        isUserVisible(),
13888ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                                        (SystemClock.uptimeMillis() - mWebViewLoadStartMs));
13898ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                            }
13908ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                            revealConversation();
13918ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                        } catch (Throwable t) {
13928ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                            LogUtils.e(LOG_TAG, t, "Error in MailJsBridge.onContentReady");
13938ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                            // Still try to show the conversation.
13948ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                            revealConversation();
139563b3c6725d60386f564ab53e3ba6495f0c158d9bAndy Huang                        }
13963bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp                    }
13978ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                });
13988ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein            } catch (Throwable t) {
13998ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                LogUtils.e(LOG_TAG, t, "Error in MailJsBridge.onContentReady");
14008ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein            }
14013bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp        }
1402e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang
1403e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang        @JavascriptInterface
1404e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang        public float getScrollYPercent() {
1405e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang            try {
1406e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang                return mWebViewYPercent;
1407e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang            } catch (Throwable t) {
1408e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang                LogUtils.e(LOG_TAG, t, "Error in MailJsBridge.getScrollYPercent");
1409e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang                return 0f;
1410e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang            }
1411e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang        }
141205c70c88e6b421bedce134799a0ea928dbf44cd5Andy Huang
141305c70c88e6b421bedce134799a0ea928dbf44cd5Andy Huang        @JavascriptInterface
141405c70c88e6b421bedce134799a0ea928dbf44cd5Andy Huang        public void onMessageTransform(String messageDomId, String transformText) {
1415ae92e15b48a00613e06ea4c6b274bd73a4e6ec40Andrew Sapperstein            try {
1416ae92e15b48a00613e06ea4c6b274bd73a4e6ec40Andrew Sapperstein                LogUtils.i(LOG_TAG, "TRANSFORM: (%s) %s", messageDomId, transformText);
1417ae92e15b48a00613e06ea4c6b274bd73a4e6ec40Andrew Sapperstein                mMessageTransforms.put(messageDomId, transformText);
1418ae92e15b48a00613e06ea4c6b274bd73a4e6ec40Andrew Sapperstein                onConversationTransformed();
1419ae92e15b48a00613e06ea4c6b274bd73a4e6ec40Andrew Sapperstein            } catch (Throwable t) {
1420ae92e15b48a00613e06ea4c6b274bd73a4e6ec40Andrew Sapperstein                LogUtils.e(LOG_TAG, t, "Error in MailJsBridge.onMessageTransform");
14218ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein            }
14228ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein        }
14238ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein
14248ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein        @JavascriptInterface
14258ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein        public void onInlineAttachmentsParsed(final String[] urls, final String[] messageIds) {
14268ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein            try {
14278ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                getHandler().post(new FragmentRunnable("onInlineAttachmentsParsed",
14288ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                        ConversationViewFragment.this) {
14298ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                    @Override
14308ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                    public void go() {
14318ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                        try {
14328ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                            for (int i = 0, size = urls.length; i < size; i++) {
14338ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                                mUrlToMessageIdMap.put(urls[i], messageIds[i]);
14348ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                            }
14358ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                        } catch (ArrayIndexOutOfBoundsException e) {
14368ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                            LogUtils.e(LOG_TAG, e,
14378ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                                    "Number of urls does not match number of message ids - %s:%s",
14388ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                                    urls.length, messageIds.length);
14398ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                        }
14408ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                    }
14418ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                });
14428ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein            } catch (Throwable t) {
14438ec43e877a9c1925514f066655984e21fbd255e8Andrew Sapperstein                LogUtils.e(LOG_TAG, t, "Error in MailJsBridge.onInlineAttachmentsParsed");
1444ae92e15b48a00613e06ea4c6b274bd73a4e6ec40Andrew Sapperstein            }
144505c70c88e6b421bedce134799a0ea928dbf44cd5Andy Huang        }
1446674afa42682908640167fc6109b76f6f843e6fbeMindy Pereira    }
1447f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
144847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    private class NewMessagesInfo {
144947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        int count;
145006c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang        int countFromSelf;
145147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        String senderAddress;
145247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
145347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        /**
145447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang         * Return the display text for the new message notification overlay. It will be formatted
145547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang         * appropriately for a single new message vs. multiple new messages.
145647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang         *
145747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang         * @return display text
145847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang         */
145947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        public String getNotificationText() {
1460ad0c30d0517e06c472c76b11795092f5e67d8f5amindyp            Resources res = getResources();
146147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang            if (count > 1) {
146266d6911d57495b7010abeb5576a15f1521af443dAndrew Sapperstein                return res.getQuantityString(R.plurals.new_incoming_messages_many, count, count);
146347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang            } else {
14641617481de7c1f9b8dfcd25ba9828e933cf9d5490Andy Huang                final Address addr = getAddress(senderAddress);
1465ad0c30d0517e06c472c76b11795092f5e67d8f5amindyp                return res.getString(R.string.new_incoming_messages_one,
14662fd167d6131482da984768b5ee75cefa32ed3e32Andrew Sapperstein                        mBidiFormatter.unicodeWrap(TextUtils.isEmpty(addr.getPersonal())
14672fd167d6131482da984768b5ee75cefa32ed3e32Andrew Sapperstein                                ? addr.getAddress() : addr.getPersonal()));
146847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang            }
146947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        }
147047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    }
147147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
1472f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
1473c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook    public void onMessageCursorLoadFinished(Loader<ObjectCursor<ConversationMessage>> loader,
1474c42ad5ea3a49e6045a79d1fde3d858033176e67bPaul Westbrook            MessageCursor newCursor, MessageCursor oldCursor) {
1475f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        /*
1476f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp         * what kind of changes affect the MessageCursor? 1. new message(s) 2.
1477f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp         * read/unread state change 3. deleted message, either regular or draft
1478f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp         * 4. updated message, either from self or from others, updated in
1479f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp         * content or state or sender 5. star/unstar of message (technically
1480f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp         * similar to #1) 6. other label change Use MessageCursor.hashCode() to
1481f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp         * sort out interesting vs. no-op cursor updates.
1482f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp         */
1483014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang
1484233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang        if (oldCursor != null && !oldCursor.isClosed()) {
1485014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang            final NewMessagesInfo info = getNewIncomingMessagesInfo(newCursor);
1486014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang
1487014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang            if (info.count > 0) {
1488014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                // don't immediately render new incoming messages from other
1489014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                // senders
1490014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                // (to avoid a new message from losing the user's focus)
1491014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                LogUtils.i(LOG_TAG, "CONV RENDER: conversation updated"
14929d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                        + ", holding cursor for new incoming message (%s)", this);
1493014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                showNewMessageNotification(info);
1494014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                return;
1495014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang            }
1496014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang
149706c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang            final int oldState = oldCursor.getStateHashCode();
149806c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang            final boolean changed = newCursor.getStateHashCode() != oldState;
1499233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang
1500014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang            if (!changed) {
1501014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                final boolean processedInPlace = processInPlaceUpdates(newCursor, oldCursor);
1502014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                if (processedInPlace) {
15039d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                    LogUtils.i(LOG_TAG, "CONV RENDER: processed update(s) in place (%s)", this);
15041ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang                } else {
1505f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    LogUtils.i(LOG_TAG, "CONV RENDER: uninteresting update"
15069d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                            + ", ignoring this conversation update (%s)", this);
15071ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang                }
15081ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang                return;
150906c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang            } else if (info.countFromSelf == 1) {
151006c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang                // Special-case the very common case of a new cursor that is the same as the old
151106c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang                // one, except that there is a new message from yourself. This happens upon send.
151206c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang                final boolean sameExceptNewLast = newCursor.getStateHashCode(1) == oldState;
151306c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang                if (sameExceptNewLast) {
151406c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang                    LogUtils.i(LOG_TAG, "CONV RENDER: update is a single new message from self"
151506c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang                            + " (%s)", this);
151606c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang                    newCursor.moveToLast();
151706c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang                    processNewOutgoingMessage(newCursor.getMessage());
151806c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang                    return;
151906c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang                }
15201ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang            }
15216766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang            // cursors are different, and not due to an incoming message. fall
15226766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang            // through and render.
15236766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang            LogUtils.i(LOG_TAG, "CONV RENDER: conversation updated"
15246766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang                    + ", but not due to incoming message. rendering. (%s)", this);
152506c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang
152606c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang            if (DEBUG_DUMP_CURSOR_CONTENTS) {
152706c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang                LogUtils.i(LOG_TAG, "old cursor: %s", oldCursor.getDebugDump());
152806c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang                LogUtils.i(LOG_TAG, "new cursor: %s", newCursor.getDebugDump());
152906c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang            }
15306766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang        } else {
15316766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang            LogUtils.i(LOG_TAG, "CONV RENDER: initial render. (%s)", this);
1532243c23618b066bcdcd0ab9e36d8c01f50db2cbd0Andy Huang            timerMark("message cursor load finished");
1533b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang        }
1534b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang
1535606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein        renderContent(newCursor);
1536606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein    }
1537606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein
1538606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein    protected void renderContent(MessageCursor messageCursor) {
15394071c2f73218ce75750345557bb31a9110737841Mark Wei        // if layout hasn't happened, delay render
15404071c2f73218ce75750345557bb31a9110737841Mark Wei        // This is needed in addition to the showConversation() delay to speed
15414071c2f73218ce75750345557bb31a9110737841Mark Wei        // up rotation and restoration.
15424071c2f73218ce75750345557bb31a9110737841Mark Wei        if (mConversationContainer.getWidth() == 0) {
15434071c2f73218ce75750345557bb31a9110737841Mark Wei            mNeedRender = true;
15444071c2f73218ce75750345557bb31a9110737841Mark Wei            mConversationContainer.addOnLayoutChangeListener(this);
15454071c2f73218ce75750345557bb31a9110737841Mark Wei        } else {
1546606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein            renderConversation(messageCursor);
15474071c2f73218ce75750345557bb31a9110737841Mark Wei        }
1548b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang    }
1549b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang
1550f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    private NewMessagesInfo getNewIncomingMessagesInfo(MessageCursor newCursor) {
1551f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        final NewMessagesInfo info = new NewMessagesInfo();
1552b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang
1553f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        int pos = -1;
1554f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        while (newCursor.moveToPosition(++pos)) {
1555f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            final Message m = newCursor.getMessage();
1556f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            if (!mViewState.contains(m)) {
1557f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                LogUtils.i(LOG_TAG, "conversation diff: found new msg: %s", m.uri);
1558f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
15598960f0af431bc164003e09b3c8981aab808d9ec1Scott Kennedy                final Address from = getAddress(m.getFrom());
1560f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                // distinguish ours from theirs
1561f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                // new messages from the account owner should not trigger a
1562f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                // notification
15631353da4b3075af7d5bb8f6bcd0dcb5bb3f8afdabPaul Westbrook                if (from == null || mAccount.ownsFromAddress(from.getAddress())) {
1564f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    LogUtils.i(LOG_TAG, "found message from self: %s", m.uri);
156506c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang                    info.countFromSelf++;
1566f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    continue;
1567f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                }
1568b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang
1569f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                info.count++;
15708960f0af431bc164003e09b3c8981aab808d9ec1Scott Kennedy                info.senderAddress = m.getFrom();
1571b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang            }
1572b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang        }
1573f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return info;
1574b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang    }
1575b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang
1576014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang    private boolean processInPlaceUpdates(MessageCursor newCursor, MessageCursor oldCursor) {
1577014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang        final Set<String> idsOfChangedBodies = Sets.newHashSet();
15786b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang        final List<Integer> changedOverlayPositions = Lists.newArrayList();
15796b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang
1580014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang        boolean changed = false;
1581014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang
1582014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang        int pos = 0;
1583014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang        while (true) {
1584014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang            if (!newCursor.moveToPosition(pos) || !oldCursor.moveToPosition(pos)) {
1585014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                break;
1586014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang            }
1587014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang
1588014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang            final ConversationMessage newMsg = newCursor.getMessage();
1589014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang            final ConversationMessage oldMsg = oldCursor.getMessage();
1590014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang
15912ef5f08719a9981d73f7954ce82a78ea6842f187Jin Cao            // We are going to update the data in the adapter whenever any input fields change.
15922ef5f08719a9981d73f7954ce82a78ea6842f187Jin Cao            // This ensures that the Message object that ComposeActivity uses will be correctly
15932ef5f08719a9981d73f7954ce82a78ea6842f187Jin Cao            // aligned with the most up-to-date data.
15942ef5f08719a9981d73f7954ce82a78ea6842f187Jin Cao            if (!newMsg.isEqual(oldMsg)) {
15956b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang                mAdapter.updateItemsForMessage(newMsg, changedOverlayPositions);
15966a2df258316b267151296556dbbdba20200ecb1fJin Cao                LogUtils.i(LOG_TAG, "msg #%d (%d): detected field(s) change. sendingState=%s",
15976a2df258316b267151296556dbbdba20200ecb1fJin Cao                        pos, newMsg.id, newMsg.sendingState);
1598014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang            }
1599014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang
1600014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang            // update changed message bodies in-place
1601014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang            if (!TextUtils.equals(newMsg.bodyHtml, oldMsg.bodyHtml) ||
1602014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                    !TextUtils.equals(newMsg.bodyText, oldMsg.bodyText)) {
1603014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                // maybe just set a flag to notify JS to re-request changed bodies
1604014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                idsOfChangedBodies.add('"' + mTemplates.getMessageDomId(newMsg) + '"');
1605014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                LogUtils.i(LOG_TAG, "msg #%d (%d): detected body change", pos, newMsg.id);
1606014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang            }
1607014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang
1608014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang            pos++;
1609014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang        }
1610014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang
16116b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang
16126b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang        if (!changedOverlayPositions.isEmpty()) {
161306c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang            // notify once after the entire adapter is updated
16146b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang            mConversationContainer.onOverlayModelUpdate(changedOverlayPositions);
16156b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang            changed = true;
161606c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang        }
161706c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang
1618735a22a197215ec4787ad9f3cbaf465cce54f4d0Andrew Sapperstein        final ConversationFooterItem footerItem = mAdapter.getFooterItem();
1619735a22a197215ec4787ad9f3cbaf465cce54f4d0Andrew Sapperstein        if (footerItem != null) {
1620735a22a197215ec4787ad9f3cbaf465cce54f4d0Andrew Sapperstein            footerItem.invalidateMeasurement();
1621735a22a197215ec4787ad9f3cbaf465cce54f4d0Andrew Sapperstein        }
1622014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang        if (!idsOfChangedBodies.isEmpty()) {
1623014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang            mWebView.loadUrl(String.format("javascript:replaceMessageBodies([%s]);",
1624014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                    TextUtils.join(",", idsOfChangedBodies)));
1625014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang            changed = true;
1626014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang        }
1627014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang
1628014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang        return changed;
1629014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang    }
1630014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang
163106c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang    private void processNewOutgoingMessage(ConversationMessage msg) {
1632e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein        // Temporarily remove the ConversationFooterItem and its view.
1633e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein        // It will get re-added right after the new message is added.
1634e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein        final ConversationFooterItem footerItem = mAdapter.removeFooterItem();
1635e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein        mConversationContainer.removeViewAtAdapterIndex(footerItem.getPosition());
163606c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang        mTemplates.reset();
163706c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang        // this method will add some items to mAdapter, but we deliberately want to avoid notifying
163806c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang        // adapter listeners (i.e. ConversationContainer) until onWebContentGeometryChange is next
163906c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang        // called, to prevent N+1 headers rendering with N message bodies.
1640e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein        renderMessage(msg, true /* expanded */, msg.alwaysShowImages);
164106c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang        mTempBodiesHtml = mTemplates.emit();
164206c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang
1643e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein        if (footerItem != null) {
1644e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein            footerItem.setLastMessageHeaderItem(getLastMessageHeaderItem());
1645735a22a197215ec4787ad9f3cbaf465cce54f4d0Andrew Sapperstein            footerItem.invalidateMeasurement();
1646e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein            mAdapter.addItem(footerItem);
1647e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein        }
1648e2a30e19a9fff0e4368c4ec36280a3fcd4ca03e2Andrew Sapperstein
164906c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang        mViewState.setExpansionState(msg, ExpansionState.EXPANDED);
165006c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang        // FIXME: should the provider set this as initial state?
165106c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang        mViewState.setReadState(msg, false /* read */);
165206c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang
165391d782abc8015bd651fb5d0252b4d1ef369ec57bAndy Huang        // From now until the updated spacer geometry is returned, the adapter items are mismatched
165491d782abc8015bd651fb5d0252b4d1ef369ec57bAndy Huang        // with the existing spacers. Do not let them layout.
165591d782abc8015bd651fb5d0252b4d1ef369ec57bAndy Huang        mConversationContainer.invalidateSpacerGeometry();
165691d782abc8015bd651fb5d0252b4d1ef369ec57bAndy Huang
165706c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang        mWebView.loadUrl("javascript:appendMessageHtml();");
165806c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang    }
165906c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang
1660f59d01c3116dc2adde97a5b52aa6094144c2d315Andrew Sapperstein    private static class SetCookieTask extends AsyncTask<Void, Void, Void> {
1661f59d01c3116dc2adde97a5b52aa6094144c2d315Andrew Sapperstein        private final Context mContext;
1662f59d01c3116dc2adde97a5b52aa6094144c2d315Andrew Sapperstein        private final String mUri;
1663f59d01c3116dc2adde97a5b52aa6094144c2d315Andrew Sapperstein        private final Uri mAccountCookieQueryUri;
1664f59d01c3116dc2adde97a5b52aa6094144c2d315Andrew Sapperstein        private final ContentResolver mResolver;
1665cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook
1666f59d01c3116dc2adde97a5b52aa6094144c2d315Andrew Sapperstein        /* package */ SetCookieTask(Context context, String baseUri, Uri accountCookieQueryUri) {
1667f59d01c3116dc2adde97a5b52aa6094144c2d315Andrew Sapperstein            mContext = context;
1668f59d01c3116dc2adde97a5b52aa6094144c2d315Andrew Sapperstein            mUri = baseUri;
1669b8361c9f8938b74977316319885998aae09aab77Paul Westbrook            mAccountCookieQueryUri = accountCookieQueryUri;
1670b8361c9f8938b74977316319885998aae09aab77Paul Westbrook            mResolver = context.getContentResolver();
1671cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook        }
1672cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook
1673cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook        @Override
1674cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook        public Void doInBackground(Void... args) {
1675f59d01c3116dc2adde97a5b52aa6094144c2d315Andrew Sapperstein            // First query for the cookie string from the UI provider
1676b8361c9f8938b74977316319885998aae09aab77Paul Westbrook            final Cursor cookieCursor = mResolver.query(mAccountCookieQueryUri,
1677b8361c9f8938b74977316319885998aae09aab77Paul Westbrook                    UIProvider.ACCOUNT_COOKIE_PROJECTION, null, null, null);
1678b8361c9f8938b74977316319885998aae09aab77Paul Westbrook            if (cookieCursor == null) {
1679b8361c9f8938b74977316319885998aae09aab77Paul Westbrook                return null;
1680b8361c9f8938b74977316319885998aae09aab77Paul Westbrook            }
1681b8361c9f8938b74977316319885998aae09aab77Paul Westbrook
1682b8361c9f8938b74977316319885998aae09aab77Paul Westbrook            try {
1683b8361c9f8938b74977316319885998aae09aab77Paul Westbrook                if (cookieCursor.moveToFirst()) {
1684b8361c9f8938b74977316319885998aae09aab77Paul Westbrook                    final String cookie = cookieCursor.getString(
1685b8361c9f8938b74977316319885998aae09aab77Paul Westbrook                            cookieCursor.getColumnIndex(UIProvider.AccountCookieColumns.COOKIE));
1686b8361c9f8938b74977316319885998aae09aab77Paul Westbrook
1687b8361c9f8938b74977316319885998aae09aab77Paul Westbrook                    if (cookie != null) {
1688b8361c9f8938b74977316319885998aae09aab77Paul Westbrook                        final CookieSyncManager csm =
1689f59d01c3116dc2adde97a5b52aa6094144c2d315Andrew Sapperstein                                CookieSyncManager.createInstance(mContext);
1690b8361c9f8938b74977316319885998aae09aab77Paul Westbrook                        CookieManager.getInstance().setCookie(mUri, cookie);
1691b8361c9f8938b74977316319885998aae09aab77Paul Westbrook                        csm.sync();
1692b8361c9f8938b74977316319885998aae09aab77Paul Westbrook                    }
1693b8361c9f8938b74977316319885998aae09aab77Paul Westbrook                }
1694b8361c9f8938b74977316319885998aae09aab77Paul Westbrook
1695b8361c9f8938b74977316319885998aae09aab77Paul Westbrook            } finally {
1696b8361c9f8938b74977316319885998aae09aab77Paul Westbrook                cookieCursor.close();
1697b8361c9f8938b74977316319885998aae09aab77Paul Westbrook            }
1698b8361c9f8938b74977316319885998aae09aab77Paul Westbrook
1699b8361c9f8938b74977316319885998aae09aab77Paul Westbrook
1700cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook            return null;
1701cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook        }
1702cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook    }
170336280f3c2939bb2dacb4077bd528900346ff4bb9mindyp
170426d4d2d9c43c499f458808f050ec73ea3c28dec4mindyp    @Override
170536280f3c2939bb2dacb4077bd528900346ff4bb9mindyp    public void onConversationUpdated(Conversation conv) {
170636280f3c2939bb2dacb4077bd528900346ff4bb9mindyp        final ConversationViewHeader headerView = (ConversationViewHeader) mConversationContainer
170736280f3c2939bb2dacb4077bd528900346ff4bb9mindyp                .findViewById(R.id.conversation_header);
1708b2b98ba97983f225eec19dc9bc5333e6ef80ee15mindyp        mConversation = conv;
17099e0b236be55cf9dab8f1670a9d91ed16e055a9d0mindyp        if (headerView != null) {
17109e0b236be55cf9dab8f1670a9d91ed16e055a9d0mindyp            headerView.onConversationUpdated(conv);
17119e0b236be55cf9dab8f1670a9d91ed16e055a9d0mindyp        }
171236280f3c2939bb2dacb4077bd528900346ff4bb9mindyp    }
17134071c2f73218ce75750345557bb31a9110737841Mark Wei
17144071c2f73218ce75750345557bb31a9110737841Mark Wei    @Override
17154071c2f73218ce75750345557bb31a9110737841Mark Wei    public void onLayoutChange(View v, int left, int top, int right,
17164071c2f73218ce75750345557bb31a9110737841Mark Wei            int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
17174071c2f73218ce75750345557bb31a9110737841Mark Wei        boolean sizeChanged = mNeedRender
17184071c2f73218ce75750345557bb31a9110737841Mark Wei                && mConversationContainer.getWidth() != 0;
17194071c2f73218ce75750345557bb31a9110737841Mark Wei        if (sizeChanged) {
17204071c2f73218ce75750345557bb31a9110737841Mark Wei            mNeedRender = false;
17214071c2f73218ce75750345557bb31a9110737841Mark Wei            mConversationContainer.removeOnLayoutChangeListener(this);
17224071c2f73218ce75750345557bb31a9110737841Mark Wei            renderConversation(getMessageCursor());
17234071c2f73218ce75750345557bb31a9110737841Mark Wei        }
17244071c2f73218ce75750345557bb31a9110737841Mark Wei    }
17251b3cc47f54072105c161d6ed557550e0e149b8bbmindyp
17261b3cc47f54072105c161d6ed557550e0e149b8bbmindyp    @Override
17277cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux    public void setMessageDetailsExpanded(MessageHeaderItem i, boolean expanded, int heightBefore) {
17281b3cc47f54072105c161d6ed557550e0e149b8bbmindyp        mDiff = (expanded ? 1 : -1) * Math.abs(i.getHeight() - heightBefore);
17291b3cc47f54072105c161d6ed557550e0e149b8bbmindyp    }
173002f9d18a54072db8d86c524f9c09e508092ddd7cAndy Huang
17317cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux    /**
17327cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux     * @return {@code true} because either the Print or Print All menu item is shown in GMail
17337cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux     */
17347cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux    @Override
17357cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux    protected boolean shouldShowPrintInOverflow() {
17367cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux        return true;
17377cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux    }
17387cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux
17397cad28070b4e903e4ce05e5fc610988f71630f97James Lemieux    @Override
17405c1692a5faeab220881a17a3427a8986ef874403Andrew Sapperstein    protected void printConversation() {
1741234d35352eeaaa8d3928e31028be49112607bd29Andrew Sapperstein        PrintUtils.printConversation(mActivity.getActivityContext(), getMessageCursor(),
1742234d35352eeaaa8d3928e31028be49112607bd29Andrew Sapperstein                mAddressCache, mConversation.getBaseUri(mBaseUri), true /* useJavascript */);
17435c1692a5faeab220881a17a3427a8986ef874403Andrew Sapperstein    }
17449b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira}
1745