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