ConversationViewFragment.java revision 6b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83
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
20f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
21b8361c9f8938b74977316319885998aae09aab77Paul Westbrookimport android.content.ContentResolver;
229b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereiraimport android.content.Context;
238e915724b6e4374da9b70161ee0a55f0c763e563Mindy Pereiraimport android.content.Loader;
24ad0c30d0517e06c472c76b11795092f5e67d8f5amindypimport android.content.res.Resources;
259b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereiraimport android.database.Cursor;
269d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huangimport android.database.DataSetObserver;
27b8361c9f8938b74977316319885998aae09aab77Paul Westbrookimport android.net.Uri;
28cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrookimport android.os.AsyncTask;
299b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereiraimport android.os.Bundle;
303bcf180f8104bc27319086a9a6ece5a3c2917c37mindypimport android.os.SystemClock;
3147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huangimport android.text.TextUtils;
329b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereiraimport android.view.LayoutInflater;
339b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereiraimport android.view.View;
344071c2f73218ce75750345557bb31a9110737841Mark Weiimport android.view.View.OnLayoutChangeListener;
359b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereiraimport android.view.ViewGroup;
36f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport android.webkit.ConsoleMessage;
37cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrookimport android.webkit.CookieManager;
38cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrookimport android.webkit.CookieSyncManager;
39974c966c14000d42f089e2021b098d5cf61f9245Mindy Pereiraimport android.webkit.JavascriptInterface;
40f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport android.webkit.WebChromeClient;
41f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport android.webkit.WebSettings;
4217a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huangimport android.webkit.WebView;
4317a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huangimport android.webkit.WebViewClient;
4447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huangimport android.widget.TextView;
459b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira
4659e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huangimport com.android.mail.FormattedDateBuilder;
479b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereiraimport com.android.mail.R;
485ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huangimport com.android.mail.browse.ConversationContainer;
49adbf3e8cadb66666f307352b72537fbac57b916fAndy Huangimport com.android.mail.browse.ConversationContainer.OverlayPosition;
5046dfba6160b55a582b344328067e3dafeb881dd9Andy Huangimport com.android.mail.browse.ConversationOverlayItem;
517bdc3750454efe59617b7df945eadd7e59bee954Andy Huangimport com.android.mail.browse.ConversationViewAdapter;
5228b7aee7fa1f7d096d33fc823a88a64f7a3fa79dAndy Huangimport com.android.mail.browse.ConversationViewAdapter.ConversationAccountController;
5346dfba6160b55a582b344328067e3dafeb881dd9Andy Huangimport com.android.mail.browse.ConversationViewAdapter.MessageFooterItem;
547bdc3750454efe59617b7df945eadd7e59bee954Andy Huangimport com.android.mail.browse.ConversationViewAdapter.MessageHeaderItem;
5546dfba6160b55a582b344328067e3dafeb881dd9Andy Huangimport com.android.mail.browse.ConversationViewAdapter.SuperCollapsedBlockItem;
565ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huangimport com.android.mail.browse.ConversationViewHeader;
575ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huangimport com.android.mail.browse.ConversationWebView;
58dde3f9ff76c1a7f9a6e9fa959a6422a8a8d82e81mindypimport com.android.mail.browse.ConversationWebView.ContentSizeChangeListener;
597bdc3750454efe59617b7df945eadd7e59bee954Andy Huangimport com.android.mail.browse.MessageCursor;
60cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huangimport com.android.mail.browse.MessageCursor.ConversationController;
6128b7aee7fa1f7d096d33fc823a88a64f7a3fa79dAndy Huangimport com.android.mail.browse.MessageCursor.ConversationMessage;
6259e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huangimport com.android.mail.browse.MessageHeaderView;
633233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huangimport com.android.mail.browse.MessageHeaderView.MessageHeaderViewCallbacks;
64adbf3e8cadb66666f307352b72537fbac57b916fAndy Huangimport com.android.mail.browse.ScrollIndicatorsView;
6546dfba6160b55a582b344328067e3dafeb881dd9Andy Huangimport com.android.mail.browse.SuperCollapsedBlock;
660b7ed6fae6e36f2abc4ca916764177d1879731b4Andy Huangimport com.android.mail.browse.WebViewContextMenu;
679b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereiraimport com.android.mail.providers.Account;
6865fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huangimport com.android.mail.providers.Address;
699b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereiraimport com.android.mail.providers.Conversation;
70f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport com.android.mail.providers.Message;
71b8361c9f8938b74977316319885998aae09aab77Paul Westbrookimport com.android.mail.providers.UIProvider;
72cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huangimport com.android.mail.ui.ConversationViewState.ExpansionState;
73b334c9035e9b7a38766bb66c29da2208525d1e11Paul Westbrookimport com.android.mail.utils.LogTag;
749b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereiraimport com.android.mail.utils.LogUtils;
752e9acfe527426c00b5df2ca66189262dc40c8575Andy Huangimport com.android.mail.utils.Utils;
7646dfba6160b55a582b344328067e3dafeb881dd9Andy Huangimport com.google.common.collect.Lists;
77b8331b4565566ca733997398e8c07a26cd2bee98Andy Huangimport com.google.common.collect.Sets;
7865fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huang
7946dfba6160b55a582b344328067e3dafeb881dd9Andy Huangimport java.util.List;
80b8331b4565566ca733997398e8c07a26cd2bee98Andy Huangimport java.util.Set;
819b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira
82f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
839b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira/**
849b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira * The conversation view UI component.
859b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira */
86f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyppublic final class ConversationViewFragment extends AbstractConversationViewFragment implements
8746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        MessageHeaderViewCallbacks,
88cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang        SuperCollapsedBlock.OnClickListener,
8928b7aee7fa1f7d096d33fc823a88a64f7a3fa79dAndy Huang        ConversationController,
904071c2f73218ce75750345557bb31a9110737841Mark Wei        ConversationAccountController,
914071c2f73218ce75750345557bb31a9110737841Mark Wei        OnLayoutChangeListener {
928e915724b6e4374da9b70161ee0a55f0c763e563Mindy Pereira
93b334c9035e9b7a38766bb66c29da2208525d1e11Paul Westbrook    private static final String LOG_TAG = LogTag.getLogTag();
94632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang    public static final String LAYOUT_TAG = "ConvLayout";
959b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira
969d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    /**
971b3cc47f54072105c161d6ed557550e0e149b8bbmindyp     * Difference in the height of the message header whose details have been expanded/collapsed
981b3cc47f54072105c161d6ed557550e0e149b8bbmindyp     */
991b3cc47f54072105c161d6ed557550e0e149b8bbmindyp    private int mDiff = 0;
1001b3cc47f54072105c161d6ed557550e0e149b8bbmindyp
1011b3cc47f54072105c161d6ed557550e0e149b8bbmindyp    /**
1029d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     * Default value for {@link #mLoadWaitReason}. Conversation load will happen immediately.
1039d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     */
1049d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    private final int LOAD_NOW = 0;
1059d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    /**
1069d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     * Value for {@link #mLoadWaitReason} that means we are offscreen and waiting for the visible
1079d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     * conversation to finish loading before beginning our load.
1089d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     * <p>
1099d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     * When this value is set, the fragment should register with {@link ConversationListCallbacks}
1109d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     * to know when the visible conversation is loaded. When it is unset, it should unregister.
1119d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     */
1129d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    private final int LOAD_WAIT_FOR_INITIAL_CONVERSATION = 1;
1139d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    /**
1149d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     * Value for {@link #mLoadWaitReason} used when a conversation is too heavyweight to load at
1159d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     * all when not visible (e.g. requires network fetch, or too complex). Conversation load will
1169d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     * wait until this fragment is visible.
1179d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     */
1189d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    private final int LOAD_WAIT_UNTIL_VISIBLE = 2;
1193bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp
120f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    private ConversationContainer mConversationContainer;
1219b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira
122f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    private ConversationWebView mWebView;
1239b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira
12456d83850db72592a16f4e6ee9e0d59b60ec0824aMark Wei    private ScrollIndicatorsView mScrollIndicators;
12556d83850db72592a16f4e6ee9e0d59b60ec0824aMark Wei
12647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    private View mNewMessageBar;
12747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
128f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    private HtmlConversationTemplates mTemplates;
129f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
130f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    private final MailJsBridge mJsBridge = new MailJsBridge();
131f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
13217a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huang    private final WebViewClient mWebViewClient = new ConversationWebViewClient();
13317a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huang
1347bdc3750454efe59617b7df945eadd7e59bee954Andy Huang    private ConversationViewAdapter mAdapter;
13551067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang
13651067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang    private boolean mViewsCreated;
1374071c2f73218ce75750345557bb31a9110737841Mark Wei    // True if we attempted to render before the views were laid out
1384071c2f73218ce75750345557bb31a9110737841Mark Wei    // We will render immediately once layout is done
1394071c2f73218ce75750345557bb31a9110737841Mark Wei    private boolean mNeedRender;
14051067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang
14146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    /**
14246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang     * Temporary string containing the message bodies of the messages within a super-collapsed
14346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang     * block, for one-time use during block expansion. We cannot easily pass the body HTML
14446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang     * into JS without problematic escaping, so hold onto it momentarily and signal JS to fetch it
14546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang     * using {@link MailJsBridge}.
14646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang     */
14746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    private String mTempBodiesHtml;
14846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
149632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang    private int  mMaxAutoLoadMessages;
150632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang
1519d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    /**
1529d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     * If this conversation fragment is not visible, and it's inappropriate to load up front,
1539d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     * this is the reason we are waiting. This flag should be cleared once it's okay to load
1549d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     * the conversation.
1559d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     */
1569d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    private int mLoadWaitReason = LOAD_NOW;
157632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang
1583bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp    private boolean mEnableContentReadySignal;
15928b7aee7fa1f7d096d33fc823a88a64f7a3fa79dAndy Huang
160dde3f9ff76c1a7f9a6e9fa959a6422a8a8d82e81mindyp    private ContentSizeChangeListener mWebViewSizeChangeListener;
161dde3f9ff76c1a7f9a6e9fa959a6422a8a8d82e81mindyp
162e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang    private float mWebViewYPercent;
163e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang
164e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang    /**
165e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang     * Has loadData been called on the WebView yet?
166e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang     */
167e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang    private boolean mWebViewLoadedData;
168e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang
16963b3c6725d60386f564ab53e3ba6495f0c158d9bAndy Huang    private long mWebViewLoadStartMs;
17063b3c6725d60386f564ab53e3ba6495f0c158d9bAndy Huang
1719d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    private final DataSetObserver mLoadedObserver = new DataSetObserver() {
1729d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        @Override
1739d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        public void onChanged() {
1749d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            getHandler().post(new FragmentRunnable("delayedConversationLoad") {
1759d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                @Override
1769d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                public void go() {
1779d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                    LogUtils.d(LOG_TAG, "CVF load observer fired, this=%s",
1789d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                            ConversationViewFragment.this);
1799d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                    handleDelayedConversationLoad();
1809d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                }
1819d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            });
1829d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        }
1839d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    };
184f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
18530bcfe7b69f9d8e0cad2ee62dae9d3571cd0d88bAndy Huang    private final Runnable mOnProgressDismiss = new FragmentRunnable("onProgressDismiss") {
1867d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang        @Override
1877d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang        public void go() {
1887d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang            if (isUserVisible()) {
1897d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang                onConversationSeen();
1907d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang            }
19130bcfe7b69f9d8e0cad2ee62dae9d3571cd0d88bAndy Huang            mWebView.onRenderComplete();
1927d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang        }
1937d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang    };
1947d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang
195bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang    private static final boolean DEBUG_DUMP_CONVERSATION_HTML = false;
19647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    private static final boolean DISABLE_OFFSCREEN_LOADING = false;
19706c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang    private static final boolean DEBUG_DUMP_CURSOR_CONTENTS = false;
198e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang
199e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang    private static final String BUNDLE_KEY_WEBVIEW_Y_PERCENT =
200e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang            ConversationViewFragment.class.getName() + "webview-y-percent";
201bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang
2026c51158ad3269f157424e6c7bd488425c98da08fVikram Aggarwal    /**
2036c51158ad3269f157424e6c7bd488425c98da08fVikram Aggarwal     * Constructor needs to be public to handle orientation changes and activity lifecycle events.
2046c51158ad3269f157424e6c7bd488425c98da08fVikram Aggarwal     */
205f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    public ConversationViewFragment() {
2066c51158ad3269f157424e6c7bd488425c98da08fVikram Aggarwal        super();
2079b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira    }
2089b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira
2099b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira    /**
2109b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira     * Creates a new instance of {@link ConversationViewFragment}, initialized
211632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang     * to display a conversation with other parameters inherited/copied from an existing bundle,
212632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang     * typically one created using {@link #makeBasicArgs}.
213632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang     */
214632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang    public static ConversationViewFragment newInstance(Bundle existingArgs,
215632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang            Conversation conversation) {
216632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang        ConversationViewFragment f = new ConversationViewFragment();
217632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang        Bundle args = new Bundle(existingArgs);
218632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang        args.putParcelable(ARG_CONVERSATION, conversation);
219632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang        f.setArguments(args);
220632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang        return f;
221632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang    }
222632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang
223f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
224adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang    public void onAccountChanged(Account newAccount, Account oldAccount) {
225adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang        // if overview mode has changed, re-render completely (no need to also update headers)
226adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang        if (isOverviewMode(newAccount) != isOverviewMode(oldAccount)) {
227adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang            setupOverviewMode();
228adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang            final MessageCursor c = getMessageCursor();
229adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang            if (c != null) {
230adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang                renderConversation(c);
231adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang            } else {
232adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang                // Null cursor means this fragment is either waiting to load or in the middle of
233adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang                // loading. Either way, a future render will happen anyway, and the new setting
234adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang                // will take effect when that happens.
235adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang            }
236adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang            return;
237adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang        }
238adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang
239f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        // settings may have been updated; refresh views that are known to
240f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        // depend on settings
241f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mAdapter.notifyDataSetChanged();
242632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang    }
243632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang
2449b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira    @Override
2459b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira    public void onActivityCreated(Bundle savedInstanceState) {
2469d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        LogUtils.d(LOG_TAG, "IN CVF.onActivityCreated, this=%s visible=%s", this, isUserVisible());
2479b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira        super.onActivityCreated(savedInstanceState);
2481abfcaf0ee345acee28f0b4892314119082b28a2Mark Wei
2491abfcaf0ee345acee28f0b4892314119082b28a2Mark Wei        if (mActivity == null || mActivity.isFinishing()) {
2501abfcaf0ee345acee28f0b4892314119082b28a2Mark Wei            // Activity is finishing, just bail.
2511abfcaf0ee345acee28f0b4892314119082b28a2Mark Wei            return;
2521abfcaf0ee345acee28f0b4892314119082b28a2Mark Wei        }
2531abfcaf0ee345acee28f0b4892314119082b28a2Mark Wei
254f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        Context context = getContext();
255f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mTemplates = new HtmlConversationTemplates(context);
25659e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang
257f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        final FormattedDateBuilder dateBuilder = new FormattedDateBuilder(context);
25859e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang
2598081df46ef5a7794374e41cd1836e778a2da9b31Paul Westbrook        mAdapter = new ConversationViewAdapter(mActivity, this,
260f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                getLoaderManager(), this, getContactInfoSource(), this,
2618081df46ef5a7794374e41cd1836e778a2da9b31Paul Westbrook                this, mAddressCache, dateBuilder);
26251067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang        mConversationContainer.setOverlayAdapter(mAdapter);
26351067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang
26459e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang        // set up snap header (the adapter usually does this with the other ones)
26559e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang        final MessageHeaderView snapHeader = mConversationContainer.getSnapHeader();
26628b7aee7fa1f7d096d33fc823a88a64f7a3fa79dAndy Huang        snapHeader.initialize(dateBuilder, this, mAddressCache);
26759e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang        snapHeader.setCallbacks(this);
268f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        snapHeader.setContactInfoSource(getContactInfoSource());
26959e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang
270632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang        mMaxAutoLoadMessages = getResources().getInteger(R.integer.max_auto_load_messages);
271632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang
272f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mWebView.setOnCreateContextMenuListener(new WebViewContextMenu(getActivity()));
2730b7ed6fae6e36f2abc4ca916764177d1879731b4Andy Huang
274adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang        // set this up here instead of onCreateView to ensure the latest Account is loaded
275adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang        setupOverviewMode();
276adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang
2779d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        // Defer the call to initLoader with a Handler.
2789d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        // We want to wait until we know which fragments are present and their final visibility
2799d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        // states before going off and doing work. This prevents extraneous loading from occurring
2809d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        // as the ViewPager shifts about before the initial position is set.
2819d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        //
2829d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        // e.g. click on item #10
2839d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        // ViewPager.setAdapter() actually first loads #0 and #1 under the assumption that #0 is
2849d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        // the initial primary item
2859d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        // Then CPC immediately sets the primary item to #10, which tears down #0/#1 and sets up
2869d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        // #9/#10/#11.
2879d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        getHandler().post(new FragmentRunnable("showConversation") {
2889d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            @Override
2899d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            public void go() {
2909d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                showConversation();
2919d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            }
2929d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        });
293cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook
294cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook        if (mConversation.conversationBaseUri != null &&
295b8361c9f8938b74977316319885998aae09aab77Paul Westbrook                !Utils.isEmpty(mAccount.accoutCookieQueryUri)) {
296cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook            // Set the cookie for this base url
297b8361c9f8938b74977316319885998aae09aab77Paul Westbrook            new SetCookieTask(getContext(), mConversation.conversationBaseUri,
298b8361c9f8938b74977316319885998aae09aab77Paul Westbrook                    mAccount.accoutCookieQueryUri).execute();
299cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook        }
3009b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira    }
3019b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira
3029b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira    @Override
303e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang    public void onCreate(Bundle savedState) {
304e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang        super.onCreate(savedState);
305e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang
306e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang        if (savedState != null) {
307e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang            mWebViewYPercent = savedState.getFloat(BUNDLE_KEY_WEBVIEW_Y_PERCENT);
308e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang        }
309e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang    }
310e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang
311e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang    @Override
3129b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira    public View onCreateView(LayoutInflater inflater,
3139b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira            ViewGroup container, Bundle savedInstanceState) {
314839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang
315632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang        View rootView = inflater.inflate(R.layout.conversation_view, container, false);
316f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        mConversationContainer = (ConversationContainer) rootView
317f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang                .findViewById(R.id.conversation_container);
31847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
31947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        mNewMessageBar = mConversationContainer.findViewById(R.id.new_message_notification_bar);
32047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        mNewMessageBar.setOnClickListener(new View.OnClickListener() {
32147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang            @Override
32247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang            public void onClick(View v) {
32347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang                onNewMessageBarClick();
32447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang            }
32547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        });
32647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
327ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp        instantiateProgressIndicators(rootView);
3283bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp
3295ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang        mWebView = (ConversationWebView) mConversationContainer.findViewById(R.id.webview);
330f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
331f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        mWebView.addJavascriptInterface(mJsBridge, "mail");
3323bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp        // On JB or newer, we use the 'webkitAnimationStart' DOM event to signal load complete
3333bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp        // Below JB, try to speed up initial render by having the webview do supplemental draws to
3343bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp        // custom a software canvas.
335b941fdb95f61cdde4e1775ac9034343951e8b075mindyp        // TODO(mindyp):
336b941fdb95f61cdde4e1775ac9034343951e8b075mindyp        //PAGE READINESS SIGNAL FOR JELLYBEAN AND NEWER
337b941fdb95f61cdde4e1775ac9034343951e8b075mindyp        // Notify the app on 'webkitAnimationStart' of a simple dummy element with a simple no-op
338b941fdb95f61cdde4e1775ac9034343951e8b075mindyp        // animation that immediately runs on page load. The app uses this as a signal that the
339b941fdb95f61cdde4e1775ac9034343951e8b075mindyp        // content is loaded and ready to draw, since WebView delays firing this event until the
340b941fdb95f61cdde4e1775ac9034343951e8b075mindyp        // layers are composited and everything is ready to draw.
341b941fdb95f61cdde4e1775ac9034343951e8b075mindyp        // This signal does not seem to be reliable, so just use the old method for now.
34232d911f491afad153983f7519267ddb764927355mindyp        mEnableContentReadySignal = Utils.isRunningJellybeanOrLater();
343afc9b365dc9199ee9b2a1e598b8f40b3c78b6d9fmindyp        mWebView.setUseSoftwareLayer(!mEnableContentReadySignal);
34430bcfe7b69f9d8e0cad2ee62dae9d3571cd0d88bAndy Huang        mWebView.onUserVisibilityChanged(isUserVisible());
34517a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huang        mWebView.setWebViewClient(mWebViewClient);
346f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        mWebView.setWebChromeClient(new WebChromeClient() {
347f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang            @Override
348f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang            public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
349f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang                LogUtils.i(LOG_TAG, "JS: %s (%s:%d)", consoleMessage.message(),
350f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang                        consoleMessage.sourceId(), consoleMessage.lineNumber());
351f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang                return true;
352f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang            }
353f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        });
354f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
3553233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang        final WebSettings settings = mWebView.getSettings();
356f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
35756d83850db72592a16f4e6ee9e0d59b60ec0824aMark Wei        mScrollIndicators = (ScrollIndicatorsView) rootView.findViewById(R.id.scroll_indicators);
35856d83850db72592a16f4e6ee9e0d59b60ec0824aMark Wei        mScrollIndicators.setSourceView(mWebView);
35956d83850db72592a16f4e6ee9e0d59b60ec0824aMark Wei
360f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        settings.setJavaScriptEnabled(true);
361f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
362c319b551bebe40b9607acf0ee1d26a880fde9212Andy Huang        final float fontScale = getResources().getConfiguration().fontScale;
363ba283732e7cbcc55a3dbc8ab78950cc38cd078fbAndy Huang        final int desiredFontSizePx = getResources()
364ba283732e7cbcc55a3dbc8ab78950cc38cd078fbAndy Huang                .getInteger(R.integer.conversation_desired_font_size_px);
365ba283732e7cbcc55a3dbc8ab78950cc38cd078fbAndy Huang        final int unstyledFontSizePx = getResources()
366ba283732e7cbcc55a3dbc8ab78950cc38cd078fbAndy Huang                .getInteger(R.integer.conversation_unstyled_font_size_px);
367ba283732e7cbcc55a3dbc8ab78950cc38cd078fbAndy Huang
368ba283732e7cbcc55a3dbc8ab78950cc38cd078fbAndy Huang        int textZoom = settings.getTextZoom();
369ba283732e7cbcc55a3dbc8ab78950cc38cd078fbAndy Huang        // apply a correction to the default body text style to get regular text to the size we want
370ba283732e7cbcc55a3dbc8ab78950cc38cd078fbAndy Huang        textZoom = textZoom * desiredFontSizePx / unstyledFontSizePx;
371ba283732e7cbcc55a3dbc8ab78950cc38cd078fbAndy Huang        // then apply any system font scaling
372c319b551bebe40b9607acf0ee1d26a880fde9212Andy Huang        textZoom = (int) (textZoom * fontScale);
373c319b551bebe40b9607acf0ee1d26a880fde9212Andy Huang        settings.setTextZoom(textZoom);
374c319b551bebe40b9607acf0ee1d26a880fde9212Andy Huang
37551067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang        mViewsCreated = true;
376e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang        mWebViewLoadedData = false;
37751067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang
3789b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira        return rootView;
3799b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira    }
3809b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira
3819b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira    @Override
3829b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira    public void onDestroyView() {
3839b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira        super.onDestroyView();
38446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        mConversationContainer.setOverlayAdapter(null);
38546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        mAdapter = null;
3869d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        resetLoadWaiting(); // be sure to unregister any active load observer
38751067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang        mViewsCreated = false;
3889b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira    }
3899b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira
390e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang    @Override
391e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang    public void onSaveInstanceState(Bundle outState) {
392e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang        super.onSaveInstanceState(outState);
393e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang
394e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang        outState.putFloat(BUNDLE_KEY_WEBVIEW_Y_PERCENT, calculateScrollYPercent());
395e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang    }
396e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang
397e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang    private float calculateScrollYPercent() {
398e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang        float p;
399e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang        int scrollY = mWebView.getScrollY();
400e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang        int viewH = mWebView.getHeight();
401e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang        int webH = (int) (mWebView.getContentHeight() * mWebView.getScale());
402e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang
403e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang        if (webH == 0 || webH <= viewH) {
404e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang            p = 0;
405e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang        } else if (scrollY + viewH >= webH) {
406e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang            // The very bottom is a special case, it acts as a stronger anchor than the scroll top
407e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang            // at that point.
408e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang            p = 1.0f;
409e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang        } else {
410e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang            p = (float) scrollY / webH;
411e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang        }
412e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang        return p;
413e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang    }
414e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang
4159d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    private void resetLoadWaiting() {
4169d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        if (mLoadWaitReason == LOAD_WAIT_FOR_INITIAL_CONVERSATION) {
4179d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            getListController().unregisterConversationLoadedObserver(mLoadedObserver);
4189d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        }
4199d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        mLoadWaitReason = LOAD_NOW;
4209d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    }
4219d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
4225ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang    @Override
423f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    protected void markUnread() {
424839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang        // Ignore unsafe calls made after a fragment is detached from an activity
425839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang        final ControllableActivity activity = (ControllableActivity) getActivity();
426839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang        if (activity == null) {
427839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang            LogUtils.w(LOG_TAG, "ignoring markUnread for conv=%s", mConversation.id);
428839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang            return;
429839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang        }
430839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang
43128e31e2417d775b343919cf6018f85871e40a294Andy Huang        if (mViewState == null) {
43228e31e2417d775b343919cf6018f85871e40a294Andy Huang            LogUtils.i(LOG_TAG, "ignoring markUnread for conv with no view state (%d)",
43328e31e2417d775b343919cf6018f85871e40a294Andy Huang                    mConversation.id);
43428e31e2417d775b343919cf6018f85871e40a294Andy Huang            return;
43528e31e2417d775b343919cf6018f85871e40a294Andy Huang        }
436839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang        activity.getConversationUpdater().markConversationMessagesUnread(mConversation,
4374a878b606961825fb4fd412b460df75779f09ad2Vikram Aggarwal                mViewState.getUnreadMessageUris(), mViewState.getConversationInfo());
438839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang    }
439839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang
440f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
441f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public void onUserVisibleHintChanged() {
4429d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        final boolean userVisible = isUserVisible();
4439d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
4449d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        if (!userVisible) {
4459d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            dismissLoadingStatus();
4469d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        } else if (mViewsCreated) {
4479d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            if (getMessageCursor() != null) {
4489d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                LogUtils.d(LOG_TAG, "Fragment is now user-visible, onConversationSeen: %s", this);
449f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                onConversationSeen();
4509d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            } else if (isLoadWaiting()) {
4519d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                LogUtils.d(LOG_TAG, "Fragment is now user-visible, showing conversation: %s", this);
4529d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                handleDelayedConversationLoad();
453632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang            }
454632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang        }
455f8cf5468500677fceac3025727daba8155d319d6Andy Huang
45630bcfe7b69f9d8e0cad2ee62dae9d3571cd0d88bAndy Huang        if (mWebView != null) {
45730bcfe7b69f9d8e0cad2ee62dae9d3571cd0d88bAndy Huang            mWebView.onUserVisibilityChanged(userVisible);
45830bcfe7b69f9d8e0cad2ee62dae9d3571cd0d88bAndy Huang        }
459f8cf5468500677fceac3025727daba8155d319d6Andy Huang    }
460f8cf5468500677fceac3025727daba8155d319d6Andy Huang
4619d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    /**
4629d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     * Will either call initLoader now to begin loading, or set {@link #mLoadWaitReason} and do
4639d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     * nothing (in which case you should later call {@link #handleDelayedConversationLoad()}).
4649d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang     */
4659b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira    private void showConversation() {
4669d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        final int reason;
4679d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
4689d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        if (isUserVisible()) {
4699d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            LogUtils.i(LOG_TAG,
4709d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                    "SHOWCONV: CVF is user-visible, immediately loading conversation (%s)", this);
4719d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            reason = LOAD_NOW;
4729d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        } else {
4739d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            final boolean disableOffscreenLoading = DISABLE_OFFSCREEN_LOADING
4749d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                    || (mConversation.isRemote
4759d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                            || mConversation.getNumMessages() > mMaxAutoLoadMessages);
4769d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
4779d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            // When not visible, we should not immediately load if either this conversation is
4789d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            // too heavyweight, or if the main/initial conversation is busy loading.
4799d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            if (disableOffscreenLoading) {
4809d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                reason = LOAD_WAIT_UNTIL_VISIBLE;
4819d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                LogUtils.i(LOG_TAG, "SHOWCONV: CVF waiting until visible to load (%s)", this);
4829d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            } else if (getListController().isInitialConversationLoading()) {
4839d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                reason = LOAD_WAIT_FOR_INITIAL_CONVERSATION;
4849d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                LogUtils.i(LOG_TAG, "SHOWCONV: CVF waiting for initial to finish (%s)", this);
4859d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                getListController().registerConversationLoadedObserver(mLoadedObserver);
4869d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            } else {
4879d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                LogUtils.i(LOG_TAG,
4889d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                        "SHOWCONV: CVF is not visible, but no reason to wait. loading now. (%s)",
4899d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                        this);
4909d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                reason = LOAD_NOW;
4919d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            }
492632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang        }
4939d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
4949d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        mLoadWaitReason = reason;
4959d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        if (mLoadWaitReason == LOAD_NOW) {
4969d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            startConversationLoad();
4979d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        }
4989d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    }
4999d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
5009d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    private void handleDelayedConversationLoad() {
5019d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        resetLoadWaiting();
5029d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        startConversationLoad();
5039d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    }
5049d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
5059d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    private void startConversationLoad() {
5063bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp        mWebView.setVisibility(View.VISIBLE);
507f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        getLoaderManager().initLoader(MESSAGE_LOADER, Bundle.EMPTY, getMessageLoaderCallbacks());
5089d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        if (isUserVisible()) {
5095460ce477cb1dcc439dd4e5c9e8393470f00f618Andy Huang            final SubjectDisplayChanger sdc = mActivity.getSubjectDisplayChanger();
5105460ce477cb1dcc439dd4e5c9e8393470f00f618Andy Huang            if (sdc != null) {
5115460ce477cb1dcc439dd4e5c9e8393470f00f618Andy Huang                sdc.setSubject(mConversation.subject);
5125460ce477cb1dcc439dd4e5c9e8393470f00f618Andy Huang            }
5135460ce477cb1dcc439dd4e5c9e8393470f00f618Andy Huang        }
5143bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp        // TODO(mindyp): don't show loading status for a previously rendered
5153bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp        // conversation. Ielieve this is better done by making sure don't show loading status
5163bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp        // until XX ms have passed without loading completed.
5173bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp        showLoadingStatus();
5188e915724b6e4374da9b70161ee0a55f0c763e563Mindy Pereira    }
5198e915724b6e4374da9b70161ee0a55f0c763e563Mindy Pereira
5207d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang    private void revealConversation() {
52130bcfe7b69f9d8e0cad2ee62dae9d3571cd0d88bAndy Huang        dismissLoadingStatus(mOnProgressDismiss);
5227d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang    }
5237d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang
5249d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    private boolean isLoadWaiting() {
5259d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        return mLoadWaitReason != LOAD_NOW;
5269d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    }
5279d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
52851067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang    private void renderConversation(MessageCursor messageCursor) {
5293bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp        final String convHtml = renderMessageBodies(messageCursor, mEnableContentReadySignal);
530bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang
531bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang        if (DEBUG_DUMP_CONVERSATION_HTML) {
532bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang            java.io.FileWriter fw = null;
533bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang            try {
534bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang                fw = new java.io.FileWriter("/sdcard/conv" + mConversation.id
535bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang                        + ".html");
536bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang                fw.write(convHtml);
537bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang            } catch (java.io.IOException e) {
538bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang                e.printStackTrace();
539bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang            } finally {
540bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang                if (fw != null) {
541bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang                    try {
542bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang                        fw.close();
543bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang                    } catch (java.io.IOException e) {
544bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang                        e.printStackTrace();
545bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang                    }
546bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang                }
547bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang            }
548bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang        }
549bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang
550e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang        // save off existing scroll position before re-rendering
551e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang        if (mWebViewLoadedData) {
552e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang            mWebViewYPercent = calculateScrollYPercent();
553e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang        }
554e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang
555bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang        mWebView.loadDataWithBaseURL(mBaseUri, convHtml, "text/html", "utf-8", null);
556e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang        mWebViewLoadedData = true;
55763b3c6725d60386f564ab53e3ba6495f0c158d9bAndy Huang        mWebViewLoadStartMs = SystemClock.uptimeMillis();
55851067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang    }
55951067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang
5607bdc3750454efe59617b7df945eadd7e59bee954Andy Huang    /**
5617bdc3750454efe59617b7df945eadd7e59bee954Andy Huang     * Populate the adapter with overlay views (message headers, super-collapsed blocks, a
5627bdc3750454efe59617b7df945eadd7e59bee954Andy Huang     * conversation header), and return an HTML document with spacer divs inserted for all overlays.
5637bdc3750454efe59617b7df945eadd7e59bee954Andy Huang     *
5647bdc3750454efe59617b7df945eadd7e59bee954Andy Huang     */
5653bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp    private String renderMessageBodies(MessageCursor messageCursor,
5663bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp            boolean enableContentReadySignal) {
567f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        int pos = -1;
568632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang
5691ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang        LogUtils.d(LOG_TAG, "IN renderMessageBodies, fragment=%s", this);
5707bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        boolean allowNetworkImages = false;
5717bdc3750454efe59617b7df945eadd7e59bee954Andy Huang
572c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang        // TODO: re-use any existing adapter item state (expanded, details expanded, show pics)
57328b7aee7fa1f7d096d33fc823a88a64f7a3fa79dAndy Huang
5747bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        // Walk through the cursor and build up an overlay adapter as you go.
5757bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        // Each overlay has an entry in the adapter for easy scroll handling in the container.
5767bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        // Items are not necessarily 1:1 in cursor and adapter because of super-collapsed blocks.
5777bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        // When adding adapter items, also add their heights to help the container later determine
5787bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        // overlay dimensions.
5797bdc3750454efe59617b7df945eadd7e59bee954Andy Huang
580db620fe42dcf1909468822b61238a23103d15376Andy Huang        // When re-rendering, prevent ConversationContainer from laying out overlays until after
581db620fe42dcf1909468822b61238a23103d15376Andy Huang        // the new spacers are positioned by WebView.
582db620fe42dcf1909468822b61238a23103d15376Andy Huang        mConversationContainer.invalidateSpacerGeometry();
583db620fe42dcf1909468822b61238a23103d15376Andy Huang
5847bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        mAdapter.clear();
5857bdc3750454efe59617b7df945eadd7e59bee954Andy Huang
58647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        // re-evaluate the message parts of the view state, since the messages may have changed
58747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        // since the previous render
58847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        final ConversationViewState prevState = mViewState;
58947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        mViewState = new ConversationViewState(prevState);
59047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
5915ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang        // N.B. the units of height for spacers are actually dp and not px because WebView assumes
5922e9acfe527426c00b5df2ca66189262dc40c8575Andy Huang        // a pixel is an mdpi pixel, unless you set device-dpi.
5935ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang
5947bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        // add a single conversation header item
5957bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        final int convHeaderPos = mAdapter.addConversationHeader(mConversation);
59623014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang        final int convHeaderPx = measureOverlayHeight(convHeaderPos);
5973233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang
598256b35c0a8287f48c28e0d1ba3fae65790063295Andy Huang        final int sideMarginPx = getResources().getDimensionPixelOffset(
599256b35c0a8287f48c28e0d1ba3fae65790063295Andy Huang                R.dimen.conversation_view_margin_side) + getResources().getDimensionPixelOffset(
600256b35c0a8287f48c28e0d1ba3fae65790063295Andy Huang                R.dimen.conversation_message_content_margin_side);
601256b35c0a8287f48c28e0d1ba3fae65790063295Andy Huang
602256b35c0a8287f48c28e0d1ba3fae65790063295Andy Huang        mTemplates.startConversation(mWebView.screenPxToWebPx(sideMarginPx),
603256b35c0a8287f48c28e0d1ba3fae65790063295Andy Huang                mWebView.screenPxToWebPx(convHeaderPx));
6043233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang
60546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        int collapsedStart = -1;
606839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang        ConversationMessage prevCollapsedMsg = null;
60746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        boolean prevSafeForImages = false;
60846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
609f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        while (messageCursor.moveToPosition(++pos)) {
610839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang            final ConversationMessage msg = messageCursor.getMessage();
61146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
6123233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang            // TODO: save/restore 'show pics' state
6133233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang            final boolean safeForImages = msg.alwaysShowImages /* || savedStateSaysSafe */;
6143233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang            allowNetworkImages |= safeForImages;
6152405528bb15245f594277fe7b6efacd6a18ce69eAndy Huang
61608098ec4c894d9a15dfe800ad2397494e7e0a79aPaul Westbrook            final Integer savedExpanded = prevState.getExpansionState(msg);
61708098ec4c894d9a15dfe800ad2397494e7e0a79aPaul Westbrook            final int expandedState;
618839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang            if (savedExpanded != null) {
6191ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang                if (ExpansionState.isSuperCollapsed(savedExpanded) && messageCursor.isLast()) {
6201ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang                    // override saved state when this is now the new last message
6211ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang                    // this happens to the second-to-last message when you discard a draft
6221ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang                    expandedState = ExpansionState.EXPANDED;
6231ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang                } else {
6241ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang                    expandedState = savedExpanded;
6251ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang                }
626839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang            } else {
627cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang                // new messages that are not expanded default to being eligible for super-collapse
62808098ec4c894d9a15dfe800ad2397494e7e0a79aPaul Westbrook                expandedState = (!msg.read || msg.starred || messageCursor.isLast()) ?
629cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang                        ExpansionState.EXPANDED : ExpansionState.SUPER_COLLAPSED;
630839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang            }
63108098ec4c894d9a15dfe800ad2397494e7e0a79aPaul Westbrook            mViewState.setExpansionState(msg, expandedState);
632839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang
633839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang            // save off "read" state from the cursor
634839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang            // later, the view may not match the cursor (e.g. conversation marked read on open)
635423bea25992492efea7d414819729f9eae7ce72eAndy Huang            // however, if a previous state indicated this message was unread, trust that instead
636423bea25992492efea7d414819729f9eae7ce72eAndy Huang            // so "mark unread" marks all originally unread messages
637423bea25992492efea7d414819729f9eae7ce72eAndy Huang            mViewState.setReadState(msg, msg.read && !prevState.isUnread(msg));
638c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang
639cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang            // We only want to consider this for inclusion in the super collapsed block if
640cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang            // 1) The we don't have previous state about this message  (The first time that the
641cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang            //    user opens a conversation)
642cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang            // 2) The previously saved state for this message indicates that this message is
643cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang            //    in the super collapsed block.
644cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang            if (ExpansionState.isSuperCollapsed(expandedState)) {
645cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang                // contribute to a super-collapsed block that will be emitted just before the
646cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang                // next expanded header
647cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang                if (collapsedStart < 0) {
648cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang                    collapsedStart = pos;
64946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                }
650cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang                prevCollapsedMsg = msg;
651cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang                prevSafeForImages = safeForImages;
652cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang                continue;
65346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            }
6547bdc3750454efe59617b7df945eadd7e59bee954Andy Huang
65546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            // resolve any deferred decisions on previous collapsed items
65646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            if (collapsedStart >= 0) {
65746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                if (pos - collapsedStart == 1) {
65846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                    // special-case for a single collapsed message: no need to super-collapse it
65946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                    renderMessage(prevCollapsedMsg, false /* expanded */,
66046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                            prevSafeForImages);
66146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                } else {
66246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                    renderSuperCollapsedBlock(collapsedStart, pos - 1);
66346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                }
66446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                prevCollapsedMsg = null;
66546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                collapsedStart = -1;
66646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            }
6672405528bb15245f594277fe7b6efacd6a18ce69eAndy Huang
66808098ec4c894d9a15dfe800ad2397494e7e0a79aPaul Westbrook            renderMessage(msg, ExpansionState.isExpanded(expandedState), safeForImages);
669f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        }
6703233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang
6713233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang        mWebView.getSettings().setBlockNetworkImage(!allowNetworkImages);
6723233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang
673cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook        // If the conversation has specified a base uri, use it here, use mBaseUri
674cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook        final String conversationBaseUri = mConversation.conversationBaseUri != null ?
675cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook                mConversation.conversationBaseUri.toString() : mBaseUri;
676cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook        return mTemplates.endConversation(mBaseUri, conversationBaseUri, 320,
677adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang                mWebView.getViewportWidth(), enableContentReadySignal, isOverviewMode(mAccount));
678f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    }
679f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
68046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    private void renderSuperCollapsedBlock(int start, int end) {
68146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        final int blockPos = mAdapter.addSuperCollapsedBlock(start, end);
68223014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang        final int blockPx = measureOverlayHeight(blockPos);
68323014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang        mTemplates.appendSuperCollapsedHtml(start, mWebView.screenPxToWebPx(blockPx));
68446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    }
68546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
686839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang    private void renderMessage(ConversationMessage msg, boolean expanded,
687839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang            boolean safeForImages) {
68846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        final int headerPos = mAdapter.addMessageHeader(msg, expanded);
68946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        final MessageHeaderItem headerItem = (MessageHeaderItem) mAdapter.getItem(headerPos);
69046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
69146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        final int footerPos = mAdapter.addMessageFooter(headerItem);
69246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
69346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        // Measure item header and footer heights to allocate spacers in HTML
69446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        // But since the views themselves don't exist yet, render each item temporarily into
69546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        // a host view for measurement.
69623014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang        final int headerPx = measureOverlayHeight(headerPos);
69723014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang        final int footerPx = measureOverlayHeight(footerPos);
69846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
699256b35c0a8287f48c28e0d1ba3fae65790063295Andy Huang        mTemplates.appendMessageHtml(msg, expanded, safeForImages,
70023014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang                mWebView.screenPxToWebPx(headerPx), mWebView.screenPxToWebPx(footerPx));
70146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    }
70246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
70346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    private String renderCollapsedHeaders(MessageCursor cursor,
70446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            SuperCollapsedBlockItem blockToReplace) {
70546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        final List<ConversationOverlayItem> replacements = Lists.newArrayList();
70646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
70746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        mTemplates.reset();
70846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
7092b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei        // In devices with non-integral density multiplier, screen pixels translate to non-integral
7102b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei        // web pixels. Keep track of the error that occurs when we cast all heights to int
7112b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei        float error = 0f;
71246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        for (int i = blockToReplace.getStart(), end = blockToReplace.getEnd(); i <= end; i++) {
71346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            cursor.moveToPosition(i);
714839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang            final ConversationMessage msg = cursor.getMessage();
71546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            final MessageHeaderItem header = mAdapter.newMessageHeaderItem(msg,
71646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                    false /* expanded */);
71746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            final MessageFooterItem footer = mAdapter.newMessageFooterItem(header);
71846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
71923014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang            final int headerPx = measureOverlayHeight(header);
72023014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang            final int footerPx = measureOverlayHeight(footer);
7212b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei            error += mWebView.screenPxToWebPxError(headerPx)
7222b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei                    + mWebView.screenPxToWebPxError(footerPx);
7232b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei
7242b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei            // When the error becomes greater than 1 pixel, make the next header 1 pixel taller
7252b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei            int correction = 0;
7262b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei            if (error >= 1) {
7272b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei                correction = 1;
7282b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei                error -= 1;
7292b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei            }
73046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
731256b35c0a8287f48c28e0d1ba3fae65790063295Andy Huang            mTemplates.appendMessageHtml(msg, false /* expanded */, msg.alwaysShowImages,
7322b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei                    mWebView.screenPxToWebPx(headerPx) + correction,
7332b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei                    mWebView.screenPxToWebPx(footerPx));
73446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            replacements.add(header);
73546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            replacements.add(footer);
736839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang
73708098ec4c894d9a15dfe800ad2397494e7e0a79aPaul Westbrook            mViewState.setExpansionState(msg, ExpansionState.COLLAPSED);
73846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        }
73946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
74046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        mAdapter.replaceSuperCollapsedBlock(blockToReplace, replacements);
74106c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang        mAdapter.notifyDataSetChanged();
74246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
74346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        return mTemplates.emit();
74446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    }
74546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
74646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    private int measureOverlayHeight(int position) {
74746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        return measureOverlayHeight(mAdapter.getItem(position));
74846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    }
74946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
7507bdc3750454efe59617b7df945eadd7e59bee954Andy Huang    /**
751b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang     * Measure the height of an adapter view by rendering an adapter item into a temporary
75246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang     * host view, and asking the view to immediately measure itself. This method will reuse
7537bdc3750454efe59617b7df945eadd7e59bee954Andy Huang     * a previous adapter view from {@link ConversationContainer}'s scrap views if one was generated
7547bdc3750454efe59617b7df945eadd7e59bee954Andy Huang     * earlier.
7557bdc3750454efe59617b7df945eadd7e59bee954Andy Huang     * <p>
75646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang     * After measuring the height, this method also saves the height in the
75746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang     * {@link ConversationOverlayItem} for later use in overlay positioning.
7587bdc3750454efe59617b7df945eadd7e59bee954Andy Huang     *
75946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang     * @param convItem adapter item with data to render and measure
76023014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang     * @return height of the rendered view in screen px
7617bdc3750454efe59617b7df945eadd7e59bee954Andy Huang     */
76246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    private int measureOverlayHeight(ConversationOverlayItem convItem) {
7637bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        final int type = convItem.getType();
7647bdc3750454efe59617b7df945eadd7e59bee954Andy Huang
7657bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        final View convertView = mConversationContainer.getScrapView(type);
766b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang        final View hostView = mAdapter.getView(convItem, convertView, mConversationContainer,
767b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang                true /* measureOnly */);
7687bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        if (convertView == null) {
7697bdc3750454efe59617b7df945eadd7e59bee954Andy Huang            mConversationContainer.addScrapView(type, hostView);
7707bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        }
7717bdc3750454efe59617b7df945eadd7e59bee954Andy Huang
7729875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang        final int heightPx = mConversationContainer.measureOverlay(hostView);
7737bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        convItem.setHeight(heightPx);
7749875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang        convItem.markMeasurementValid();
7757bdc3750454efe59617b7df945eadd7e59bee954Andy Huang
77623014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang        return heightPx;
7777bdc3750454efe59617b7df945eadd7e59bee954Andy Huang    }
7787bdc3750454efe59617b7df945eadd7e59bee954Andy Huang
7795ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang    @Override
7805ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang    public void onConversationViewHeaderHeightChange(int newHeight) {
781ab2d998506c83e82ddae25b6ba1419414e1e8122Mark Wei        final int h = mWebView.screenPxToWebPx(newHeight);
782ab2d998506c83e82ddae25b6ba1419414e1e8122Mark Wei
783ab2d998506c83e82ddae25b6ba1419414e1e8122Mark Wei        mWebView.loadUrl(String.format("javascript:setConversationHeaderSpacerHeight(%s);", h));
7845ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang    }
7855ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang
7863233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang    // END conversation header callbacks
7873233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang
7883233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang    // START message header callbacks
7893233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang    @Override
790c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang    public void setMessageSpacerHeight(MessageHeaderItem item, int newSpacerHeightPx) {
791c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang        mConversationContainer.invalidateSpacerGeometry();
792c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang
793c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang        // update message HTML spacer height
79423014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang        final int h = mWebView.screenPxToWebPx(newSpacerHeightPx);
79523014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang        LogUtils.i(LAYOUT_TAG, "setting HTML spacer h=%dwebPx (%dscreenPx)", h,
79623014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang                newSpacerHeightPx);
7975349ce14695dc98615108eb26f96806921b0abbaVikram Aggarwal        mWebView.loadUrl(String.format("javascript:setMessageHeaderSpacerHeight('%s', %s);",
798014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                mTemplates.getMessageDomId(item.getMessage()), h));
7993233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang    }
8003233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang
8013233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang    @Override
802c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang    public void setMessageExpanded(MessageHeaderItem item, int newSpacerHeightPx) {
803c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang        mConversationContainer.invalidateSpacerGeometry();
804c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang
805c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang        // show/hide the HTML message body and update the spacer height
80623014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang        final int h = mWebView.screenPxToWebPx(newSpacerHeightPx);
80723014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang        LogUtils.i(LAYOUT_TAG, "setting HTML spacer expanded=%s h=%dwebPx (%dscreenPx)",
80823014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang                item.isExpanded(), h, newSpacerHeightPx);
8095349ce14695dc98615108eb26f96806921b0abbaVikram Aggarwal        mWebView.loadUrl(String.format("javascript:setMessageBodyVisible('%s', %s, %s);",
810014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                mTemplates.getMessageDomId(item.getMessage()), item.isExpanded(), h));
811839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang
812014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang        mViewState.setExpansionState(item.getMessage(),
81308098ec4c894d9a15dfe800ad2397494e7e0a79aPaul Westbrook                item.isExpanded() ? ExpansionState.EXPANDED : ExpansionState.COLLAPSED);
8143233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang    }
8153233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang
8163233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang    @Override
8173233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang    public void showExternalResources(Message msg) {
8183233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang        mWebView.getSettings().setBlockNetworkImage(false);
8193233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang        mWebView.loadUrl("javascript:unblockImages('" + mTemplates.getMessageDomId(msg) + "');");
8203233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang    }
8213233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang    // END message header callbacks
8225ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang
82346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    @Override
82446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    public void onSuperCollapsedClick(SuperCollapsedBlockItem item) {
825f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        MessageCursor cursor = getMessageCursor();
826f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        if (cursor == null || !mViewsCreated) {
82746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            return;
82846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        }
82946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
830f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mTempBodiesHtml = renderCollapsedHeaders(cursor, item);
83146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        mWebView.loadUrl("javascript:replaceSuperCollapsedBlock(" + item.getStart() + ")");
83246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    }
83346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
83447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    private void showNewMessageNotification(NewMessagesInfo info) {
83547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        final TextView descriptionView = (TextView) mNewMessageBar.findViewById(
83647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang                R.id.new_message_description);
83747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        descriptionView.setText(info.getNotificationText());
83847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        mNewMessageBar.setVisibility(View.VISIBLE);
83947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    }
84047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
84147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    private void onNewMessageBarClick() {
84247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        mNewMessageBar.setVisibility(View.GONE);
84347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
844f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        renderConversation(getMessageCursor()); // mCursor is already up-to-date
845f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                                                // per onLoadFinished()
8465fbda023f1b0570e192e03a33834244a05edf200Andy Huang    }
8475fbda023f1b0570e192e03a33834244a05edf200Andy Huang
848adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang    private static OverlayPosition[] parsePositions(final String[] topArray,
849adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang            final String[] bottomArray) {
850adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang        final int len = topArray.length;
851adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang        final OverlayPosition[] positions = new OverlayPosition[len];
852b5078b287b1cec38817e342ff054ea901d199329Andy Huang        for (int i = 0; i < len; i++) {
853adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang            positions[i] = new OverlayPosition(
854adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang                    Integer.parseInt(topArray[i]), Integer.parseInt(bottomArray[i]));
855b5078b287b1cec38817e342ff054ea901d199329Andy Huang        }
856adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang        return positions;
857b5078b287b1cec38817e342ff054ea901d199329Andy Huang    }
858b5078b287b1cec38817e342ff054ea901d199329Andy Huang
85947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    @Override
86047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    public String toString() {
86147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        // log extra info at DEBUG level or finer
86247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        final String s = super.toString();
86347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        if (!LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG) || mConversation == null) {
86447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang            return s;
86547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        }
86647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        return "(" + s + " subj=" + mConversation.subject + ")";
86747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    }
86847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
8691617481de7c1f9b8dfcd25ba9828e933cf9d5490Andy Huang    private Address getAddress(String rawFrom) {
8701617481de7c1f9b8dfcd25ba9828e933cf9d5490Andy Huang        Address addr = mAddressCache.get(rawFrom);
8711617481de7c1f9b8dfcd25ba9828e933cf9d5490Andy Huang        if (addr == null) {
8721617481de7c1f9b8dfcd25ba9828e933cf9d5490Andy Huang            addr = Address.getEmailAddress(rawFrom);
8731617481de7c1f9b8dfcd25ba9828e933cf9d5490Andy Huang            mAddressCache.put(rawFrom, addr);
8741617481de7c1f9b8dfcd25ba9828e933cf9d5490Andy Huang        }
8751617481de7c1f9b8dfcd25ba9828e933cf9d5490Andy Huang        return addr;
8761617481de7c1f9b8dfcd25ba9828e933cf9d5490Andy Huang    }
8771617481de7c1f9b8dfcd25ba9828e933cf9d5490Andy Huang
8789d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    private void ensureContentSizeChangeListener() {
8799d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        if (mWebViewSizeChangeListener == null) {
8809d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            mWebViewSizeChangeListener = new ConversationWebView.ContentSizeChangeListener() {
8819d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                @Override
8829d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                public void onHeightChange(int h) {
8839d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                    // When WebKit says the DOM height has changed, re-measure
8849d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                    // bodies and re-position their headers.
8859d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                    // This is separate from the typical JavaScript DOM change
8869d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                    // listeners because cases like NARROW_COLUMNS text reflow do not trigger DOM
8879d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                    // events.
8889d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                    mWebView.loadUrl("javascript:measurePositions();");
8899d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                }
8909d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            };
8919d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        }
8929d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang        mWebView.setContentSizeChangeListener(mWebViewSizeChangeListener);
8939d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang    }
8949d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
895adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang    private static boolean isOverviewMode(Account acct) {
896adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang        return acct.settings.conversationViewMode == UIProvider.ConversationViewMode.OVERVIEW;
897adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang    }
898adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang
899adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang    private void setupOverviewMode() {
900adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang        final boolean overviewMode = isOverviewMode(mAccount);
901adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang        final WebSettings settings = mWebView.getSettings();
90206def56be1f03acd2e5706ecf207ab147ce6f5c4Andy Huang        settings.setUseWideViewPort(overviewMode);
903adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang        settings.setSupportZoom(overviewMode);
904adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang        if (overviewMode) {
905adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang            settings.setBuiltInZoomControls(true);
906adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang            settings.setDisplayZoomControls(false);
907adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang        }
908adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang    }
909adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang
910542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook    private class ConversationWebViewClient extends AbstractConversationWebViewClient {
91117a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huang        @Override
91217a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huang        public void onPageFinished(WebView view, String url) {
9139a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang            // Ignore unsafe calls made after a fragment is detached from an activity.
9149a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang            // This method needs to, for example, get at the loader manager, which needs
9159a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang            // the fragment to be added.
916de56e970299fb413e4136dc12a7b08c5cc89e1c1Andy Huang            final ControllableActivity activity = (ControllableActivity) getActivity();
9179a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang            if (!isAdded() || !mViewsCreated) {
918de56e970299fb413e4136dc12a7b08c5cc89e1c1Andy Huang                LogUtils.i(LOG_TAG, "ignoring CVF.onPageFinished, url=%s fragment=%s", url,
919b95da853ffbff343c8ca11470de2f0047e0807a6Andy Huang                        ConversationViewFragment.this);
920b95da853ffbff343c8ca11470de2f0047e0807a6Andy Huang                return;
921b95da853ffbff343c8ca11470de2f0047e0807a6Andy Huang            }
922b95da853ffbff343c8ca11470de2f0047e0807a6Andy Huang
92330bcfe7b69f9d8e0cad2ee62dae9d3571cd0d88bAndy Huang            LogUtils.i(LOG_TAG, "IN CVF.onPageFinished, url=%s fragment=%s wv=%s t=%sms", url,
92430bcfe7b69f9d8e0cad2ee62dae9d3571cd0d88bAndy Huang                    ConversationViewFragment.this, view,
92563b3c6725d60386f564ab53e3ba6495f0c158d9bAndy Huang                    (SystemClock.uptimeMillis() - mWebViewLoadStartMs));
926632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang
9279d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang            ensureContentSizeChangeListener();
9289d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
9293bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp            if (!mEnableContentReadySignal) {
9307d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang                revealConversation();
9313bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp            }
9329d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang
9339a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang            final Set<String> emailAddresses = Sets.newHashSet();
9349a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang            for (Address addr : mAddressCache.values()) {
9359a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang                emailAddresses.add(addr.getAddress());
936b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang            }
9379a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang            ContactLoaderCallbacks callbacks = getContactInfoSource();
9389a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang            getContactInfoSource().setSenders(emailAddresses);
9399a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang            getLoaderManager().restartLoader(CONTACT_LOADER, Bundle.EMPTY, callbacks);
94017a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huang        }
94117a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huang
942af5d4e0e2a5ce3300fffa0c484431d83adb89ee8Andy Huang        @Override
943af5d4e0e2a5ce3300fffa0c484431d83adb89ee8Andy Huang        public boolean shouldOverrideUrlLoading(WebView view, String url) {
944542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            return mViewsCreated && super.shouldOverrideUrlLoading(view, url);
945af5d4e0e2a5ce3300fffa0c484431d83adb89ee8Andy Huang        }
94617a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huang    }
94717a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huang
948f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    /**
949f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang     * NOTE: all public methods must be listed in the proguard flags so that they can be accessed
950f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang     * via reflection and not stripped.
951f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang     *
952f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang     */
953f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    private class MailJsBridge {
954f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
955f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        @SuppressWarnings("unused")
956974c966c14000d42f089e2021b098d5cf61f9245Mindy Pereira        @JavascriptInterface
957adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang        public void onWebContentGeometryChange(final String[] overlayTopStrs,
958adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang                final String[] overlayBottomStrs) {
9599a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang            getHandler().post(new FragmentRunnable("onWebContentGeometryChange") {
9601b3cc47f54072105c161d6ed557550e0e149b8bbmindyp
9619a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang                @Override
9629a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang                public void go() {
9639a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang                    try {
96446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                        if (!mViewsCreated) {
9651b3cc47f54072105c161d6ed557550e0e149b8bbmindyp                            LogUtils.d(LOG_TAG, "ignoring webContentGeometryChange because views"
9661b3cc47f54072105c161d6ed557550e0e149b8bbmindyp                                    + " are gone, %s", ConversationViewFragment.this);
96746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                            return;
96846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                        }
969adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang                        mConversationContainer.onGeometryChange(
970adbf3e8cadb66666f307352b72537fbac57b916fAndy Huang                                parsePositions(overlayTopStrs, overlayBottomStrs));
9711b3cc47f54072105c161d6ed557550e0e149b8bbmindyp                        if (mDiff != 0) {
9721b3cc47f54072105c161d6ed557550e0e149b8bbmindyp                            // SCROLL!
9731b3cc47f54072105c161d6ed557550e0e149b8bbmindyp                            int scale = (int) (mWebView.getScale() / mWebView.getInitialScale());
9741b3cc47f54072105c161d6ed557550e0e149b8bbmindyp                            if (scale > 1) {
9751b3cc47f54072105c161d6ed557550e0e149b8bbmindyp                                mWebView.scrollBy(0, (mDiff * (scale - 1)));
9761b3cc47f54072105c161d6ed557550e0e149b8bbmindyp                            }
9771b3cc47f54072105c161d6ed557550e0e149b8bbmindyp                            mDiff = 0;
9781b3cc47f54072105c161d6ed557550e0e149b8bbmindyp                        }
9799a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang                    } catch (Throwable t) {
9809a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang                        LogUtils.e(LOG_TAG, t, "Error in MailJsBridge.onWebContentGeometryChange");
98151067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang                    }
9829a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang                }
9839a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang            });
98446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        }
98551067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang
98646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        @SuppressWarnings("unused")
987974c966c14000d42f089e2021b098d5cf61f9245Mindy Pereira        @JavascriptInterface
98846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        public String getTempMessageBodies() {
98946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            try {
99046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                if (!mViewsCreated) {
99146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                    return "";
992f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang                }
99346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
99446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                final String s = mTempBodiesHtml;
99546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                mTempBodiesHtml = null;
99646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                return s;
99746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            } catch (Throwable t) {
99846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                LogUtils.e(LOG_TAG, t, "Error in MailJsBridge.getTempMessageBodies");
99946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                return "";
100046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            }
1001f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        }
1002f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
1003014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang        @SuppressWarnings("unused")
1004014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang        @JavascriptInterface
1005014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang        public String getMessageBody(String domId) {
1006014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang            try {
1007014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                final MessageCursor cursor = getMessageCursor();
1008014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                if (!mViewsCreated || cursor == null) {
1009014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                    return "";
1010014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                }
1011014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang
1012014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                int pos = -1;
1013014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                while (cursor.moveToPosition(++pos)) {
1014014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                    final ConversationMessage msg = cursor.getMessage();
1015014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                    if (TextUtils.equals(domId, mTemplates.getMessageDomId(msg))) {
1016014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                        return msg.getBodyAsHtml();
1017014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                    }
1018014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                }
1019014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang
1020014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                return "";
1021014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang
1022014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang            } catch (Throwable t) {
1023014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                LogUtils.e(LOG_TAG, t, "Error in MailJsBridge.getMessageBody");
1024014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                return "";
1025014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang            }
1026014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang        }
1027014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang
10283bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp        @SuppressWarnings("unused")
1029974c966c14000d42f089e2021b098d5cf61f9245Mindy Pereira        @JavascriptInterface
10303bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp        public void onContentReady() {
10319a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang            getHandler().post(new FragmentRunnable("onContentReady") {
10329a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang                @Override
10339a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang                public void go() {
10349a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang                    try {
103563b3c6725d60386f564ab53e3ba6495f0c158d9bAndy Huang                        if (mWebViewLoadStartMs != 0) {
103663b3c6725d60386f564ab53e3ba6495f0c158d9bAndy Huang                            LogUtils.i(LOG_TAG, "IN CVF.onContentReady, f=%s vis=%s t=%sms",
103763b3c6725d60386f564ab53e3ba6495f0c158d9bAndy Huang                                    ConversationViewFragment.this,
103863b3c6725d60386f564ab53e3ba6495f0c158d9bAndy Huang                                    isUserVisible(),
103963b3c6725d60386f564ab53e3ba6495f0c158d9bAndy Huang                                    (SystemClock.uptimeMillis() - mWebViewLoadStartMs));
104063b3c6725d60386f564ab53e3ba6495f0c158d9bAndy Huang                        }
10417d4746ec15076f8982f2dc2a4bdb982b78848433Andy Huang                        revealConversation();
10429a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang                    } catch (Throwable t) {
10439a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang                        LogUtils.e(LOG_TAG, t, "Error in MailJsBridge.onContentReady");
10449a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang                        // Still try to show the conversation.
10459a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang                        revealConversation();
10463bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp                    }
10479a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang                }
10489a8bc1ead816f0398bdbeac1e744ed458028b5c4Andy Huang            });
10493bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp        }
1050e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang
1051e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang        @SuppressWarnings("unused")
1052e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang        @JavascriptInterface
1053e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang        public float getScrollYPercent() {
1054e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang            try {
1055e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang                return mWebViewYPercent;
1056e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang            } catch (Throwable t) {
1057e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang                LogUtils.e(LOG_TAG, t, "Error in MailJsBridge.getScrollYPercent");
1058e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang                return 0f;
1059e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang            }
1060e964eeec3a088b3f4c29b68d41f99e43a321ac52Andy Huang        }
1061674afa42682908640167fc6109b76f6f843e6fbeMindy Pereira    }
1062f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
106347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    private class NewMessagesInfo {
106447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        int count;
106506c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang        int countFromSelf;
106647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        String senderAddress;
106747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
106847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        /**
106947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang         * Return the display text for the new message notification overlay. It will be formatted
107047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang         * appropriately for a single new message vs. multiple new messages.
107147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang         *
107247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang         * @return display text
107347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang         */
107447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        public String getNotificationText() {
1075ad0c30d0517e06c472c76b11795092f5e67d8f5amindyp            Resources res = getResources();
107647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang            if (count > 1) {
1077ad0c30d0517e06c472c76b11795092f5e67d8f5amindyp                return res.getString(R.string.new_incoming_messages_many, count);
107847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang            } else {
10791617481de7c1f9b8dfcd25ba9828e933cf9d5490Andy Huang                final Address addr = getAddress(senderAddress);
1080ad0c30d0517e06c472c76b11795092f5e67d8f5amindyp                return res.getString(R.string.new_incoming_messages_one,
1081ad0c30d0517e06c472c76b11795092f5e67d8f5amindyp                        TextUtils.isEmpty(addr.getName()) ? addr.getAddress() : addr.getName());
108247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang            }
108347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        }
108447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    }
108547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
1086f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
1087014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang    public void onMessageCursorLoadFinished(Loader<Cursor> loader, MessageCursor newCursor,
1088014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang            MessageCursor oldCursor) {
1089f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        /*
1090f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp         * what kind of changes affect the MessageCursor? 1. new message(s) 2.
1091f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp         * read/unread state change 3. deleted message, either regular or draft
1092f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp         * 4. updated message, either from self or from others, updated in
1093f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp         * content or state or sender 5. star/unstar of message (technically
1094f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp         * similar to #1) 6. other label change Use MessageCursor.hashCode() to
1095f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp         * sort out interesting vs. no-op cursor updates.
1096f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp         */
1097014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang
1098233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang        if (oldCursor != null && !oldCursor.isClosed()) {
1099014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang            final NewMessagesInfo info = getNewIncomingMessagesInfo(newCursor);
1100014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang
1101014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang            if (info.count > 0) {
1102014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                // don't immediately render new incoming messages from other
1103014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                // senders
1104014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                // (to avoid a new message from losing the user's focus)
1105014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                LogUtils.i(LOG_TAG, "CONV RENDER: conversation updated"
11069d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                        + ", holding cursor for new incoming message (%s)", this);
1107014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                showNewMessageNotification(info);
1108014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                return;
1109014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang            }
1110014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang
111106c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang            final int oldState = oldCursor.getStateHashCode();
111206c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang            final boolean changed = newCursor.getStateHashCode() != oldState;
1113233d435cba55eda0df580d3549b8a4e3b8d789a8Andy Huang
1114014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang            if (!changed) {
1115014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                final boolean processedInPlace = processInPlaceUpdates(newCursor, oldCursor);
1116014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                if (processedInPlace) {
11179d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                    LogUtils.i(LOG_TAG, "CONV RENDER: processed update(s) in place (%s)", this);
11181ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang                } else {
1119f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    LogUtils.i(LOG_TAG, "CONV RENDER: uninteresting update"
11209d3fd92ed6091dbd0d38799222a1cf841f1c3f29Andy Huang                            + ", ignoring this conversation update (%s)", this);
11211ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang                }
11221ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang                return;
112306c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang            } else if (info.countFromSelf == 1) {
112406c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang                // Special-case the very common case of a new cursor that is the same as the old
112506c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang                // one, except that there is a new message from yourself. This happens upon send.
112606c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang                final boolean sameExceptNewLast = newCursor.getStateHashCode(1) == oldState;
112706c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang                if (sameExceptNewLast) {
112806c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang                    LogUtils.i(LOG_TAG, "CONV RENDER: update is a single new message from self"
112906c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang                            + " (%s)", this);
113006c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang                    newCursor.moveToLast();
113106c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang                    processNewOutgoingMessage(newCursor.getMessage());
113206c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang                    return;
113306c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang                }
11341ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang            }
11356766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang            // cursors are different, and not due to an incoming message. fall
11366766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang            // through and render.
11376766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang            LogUtils.i(LOG_TAG, "CONV RENDER: conversation updated"
11386766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang                    + ", but not due to incoming message. rendering. (%s)", this);
113906c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang
114006c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang            if (DEBUG_DUMP_CURSOR_CONTENTS) {
114106c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang                LogUtils.i(LOG_TAG, "old cursor: %s", oldCursor.getDebugDump());
114206c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang                LogUtils.i(LOG_TAG, "new cursor: %s", newCursor.getDebugDump());
114306c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang            }
11446766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang        } else {
11456766b6e5468d2f1935587b3bc1f8e65be94cd6fbAndy Huang            LogUtils.i(LOG_TAG, "CONV RENDER: initial render. (%s)", this);
1146b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang        }
1147b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang
11484071c2f73218ce75750345557bb31a9110737841Mark Wei        // if layout hasn't happened, delay render
11494071c2f73218ce75750345557bb31a9110737841Mark Wei        // This is needed in addition to the showConversation() delay to speed
11504071c2f73218ce75750345557bb31a9110737841Mark Wei        // up rotation and restoration.
11514071c2f73218ce75750345557bb31a9110737841Mark Wei        if (mConversationContainer.getWidth() == 0) {
11524071c2f73218ce75750345557bb31a9110737841Mark Wei            mNeedRender = true;
11534071c2f73218ce75750345557bb31a9110737841Mark Wei            mConversationContainer.addOnLayoutChangeListener(this);
11544071c2f73218ce75750345557bb31a9110737841Mark Wei        } else {
11554071c2f73218ce75750345557bb31a9110737841Mark Wei            renderConversation(newCursor);
11564071c2f73218ce75750345557bb31a9110737841Mark Wei        }
1157b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang    }
1158b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang
1159f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    private NewMessagesInfo getNewIncomingMessagesInfo(MessageCursor newCursor) {
1160f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        final NewMessagesInfo info = new NewMessagesInfo();
1161b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang
1162f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        int pos = -1;
1163f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        while (newCursor.moveToPosition(++pos)) {
1164f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            final Message m = newCursor.getMessage();
1165f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            if (!mViewState.contains(m)) {
1166f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                LogUtils.i(LOG_TAG, "conversation diff: found new msg: %s", m.uri);
1167f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
1168f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                final Address from = getAddress(m.from);
1169f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                // distinguish ours from theirs
1170f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                // new messages from the account owner should not trigger a
1171f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                // notification
1172f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                if (mAccount.ownsFromAddress(from.getAddress())) {
1173f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    LogUtils.i(LOG_TAG, "found message from self: %s", m.uri);
117406c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang                    info.countFromSelf++;
1175f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    continue;
1176f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                }
1177b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang
1178f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                info.count++;
1179f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                info.senderAddress = m.from;
1180b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang            }
1181b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang        }
1182f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return info;
1183b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang    }
1184b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang
1185014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang    private boolean processInPlaceUpdates(MessageCursor newCursor, MessageCursor oldCursor) {
1186014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang        final Set<String> idsOfChangedBodies = Sets.newHashSet();
11876b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang        final List<Integer> changedOverlayPositions = Lists.newArrayList();
11886b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang
1189014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang        boolean changed = false;
1190014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang
1191014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang        int pos = 0;
1192014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang        while (true) {
1193014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang            if (!newCursor.moveToPosition(pos) || !oldCursor.moveToPosition(pos)) {
1194014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                break;
1195014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang            }
1196014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang
1197014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang            final ConversationMessage newMsg = newCursor.getMessage();
1198014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang            final ConversationMessage oldMsg = oldCursor.getMessage();
1199014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang
12002a1e8e30bdd02dc08bdf2f878201b279f60d5142Andy Huang            if (!TextUtils.equals(newMsg.from, oldMsg.from) ||
12012a1e8e30bdd02dc08bdf2f878201b279f60d5142Andy Huang                    newMsg.isSending != oldMsg.isSending) {
12026b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang                mAdapter.updateItemsForMessage(newMsg, changedOverlayPositions);
12032a1e8e30bdd02dc08bdf2f878201b279f60d5142Andy Huang                LogUtils.i(LOG_TAG, "msg #%d (%d): detected from/sending change. isSending=%s",
12042a1e8e30bdd02dc08bdf2f878201b279f60d5142Andy Huang                        pos, newMsg.id, newMsg.isSending);
1205014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang            }
1206014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang
1207014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang            // update changed message bodies in-place
1208014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang            if (!TextUtils.equals(newMsg.bodyHtml, oldMsg.bodyHtml) ||
1209014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                    !TextUtils.equals(newMsg.bodyText, oldMsg.bodyText)) {
1210014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                // maybe just set a flag to notify JS to re-request changed bodies
1211014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                idsOfChangedBodies.add('"' + mTemplates.getMessageDomId(newMsg) + '"');
1212014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                LogUtils.i(LOG_TAG, "msg #%d (%d): detected body change", pos, newMsg.id);
1213014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang            }
1214014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang
1215014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang            pos++;
1216014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang        }
1217014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang
12186b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang
12196b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang        if (!changedOverlayPositions.isEmpty()) {
122006c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang            // notify once after the entire adapter is updated
12216b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang            mConversationContainer.onOverlayModelUpdate(changedOverlayPositions);
12226b3d0d9ab407c3d8b6bcb73bddbfd23f2513bb83Andy Huang            changed = true;
122306c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang        }
122406c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang
1225014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang        if (!idsOfChangedBodies.isEmpty()) {
1226014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang            mWebView.loadUrl(String.format("javascript:replaceMessageBodies([%s]);",
1227014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang                    TextUtils.join(",", idsOfChangedBodies)));
1228014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang            changed = true;
1229014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang        }
1230014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang
1231014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang        return changed;
1232014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang    }
1233014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang
123406c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang    private void processNewOutgoingMessage(ConversationMessage msg) {
123506c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang        mTemplates.reset();
123606c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang        // this method will add some items to mAdapter, but we deliberately want to avoid notifying
123706c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang        // adapter listeners (i.e. ConversationContainer) until onWebContentGeometryChange is next
123806c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang        // called, to prevent N+1 headers rendering with N message bodies.
123906c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang        renderMessage(msg, true /* expanded */, msg.alwaysShowImages);
124006c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang        mTempBodiesHtml = mTemplates.emit();
124106c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang
124206c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang        mViewState.setExpansionState(msg, ExpansionState.EXPANDED);
124306c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang        // FIXME: should the provider set this as initial state?
124406c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang        mViewState.setReadState(msg, false /* read */);
124506c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang
124691d782abc8015bd651fb5d0252b4d1ef369ec57bAndy Huang        // From now until the updated spacer geometry is returned, the adapter items are mismatched
124791d782abc8015bd651fb5d0252b4d1ef369ec57bAndy Huang        // with the existing spacers. Do not let them layout.
124891d782abc8015bd651fb5d0252b4d1ef369ec57bAndy Huang        mConversationContainer.invalidateSpacerGeometry();
124991d782abc8015bd651fb5d0252b4d1ef369ec57bAndy Huang
125006c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang        mWebView.loadUrl("javascript:appendMessageHtml();");
125106c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang    }
125206c0362f59437f3ea2b5832272fb66158bb4b8c0Andy Huang
1253cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook    private class SetCookieTask extends AsyncTask<Void, Void, Void> {
1254cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook        final String mUri;
1255b8361c9f8938b74977316319885998aae09aab77Paul Westbrook        final Uri mAccountCookieQueryUri;
1256b8361c9f8938b74977316319885998aae09aab77Paul Westbrook        final ContentResolver mResolver;
1257cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook
1258b8361c9f8938b74977316319885998aae09aab77Paul Westbrook        SetCookieTask(Context context, Uri baseUri, Uri accountCookieQueryUri) {
1259b8361c9f8938b74977316319885998aae09aab77Paul Westbrook            mUri = baseUri.toString();
1260b8361c9f8938b74977316319885998aae09aab77Paul Westbrook            mAccountCookieQueryUri = accountCookieQueryUri;
1261b8361c9f8938b74977316319885998aae09aab77Paul Westbrook            mResolver = context.getContentResolver();
1262cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook        }
1263cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook
1264cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook        @Override
1265cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook        public Void doInBackground(Void... args) {
1266b8361c9f8938b74977316319885998aae09aab77Paul Westbrook            // First query for the coookie string from the UI provider
1267b8361c9f8938b74977316319885998aae09aab77Paul Westbrook            final Cursor cookieCursor = mResolver.query(mAccountCookieQueryUri,
1268b8361c9f8938b74977316319885998aae09aab77Paul Westbrook                    UIProvider.ACCOUNT_COOKIE_PROJECTION, null, null, null);
1269b8361c9f8938b74977316319885998aae09aab77Paul Westbrook            if (cookieCursor == null) {
1270b8361c9f8938b74977316319885998aae09aab77Paul Westbrook                return null;
1271b8361c9f8938b74977316319885998aae09aab77Paul Westbrook            }
1272b8361c9f8938b74977316319885998aae09aab77Paul Westbrook
1273b8361c9f8938b74977316319885998aae09aab77Paul Westbrook            try {
1274b8361c9f8938b74977316319885998aae09aab77Paul Westbrook                if (cookieCursor.moveToFirst()) {
1275b8361c9f8938b74977316319885998aae09aab77Paul Westbrook                    final String cookie = cookieCursor.getString(
1276b8361c9f8938b74977316319885998aae09aab77Paul Westbrook                            cookieCursor.getColumnIndex(UIProvider.AccountCookieColumns.COOKIE));
1277b8361c9f8938b74977316319885998aae09aab77Paul Westbrook
1278b8361c9f8938b74977316319885998aae09aab77Paul Westbrook                    if (cookie != null) {
1279b8361c9f8938b74977316319885998aae09aab77Paul Westbrook                        final CookieSyncManager csm =
1280b8361c9f8938b74977316319885998aae09aab77Paul Westbrook                            CookieSyncManager.createInstance(getContext());
1281b8361c9f8938b74977316319885998aae09aab77Paul Westbrook                        CookieManager.getInstance().setCookie(mUri, cookie);
1282b8361c9f8938b74977316319885998aae09aab77Paul Westbrook                        csm.sync();
1283b8361c9f8938b74977316319885998aae09aab77Paul Westbrook                    }
1284b8361c9f8938b74977316319885998aae09aab77Paul Westbrook                }
1285b8361c9f8938b74977316319885998aae09aab77Paul Westbrook
1286b8361c9f8938b74977316319885998aae09aab77Paul Westbrook            } finally {
1287b8361c9f8938b74977316319885998aae09aab77Paul Westbrook                cookieCursor.close();
1288b8361c9f8938b74977316319885998aae09aab77Paul Westbrook            }
1289b8361c9f8938b74977316319885998aae09aab77Paul Westbrook
1290b8361c9f8938b74977316319885998aae09aab77Paul Westbrook
1291cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook            return null;
1292cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook        }
1293cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook    }
129436280f3c2939bb2dacb4077bd528900346ff4bb9mindyp
129526d4d2d9c43c499f458808f050ec73ea3c28dec4mindyp    @Override
129636280f3c2939bb2dacb4077bd528900346ff4bb9mindyp    public void onConversationUpdated(Conversation conv) {
129736280f3c2939bb2dacb4077bd528900346ff4bb9mindyp        final ConversationViewHeader headerView = (ConversationViewHeader) mConversationContainer
129836280f3c2939bb2dacb4077bd528900346ff4bb9mindyp                .findViewById(R.id.conversation_header);
1299b2b98ba97983f225eec19dc9bc5333e6ef80ee15mindyp        mConversation = conv;
13009e0b236be55cf9dab8f1670a9d91ed16e055a9d0mindyp        if (headerView != null) {
13019e0b236be55cf9dab8f1670a9d91ed16e055a9d0mindyp            headerView.onConversationUpdated(conv);
13029e0b236be55cf9dab8f1670a9d91ed16e055a9d0mindyp        }
130336280f3c2939bb2dacb4077bd528900346ff4bb9mindyp    }
13044071c2f73218ce75750345557bb31a9110737841Mark Wei
13054071c2f73218ce75750345557bb31a9110737841Mark Wei    @Override
13064071c2f73218ce75750345557bb31a9110737841Mark Wei    public void onLayoutChange(View v, int left, int top, int right,
13074071c2f73218ce75750345557bb31a9110737841Mark Wei            int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
13084071c2f73218ce75750345557bb31a9110737841Mark Wei        boolean sizeChanged = mNeedRender
13094071c2f73218ce75750345557bb31a9110737841Mark Wei                && mConversationContainer.getWidth() != 0;
13104071c2f73218ce75750345557bb31a9110737841Mark Wei        if (sizeChanged) {
13114071c2f73218ce75750345557bb31a9110737841Mark Wei            mNeedRender = false;
13124071c2f73218ce75750345557bb31a9110737841Mark Wei            mConversationContainer.removeOnLayoutChangeListener(this);
13134071c2f73218ce75750345557bb31a9110737841Mark Wei            renderConversation(getMessageCursor());
13144071c2f73218ce75750345557bb31a9110737841Mark Wei        }
13154071c2f73218ce75750345557bb31a9110737841Mark Wei    }
13161b3cc47f54072105c161d6ed557550e0e149b8bbmindyp
13171b3cc47f54072105c161d6ed557550e0e149b8bbmindyp    @Override
13181b3cc47f54072105c161d6ed557550e0e149b8bbmindyp    public void setMessageDetailsExpanded(MessageHeaderItem i, boolean expanded,
13191b3cc47f54072105c161d6ed557550e0e149b8bbmindyp            int heightBefore) {
13201b3cc47f54072105c161d6ed557550e0e149b8bbmindyp        mDiff = (expanded ? 1 : -1) * Math.abs(i.getHeight() - heightBefore);
13211b3cc47f54072105c161d6ed557550e0e149b8bbmindyp    }
13229b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira}
1323