ConversationViewFragment.java revision afc9b365dc9199ee9b2a1e598b8f40b3c78b6d9f
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
219b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereiraimport android.content.Context;
228e915724b6e4374da9b70161ee0a55f0c763e563Mindy Pereiraimport android.content.Loader;
239b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereiraimport android.database.Cursor;
24cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrookimport android.os.AsyncTask;
259b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereiraimport android.os.Bundle;
263bcf180f8104bc27319086a9a6ece5a3c2917c37mindypimport android.os.SystemClock;
2747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huangimport android.text.TextUtils;
289b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereiraimport android.view.LayoutInflater;
299b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereiraimport android.view.View;
309b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereiraimport android.view.ViewGroup;
31f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport android.webkit.ConsoleMessage;
32cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrookimport android.webkit.CookieManager;
33cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrookimport android.webkit.CookieSyncManager;
34974c966c14000d42f089e2021b098d5cf61f9245Mindy Pereiraimport android.webkit.JavascriptInterface;
35f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport android.webkit.WebChromeClient;
36f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport android.webkit.WebSettings;
3717a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huangimport android.webkit.WebView;
3817a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huangimport android.webkit.WebViewClient;
3947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huangimport android.widget.TextView;
409b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira
4159e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huangimport com.android.mail.FormattedDateBuilder;
429b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereiraimport com.android.mail.R;
435ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huangimport com.android.mail.browse.ConversationContainer;
4446dfba6160b55a582b344328067e3dafeb881dd9Andy Huangimport com.android.mail.browse.ConversationOverlayItem;
457bdc3750454efe59617b7df945eadd7e59bee954Andy Huangimport com.android.mail.browse.ConversationViewAdapter;
4656d83850db72592a16f4e6ee9e0d59b60ec0824aMark Weiimport com.android.mail.browse.ScrollIndicatorsView;
4728b7aee7fa1f7d096d33fc823a88a64f7a3fa79dAndy Huangimport com.android.mail.browse.ConversationViewAdapter.ConversationAccountController;
4846dfba6160b55a582b344328067e3dafeb881dd9Andy Huangimport com.android.mail.browse.ConversationViewAdapter.MessageFooterItem;
497bdc3750454efe59617b7df945eadd7e59bee954Andy Huangimport com.android.mail.browse.ConversationViewAdapter.MessageHeaderItem;
5046dfba6160b55a582b344328067e3dafeb881dd9Andy Huangimport com.android.mail.browse.ConversationViewAdapter.SuperCollapsedBlockItem;
515ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huangimport com.android.mail.browse.ConversationViewHeader;
525ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huangimport com.android.mail.browse.ConversationWebView;
53dde3f9ff76c1a7f9a6e9fa959a6422a8a8d82e81mindypimport com.android.mail.browse.ConversationWebView.ContentSizeChangeListener;
547bdc3750454efe59617b7df945eadd7e59bee954Andy Huangimport com.android.mail.browse.MessageCursor;
55cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huangimport com.android.mail.browse.MessageCursor.ConversationController;
5628b7aee7fa1f7d096d33fc823a88a64f7a3fa79dAndy Huangimport com.android.mail.browse.MessageCursor.ConversationMessage;
5759e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huangimport com.android.mail.browse.MessageHeaderView;
583233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huangimport com.android.mail.browse.MessageHeaderView.MessageHeaderViewCallbacks;
5946dfba6160b55a582b344328067e3dafeb881dd9Andy Huangimport com.android.mail.browse.SuperCollapsedBlock;
600b7ed6fae6e36f2abc4ca916764177d1879731b4Andy Huangimport com.android.mail.browse.WebViewContextMenu;
619b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereiraimport com.android.mail.providers.Account;
6265fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huangimport com.android.mail.providers.Address;
639b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereiraimport com.android.mail.providers.Conversation;
64f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport com.android.mail.providers.Message;
65cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huangimport com.android.mail.ui.ConversationViewState.ExpansionState;
66b334c9035e9b7a38766bb66c29da2208525d1e11Paul Westbrookimport com.android.mail.utils.LogTag;
679b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereiraimport com.android.mail.utils.LogUtils;
682e9acfe527426c00b5df2ca66189262dc40c8575Andy Huangimport com.android.mail.utils.Utils;
6946dfba6160b55a582b344328067e3dafeb881dd9Andy Huangimport com.google.common.collect.Lists;
70b8331b4565566ca733997398e8c07a26cd2bee98Andy Huangimport com.google.common.collect.Sets;
7165fe28fa88daad08f3be4c084ca5b4eaa366d1a7Andy Huang
7246dfba6160b55a582b344328067e3dafeb881dd9Andy Huangimport java.util.List;
73b8331b4565566ca733997398e8c07a26cd2bee98Andy Huangimport java.util.Set;
749b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira
75f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
769b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira/**
779b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira * The conversation view UI component.
789b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira */
79f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyppublic final class ConversationViewFragment extends AbstractConversationViewFragment implements
8046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        MessageHeaderViewCallbacks,
81cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang        SuperCollapsedBlock.OnClickListener,
8228b7aee7fa1f7d096d33fc823a88a64f7a3fa79dAndy Huang        ConversationController,
8328b7aee7fa1f7d096d33fc823a88a64f7a3fa79dAndy Huang        ConversationAccountController {
848e915724b6e4374da9b70161ee0a55f0c763e563Mindy Pereira
85b334c9035e9b7a38766bb66c29da2208525d1e11Paul Westbrook    private static final String LOG_TAG = LogTag.getLogTag();
86632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang    public static final String LAYOUT_TAG = "ConvLayout";
879b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira
883bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp    /** Do not auto load data when create this {@link ConversationView}. */
893bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp    public static final int NO_AUTO_LOAD = 0;
903bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp    /** Auto load data but do not show any animation. */
913bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp    public static final int AUTO_LOAD_BACKGROUND = 1;
923bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp    /** Auto load data and show animation. */
933bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp    public static final int AUTO_LOAD_VISIBLE = 2;
943bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp
95f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    private ConversationContainer mConversationContainer;
969b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira
97f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    private ConversationWebView mWebView;
989b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira
9956d83850db72592a16f4e6ee9e0d59b60ec0824aMark Wei    private ScrollIndicatorsView mScrollIndicators;
10056d83850db72592a16f4e6ee9e0d59b60ec0824aMark Wei
10147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    private View mNewMessageBar;
10247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
103f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    private HtmlConversationTemplates mTemplates;
104f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
105f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    private final MailJsBridge mJsBridge = new MailJsBridge();
106f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
10717a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huang    private final WebViewClient mWebViewClient = new ConversationWebViewClient();
10817a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huang
1097bdc3750454efe59617b7df945eadd7e59bee954Andy Huang    private ConversationViewAdapter mAdapter;
11051067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang
11151067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang    private boolean mViewsCreated;
11251067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang
11346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    /**
11446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang     * Temporary string containing the message bodies of the messages within a super-collapsed
11546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang     * block, for one-time use during block expansion. We cannot easily pass the body HTML
11646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang     * into JS without problematic escaping, so hold onto it momentarily and signal JS to fetch it
11746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang     * using {@link MailJsBridge}.
11846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang     */
11946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    private String mTempBodiesHtml;
12046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
121632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang    private int  mMaxAutoLoadMessages;
122632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang
123632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang    private boolean mDeferredConversationLoad;
124632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang
1253bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp    private boolean mEnableContentReadySignal;
12628b7aee7fa1f7d096d33fc823a88a64f7a3fa79dAndy Huang
127dde3f9ff76c1a7f9a6e9fa959a6422a8a8d82e81mindyp    private ContentSizeChangeListener mWebViewSizeChangeListener;
128dde3f9ff76c1a7f9a6e9fa959a6422a8a8d82e81mindyp
129839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang    private static final String BUNDLE_VIEW_STATE = "viewstate";
130f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
131bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang    private static final boolean DEBUG_DUMP_CONVERSATION_HTML = false;
13247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    private static final boolean DISABLE_OFFSCREEN_LOADING = false;
1333bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp    protected static final String AUTO_LOAD_KEY = "auto-load";
134bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang
1356c51158ad3269f157424e6c7bd488425c98da08fVikram Aggarwal    /**
1366c51158ad3269f157424e6c7bd488425c98da08fVikram Aggarwal     * Constructor needs to be public to handle orientation changes and activity lifecycle events.
1376c51158ad3269f157424e6c7bd488425c98da08fVikram Aggarwal     */
138f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    public ConversationViewFragment() {
1396c51158ad3269f157424e6c7bd488425c98da08fVikram Aggarwal        super();
1409b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira    }
1419b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira
1429b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira    /**
1439b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira     * Creates a new instance of {@link ConversationViewFragment}, initialized
144632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang     * to display a conversation with other parameters inherited/copied from an existing bundle,
145632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang     * typically one created using {@link #makeBasicArgs}.
146632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang     */
147632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang    public static ConversationViewFragment newInstance(Bundle existingArgs,
148632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang            Conversation conversation) {
149632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang        ConversationViewFragment f = new ConversationViewFragment();
150632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang        Bundle args = new Bundle(existingArgs);
151632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang        args.putParcelable(ARG_CONVERSATION, conversation);
152632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang        f.setArguments(args);
153632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang        return f;
154632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang    }
155632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang
156f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
157f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public void onAccountChanged() {
158f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        // settings may have been updated; refresh views that are known to
159f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        // depend on settings
160f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mConversationContainer.getSnapHeader().onAccountChanged();
161f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mAdapter.notifyDataSetChanged();
162632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang    }
163632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang
1649b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira    @Override
1659b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira    public void onActivityCreated(Bundle savedInstanceState) {
166632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang        LogUtils.d(LOG_TAG, "IN CVF.onActivityCreated, this=%s subj=%s", this,
167632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang                mConversation.subject);
1689b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira        super.onActivityCreated(savedInstanceState);
169f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        Context context = getContext();
170f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mTemplates = new HtmlConversationTemplates(context);
17159e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang
172f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        final FormattedDateBuilder dateBuilder = new FormattedDateBuilder(context);
17359e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang
1748081df46ef5a7794374e41cd1836e778a2da9b31Paul Westbrook        mAdapter = new ConversationViewAdapter(mActivity, this,
175f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                getLoaderManager(), this, getContactInfoSource(), this,
1768081df46ef5a7794374e41cd1836e778a2da9b31Paul Westbrook                this, mAddressCache, dateBuilder);
17751067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang        mConversationContainer.setOverlayAdapter(mAdapter);
17851067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang
17959e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang        // set up snap header (the adapter usually does this with the other ones)
18059e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang        final MessageHeaderView snapHeader = mConversationContainer.getSnapHeader();
18128b7aee7fa1f7d096d33fc823a88a64f7a3fa79dAndy Huang        snapHeader.initialize(dateBuilder, this, mAddressCache);
18259e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang        snapHeader.setCallbacks(this);
183f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        snapHeader.setContactInfoSource(getContactInfoSource());
18459e0b18db1bd06cfb74693d7dbb0cb48112a69b1Andy Huang
185632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang        mMaxAutoLoadMessages = getResources().getInteger(R.integer.max_auto_load_messages);
186632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang
187f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mWebView.setOnCreateContextMenuListener(new WebViewContextMenu(getActivity()));
1880b7ed6fae6e36f2abc4ca916764177d1879731b4Andy Huang
1899b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira        showConversation();
190cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook
191cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook        if (mConversation.conversationBaseUri != null &&
192cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook                !TextUtils.isEmpty(mConversation.conversationCookie)) {
193cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook            // Set the cookie for this base url
194cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook            new SetCookieTask(mConversation.conversationBaseUri.toString(),
195cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook                    mConversation.conversationCookie).execute();
196cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook        }
1979b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira    }
1989b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira
1999b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira    @Override
2009b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira    public View onCreateView(LayoutInflater inflater,
2019b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira            ViewGroup container, Bundle savedInstanceState) {
202839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang        if (savedInstanceState != null) {
203839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang            mViewState = savedInstanceState.getParcelable(BUNDLE_VIEW_STATE);
204839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang        } else {
2054d8cad5e37ade03903a23cca8ea3e782af21170fPaul Westbrook            mViewState = getNewViewState();
206839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang        }
207839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang
208632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang        View rootView = inflater.inflate(R.layout.conversation_view, container, false);
209f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        mConversationContainer = (ConversationContainer) rootView
210f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang                .findViewById(R.id.conversation_container);
21147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
21247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        mNewMessageBar = mConversationContainer.findViewById(R.id.new_message_notification_bar);
21347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        mNewMessageBar.setOnClickListener(new View.OnClickListener() {
21447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang            @Override
21547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang            public void onClick(View v) {
21647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang                onNewMessageBarClick();
21747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang            }
21847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        });
21947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
220ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp        instantiateProgressIndicators(rootView);
2213bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp
2225ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang        mWebView = (ConversationWebView) mConversationContainer.findViewById(R.id.webview);
223f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
224f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        mWebView.addJavascriptInterface(mJsBridge, "mail");
2253bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp        // On JB or newer, we use the 'webkitAnimationStart' DOM event to signal load complete
2263bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp        // Below JB, try to speed up initial render by having the webview do supplemental draws to
2273bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp        // custom a software canvas.
228b941fdb95f61cdde4e1775ac9034343951e8b075mindyp        // TODO(mindyp):
229b941fdb95f61cdde4e1775ac9034343951e8b075mindyp        //PAGE READINESS SIGNAL FOR JELLYBEAN AND NEWER
230b941fdb95f61cdde4e1775ac9034343951e8b075mindyp        // Notify the app on 'webkitAnimationStart' of a simple dummy element with a simple no-op
231b941fdb95f61cdde4e1775ac9034343951e8b075mindyp        // animation that immediately runs on page load. The app uses this as a signal that the
232b941fdb95f61cdde4e1775ac9034343951e8b075mindyp        // content is loaded and ready to draw, since WebView delays firing this event until the
233b941fdb95f61cdde4e1775ac9034343951e8b075mindyp        // layers are composited and everything is ready to draw.
234b941fdb95f61cdde4e1775ac9034343951e8b075mindyp        // This signal does not seem to be reliable, so just use the old method for now.
23532d911f491afad153983f7519267ddb764927355mindyp        mEnableContentReadySignal = Utils.isRunningJellybeanOrLater();
236afc9b365dc9199ee9b2a1e598b8f40b3c78b6d9fmindyp        mWebView.setUseSoftwareLayer(!mEnableContentReadySignal);
23717a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huang        mWebView.setWebViewClient(mWebViewClient);
238f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        mWebView.setWebChromeClient(new WebChromeClient() {
239f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang            @Override
240f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang            public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
241f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang                LogUtils.i(LOG_TAG, "JS: %s (%s:%d)", consoleMessage.message(),
242f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang                        consoleMessage.sourceId(), consoleMessage.lineNumber());
243f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang                return true;
244f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang            }
245f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        });
246f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
2473233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang        final WebSettings settings = mWebView.getSettings();
248f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
24956d83850db72592a16f4e6ee9e0d59b60ec0824aMark Wei        mScrollIndicators = (ScrollIndicatorsView) rootView.findViewById(R.id.scroll_indicators);
25056d83850db72592a16f4e6ee9e0d59b60ec0824aMark Wei        mScrollIndicators.setSourceView(mWebView);
25156d83850db72592a16f4e6ee9e0d59b60ec0824aMark Wei
252f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        settings.setJavaScriptEnabled(true);
253f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        settings.setUseWideViewPort(true);
25423014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang        settings.setLoadWithOverviewMode(true);
255f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
256f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        settings.setSupportZoom(true);
257f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        settings.setBuiltInZoomControls(true);
258f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        settings.setDisplayZoomControls(false);
2599b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira
260c319b551bebe40b9607acf0ee1d26a880fde9212Andy Huang        final float fontScale = getResources().getConfiguration().fontScale;
261ba283732e7cbcc55a3dbc8ab78950cc38cd078fbAndy Huang        final int desiredFontSizePx = getResources()
262ba283732e7cbcc55a3dbc8ab78950cc38cd078fbAndy Huang                .getInteger(R.integer.conversation_desired_font_size_px);
263ba283732e7cbcc55a3dbc8ab78950cc38cd078fbAndy Huang        final int unstyledFontSizePx = getResources()
264ba283732e7cbcc55a3dbc8ab78950cc38cd078fbAndy Huang                .getInteger(R.integer.conversation_unstyled_font_size_px);
265ba283732e7cbcc55a3dbc8ab78950cc38cd078fbAndy Huang
266ba283732e7cbcc55a3dbc8ab78950cc38cd078fbAndy Huang        int textZoom = settings.getTextZoom();
267ba283732e7cbcc55a3dbc8ab78950cc38cd078fbAndy Huang        // apply a correction to the default body text style to get regular text to the size we want
268ba283732e7cbcc55a3dbc8ab78950cc38cd078fbAndy Huang        textZoom = textZoom * desiredFontSizePx / unstyledFontSizePx;
269ba283732e7cbcc55a3dbc8ab78950cc38cd078fbAndy Huang        // then apply any system font scaling
270c319b551bebe40b9607acf0ee1d26a880fde9212Andy Huang        textZoom = (int) (textZoom * fontScale);
271c319b551bebe40b9607acf0ee1d26a880fde9212Andy Huang        settings.setTextZoom(textZoom);
272c319b551bebe40b9607acf0ee1d26a880fde9212Andy Huang
27351067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang        mViewsCreated = true;
27451067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang
2759b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira        return rootView;
2769b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira    }
2779b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira
2789b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira    @Override
279a6e965ef4a7ac2266f0a5509be25ac1e8d272595Andy Huang    public void onResume() {
280a6e965ef4a7ac2266f0a5509be25ac1e8d272595Andy Huang        super.onResume();
281a6e965ef4a7ac2266f0a5509be25ac1e8d272595Andy Huang
282a6e965ef4a7ac2266f0a5509be25ac1e8d272595Andy Huang        // Hacky workaround for http://b/6946182
283a6e965ef4a7ac2266f0a5509be25ac1e8d272595Andy Huang        Utils.fixSubTreeLayoutIfOrphaned(getView(), "ConversationViewFragment");
284a6e965ef4a7ac2266f0a5509be25ac1e8d272595Andy Huang    }
285a6e965ef4a7ac2266f0a5509be25ac1e8d272595Andy Huang
286a6e965ef4a7ac2266f0a5509be25ac1e8d272595Andy Huang    @Override
287839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang    public void onSaveInstanceState(Bundle outState) {
288839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang        if (mViewState != null) {
289839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang            outState.putParcelable(BUNDLE_VIEW_STATE, mViewState);
290839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang        }
291839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang    }
292839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang
293839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang    @Override
2949b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira    public void onDestroyView() {
2959b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira        super.onDestroyView();
29646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        mConversationContainer.setOverlayAdapter(null);
29746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        mAdapter = null;
29851067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang        mViewsCreated = false;
2999b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira    }
3009b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira
3015ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang    @Override
302f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    protected void markUnread() {
303839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang        // Ignore unsafe calls made after a fragment is detached from an activity
304839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang        final ControllableActivity activity = (ControllableActivity) getActivity();
305839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang        if (activity == null) {
306839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang            LogUtils.w(LOG_TAG, "ignoring markUnread for conv=%s", mConversation.id);
307839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang            return;
308839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang        }
309839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang
31028e31e2417d775b343919cf6018f85871e40a294Andy Huang        if (mViewState == null) {
31128e31e2417d775b343919cf6018f85871e40a294Andy Huang            LogUtils.i(LOG_TAG, "ignoring markUnread for conv with no view state (%d)",
31228e31e2417d775b343919cf6018f85871e40a294Andy Huang                    mConversation.id);
31328e31e2417d775b343919cf6018f85871e40a294Andy Huang            return;
31428e31e2417d775b343919cf6018f85871e40a294Andy Huang        }
315839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang        activity.getConversationUpdater().markConversationMessagesUnread(mConversation,
3164a878b606961825fb4fd412b460df75779f09ad2Vikram Aggarwal                mViewState.getUnreadMessageUris(), mViewState.getConversationInfo());
317839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang    }
318839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang
319f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
320f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public void onUserVisibleHintChanged() {
321f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        if (mUserVisible && mViewsCreated) {
322f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            Cursor cursor = getMessageCursor();
323f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            if (cursor == null && mDeferredConversationLoad) {
324f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                // load
325f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                LogUtils.v(LOG_TAG, "Fragment is now user-visible, showing conversation: %s",
326f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                        mConversation.uri);
327f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                showConversation();
328f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                mDeferredConversationLoad = false;
329f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            } else {
330f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                onConversationSeen();
331632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang            }
33232d911f491afad153983f7519267ddb764927355mindyp        } else if (!mUserVisible) {
33332d911f491afad153983f7519267ddb764927355mindyp            dismissLoadingStatus();
334632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang        }
335632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang    }
336632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang
3379b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira    private void showConversation() {
3383bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp        final boolean disableOffscreenLoading = DISABLE_OFFSCREEN_LOADING
3393bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp                || (mConversation.isRemote
3403bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp                        || mConversation.getNumMessages() > mMaxAutoLoadMessages);
34147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        if (!mUserVisible && disableOffscreenLoading) {
342632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang            LogUtils.v(LOG_TAG, "Fragment not user-visible, not showing conversation: %s",
343632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang                    mConversation.uri);
344632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang            mDeferredConversationLoad = true;
345632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang            return;
346632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang        }
347632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang        LogUtils.v(LOG_TAG,
348632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang                "Fragment is short or user-visible, immediately rendering conversation: %s",
349632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang                mConversation.uri);
3503bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp        mWebView.setVisibility(View.VISIBLE);
351f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        getLoaderManager().initLoader(MESSAGE_LOADER, Bundle.EMPTY, getMessageLoaderCallbacks());
3525460ce477cb1dcc439dd4e5c9e8393470f00f618Andy Huang        if (mUserVisible) {
3535460ce477cb1dcc439dd4e5c9e8393470f00f618Andy Huang            final SubjectDisplayChanger sdc = mActivity.getSubjectDisplayChanger();
3545460ce477cb1dcc439dd4e5c9e8393470f00f618Andy Huang            if (sdc != null) {
3555460ce477cb1dcc439dd4e5c9e8393470f00f618Andy Huang                sdc.setSubject(mConversation.subject);
3565460ce477cb1dcc439dd4e5c9e8393470f00f618Andy Huang            }
3575460ce477cb1dcc439dd4e5c9e8393470f00f618Andy Huang        }
3583bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp        // TODO(mindyp): don't show loading status for a previously rendered
3593bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp        // conversation. Ielieve this is better done by making sure don't show loading status
3603bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp        // until XX ms have passed without loading completed.
3613bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp        showLoadingStatus();
3628e915724b6e4374da9b70161ee0a55f0c763e563Mindy Pereira    }
3638e915724b6e4374da9b70161ee0a55f0c763e563Mindy Pereira
36451067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang    private void renderConversation(MessageCursor messageCursor) {
3653bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp        final String convHtml = renderMessageBodies(messageCursor, mEnableContentReadySignal);
366bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang
367bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang        if (DEBUG_DUMP_CONVERSATION_HTML) {
368bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang            java.io.FileWriter fw = null;
369bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang            try {
370bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang                fw = new java.io.FileWriter("/sdcard/conv" + mConversation.id
371bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang                        + ".html");
372bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang                fw.write(convHtml);
373bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang            } catch (java.io.IOException e) {
374bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang                e.printStackTrace();
375bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang            } finally {
376bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang                if (fw != null) {
377bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang                    try {
378bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang                        fw.close();
379bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang                    } catch (java.io.IOException e) {
380bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang                        e.printStackTrace();
381bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang                    }
382bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang                }
383bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang            }
384bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang        }
385bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang
386bd544e3c9c703042b68e6ad611104850ab8e0094Andy Huang        mWebView.loadDataWithBaseURL(mBaseUri, convHtml, "text/html", "utf-8", null);
38751067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang    }
38851067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang
3897bdc3750454efe59617b7df945eadd7e59bee954Andy Huang    /**
3907bdc3750454efe59617b7df945eadd7e59bee954Andy Huang     * Populate the adapter with overlay views (message headers, super-collapsed blocks, a
3917bdc3750454efe59617b7df945eadd7e59bee954Andy Huang     * conversation header), and return an HTML document with spacer divs inserted for all overlays.
3927bdc3750454efe59617b7df945eadd7e59bee954Andy Huang     *
3937bdc3750454efe59617b7df945eadd7e59bee954Andy Huang     */
3943bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp    private String renderMessageBodies(MessageCursor messageCursor,
3953bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp            boolean enableContentReadySignal) {
396f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        int pos = -1;
397632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang
3981ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang        LogUtils.d(LOG_TAG, "IN renderMessageBodies, fragment=%s", this);
3997bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        boolean allowNetworkImages = false;
4007bdc3750454efe59617b7df945eadd7e59bee954Andy Huang
401c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang        // TODO: re-use any existing adapter item state (expanded, details expanded, show pics)
40228b7aee7fa1f7d096d33fc823a88a64f7a3fa79dAndy Huang
4037bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        // Walk through the cursor and build up an overlay adapter as you go.
4047bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        // Each overlay has an entry in the adapter for easy scroll handling in the container.
4057bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        // Items are not necessarily 1:1 in cursor and adapter because of super-collapsed blocks.
4067bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        // When adding adapter items, also add their heights to help the container later determine
4077bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        // overlay dimensions.
4087bdc3750454efe59617b7df945eadd7e59bee954Andy Huang
409db620fe42dcf1909468822b61238a23103d15376Andy Huang        // When re-rendering, prevent ConversationContainer from laying out overlays until after
410db620fe42dcf1909468822b61238a23103d15376Andy Huang        // the new spacers are positioned by WebView.
411db620fe42dcf1909468822b61238a23103d15376Andy Huang        mConversationContainer.invalidateSpacerGeometry();
412db620fe42dcf1909468822b61238a23103d15376Andy Huang
4137bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        mAdapter.clear();
4147bdc3750454efe59617b7df945eadd7e59bee954Andy Huang
41547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        // re-evaluate the message parts of the view state, since the messages may have changed
41647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        // since the previous render
41747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        final ConversationViewState prevState = mViewState;
41847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        mViewState = new ConversationViewState(prevState);
41947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
4205ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang        // N.B. the units of height for spacers are actually dp and not px because WebView assumes
4212e9acfe527426c00b5df2ca66189262dc40c8575Andy Huang        // a pixel is an mdpi pixel, unless you set device-dpi.
4225ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang
4237bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        // add a single conversation header item
4247bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        final int convHeaderPos = mAdapter.addConversationHeader(mConversation);
42523014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang        final int convHeaderPx = measureOverlayHeight(convHeaderPos);
4263233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang
427256b35c0a8287f48c28e0d1ba3fae65790063295Andy Huang        final int sideMarginPx = getResources().getDimensionPixelOffset(
428256b35c0a8287f48c28e0d1ba3fae65790063295Andy Huang                R.dimen.conversation_view_margin_side) + getResources().getDimensionPixelOffset(
429256b35c0a8287f48c28e0d1ba3fae65790063295Andy Huang                R.dimen.conversation_message_content_margin_side);
430256b35c0a8287f48c28e0d1ba3fae65790063295Andy Huang
431256b35c0a8287f48c28e0d1ba3fae65790063295Andy Huang        mTemplates.startConversation(mWebView.screenPxToWebPx(sideMarginPx),
432256b35c0a8287f48c28e0d1ba3fae65790063295Andy Huang                mWebView.screenPxToWebPx(convHeaderPx));
4333233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang
43446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        int collapsedStart = -1;
435839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang        ConversationMessage prevCollapsedMsg = null;
43646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        boolean prevSafeForImages = false;
43746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
438f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        while (messageCursor.moveToPosition(++pos)) {
439839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang            final ConversationMessage msg = messageCursor.getMessage();
44046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
4413233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang            // TODO: save/restore 'show pics' state
4423233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang            final boolean safeForImages = msg.alwaysShowImages /* || savedStateSaysSafe */;
4433233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang            allowNetworkImages |= safeForImages;
4442405528bb15245f594277fe7b6efacd6a18ce69eAndy Huang
44508098ec4c894d9a15dfe800ad2397494e7e0a79aPaul Westbrook            final Integer savedExpanded = prevState.getExpansionState(msg);
44608098ec4c894d9a15dfe800ad2397494e7e0a79aPaul Westbrook            final int expandedState;
447839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang            if (savedExpanded != null) {
4481ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang                if (ExpansionState.isSuperCollapsed(savedExpanded) && messageCursor.isLast()) {
4491ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang                    // override saved state when this is now the new last message
4501ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang                    // this happens to the second-to-last message when you discard a draft
4511ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang                    expandedState = ExpansionState.EXPANDED;
4521ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang                } else {
4531ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang                    expandedState = savedExpanded;
4541ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang                }
455839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang            } else {
456cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang                // new messages that are not expanded default to being eligible for super-collapse
45708098ec4c894d9a15dfe800ad2397494e7e0a79aPaul Westbrook                expandedState = (!msg.read || msg.starred || messageCursor.isLast()) ?
458cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang                        ExpansionState.EXPANDED : ExpansionState.SUPER_COLLAPSED;
459839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang            }
46008098ec4c894d9a15dfe800ad2397494e7e0a79aPaul Westbrook            mViewState.setExpansionState(msg, expandedState);
461839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang
462839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang            // save off "read" state from the cursor
463839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang            // later, the view may not match the cursor (e.g. conversation marked read on open)
464423bea25992492efea7d414819729f9eae7ce72eAndy Huang            // however, if a previous state indicated this message was unread, trust that instead
465423bea25992492efea7d414819729f9eae7ce72eAndy Huang            // so "mark unread" marks all originally unread messages
466423bea25992492efea7d414819729f9eae7ce72eAndy Huang            mViewState.setReadState(msg, msg.read && !prevState.isUnread(msg));
467c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang
468cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang            // We only want to consider this for inclusion in the super collapsed block if
469cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang            // 1) The we don't have previous state about this message  (The first time that the
470cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang            //    user opens a conversation)
471cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang            // 2) The previously saved state for this message indicates that this message is
472cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang            //    in the super collapsed block.
473cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang            if (ExpansionState.isSuperCollapsed(expandedState)) {
474cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang                // contribute to a super-collapsed block that will be emitted just before the
475cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang                // next expanded header
476cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang                if (collapsedStart < 0) {
477cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang                    collapsedStart = pos;
47846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                }
479cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang                prevCollapsedMsg = msg;
480cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang                prevSafeForImages = safeForImages;
481cd5c5eeae167885ffa2959c200233fea2f39c5f7Andy Huang                continue;
48246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            }
4837bdc3750454efe59617b7df945eadd7e59bee954Andy Huang
48446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            // resolve any deferred decisions on previous collapsed items
48546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            if (collapsedStart >= 0) {
48646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                if (pos - collapsedStart == 1) {
48746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                    // special-case for a single collapsed message: no need to super-collapse it
48846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                    renderMessage(prevCollapsedMsg, false /* expanded */,
48946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                            prevSafeForImages);
49046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                } else {
49146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                    renderSuperCollapsedBlock(collapsedStart, pos - 1);
49246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                }
49346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                prevCollapsedMsg = null;
49446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                collapsedStart = -1;
49546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            }
4962405528bb15245f594277fe7b6efacd6a18ce69eAndy Huang
49708098ec4c894d9a15dfe800ad2397494e7e0a79aPaul Westbrook            renderMessage(msg, ExpansionState.isExpanded(expandedState), safeForImages);
498f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        }
4993233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang
5003233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang        mWebView.getSettings().setBlockNetworkImage(!allowNetworkImages);
5013233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang
502cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook        // If the conversation has specified a base uri, use it here, use mBaseUri
503cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook        final String conversationBaseUri = mConversation.conversationBaseUri != null ?
504cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook                mConversation.conversationBaseUri.toString() : mBaseUri;
505cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook        return mTemplates.endConversation(mBaseUri, conversationBaseUri, 320,
5063bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp                mWebView.getViewportWidth(), enableContentReadySignal);
507f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    }
508f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
50946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    private void renderSuperCollapsedBlock(int start, int end) {
51046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        final int blockPos = mAdapter.addSuperCollapsedBlock(start, end);
51123014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang        final int blockPx = measureOverlayHeight(blockPos);
51223014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang        mTemplates.appendSuperCollapsedHtml(start, mWebView.screenPxToWebPx(blockPx));
51346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    }
51446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
515839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang    private void renderMessage(ConversationMessage msg, boolean expanded,
516839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang            boolean safeForImages) {
51746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        final int headerPos = mAdapter.addMessageHeader(msg, expanded);
51846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        final MessageHeaderItem headerItem = (MessageHeaderItem) mAdapter.getItem(headerPos);
51946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
52046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        final int footerPos = mAdapter.addMessageFooter(headerItem);
52146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
52246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        // Measure item header and footer heights to allocate spacers in HTML
52346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        // But since the views themselves don't exist yet, render each item temporarily into
52446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        // a host view for measurement.
52523014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang        final int headerPx = measureOverlayHeight(headerPos);
52623014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang        final int footerPx = measureOverlayHeight(footerPos);
52746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
528256b35c0a8287f48c28e0d1ba3fae65790063295Andy Huang        mTemplates.appendMessageHtml(msg, expanded, safeForImages,
52923014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang                mWebView.screenPxToWebPx(headerPx), mWebView.screenPxToWebPx(footerPx));
53046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    }
53146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
53246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    private String renderCollapsedHeaders(MessageCursor cursor,
53346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            SuperCollapsedBlockItem blockToReplace) {
53446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        final List<ConversationOverlayItem> replacements = Lists.newArrayList();
53546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
53646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        mTemplates.reset();
53746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
5382b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei        // In devices with non-integral density multiplier, screen pixels translate to non-integral
5392b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei        // web pixels. Keep track of the error that occurs when we cast all heights to int
5402b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei        float error = 0f;
54146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        for (int i = blockToReplace.getStart(), end = blockToReplace.getEnd(); i <= end; i++) {
54246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            cursor.moveToPosition(i);
543839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang            final ConversationMessage msg = cursor.getMessage();
54446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            final MessageHeaderItem header = mAdapter.newMessageHeaderItem(msg,
54546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                    false /* expanded */);
54646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            final MessageFooterItem footer = mAdapter.newMessageFooterItem(header);
54746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
54823014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang            final int headerPx = measureOverlayHeight(header);
54923014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang            final int footerPx = measureOverlayHeight(footer);
5502b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei            error += mWebView.screenPxToWebPxError(headerPx)
5512b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei                    + mWebView.screenPxToWebPxError(footerPx);
5522b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei
5532b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei            // When the error becomes greater than 1 pixel, make the next header 1 pixel taller
5542b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei            int correction = 0;
5552b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei            if (error >= 1) {
5562b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei                correction = 1;
5572b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei                error -= 1;
5582b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei            }
55946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
560256b35c0a8287f48c28e0d1ba3fae65790063295Andy Huang            mTemplates.appendMessageHtml(msg, false /* expanded */, msg.alwaysShowImages,
5612b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei                    mWebView.screenPxToWebPx(headerPx) + correction,
5622b24e995cfcdb6ab0579b2fbcccb399a53632395Mark Wei                    mWebView.screenPxToWebPx(footerPx));
56346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            replacements.add(header);
56446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            replacements.add(footer);
565839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang
56608098ec4c894d9a15dfe800ad2397494e7e0a79aPaul Westbrook            mViewState.setExpansionState(msg, ExpansionState.COLLAPSED);
56746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        }
56846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
56946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        mAdapter.replaceSuperCollapsedBlock(blockToReplace, replacements);
57046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
57146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        return mTemplates.emit();
57246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    }
57346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
57446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    private int measureOverlayHeight(int position) {
57546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        return measureOverlayHeight(mAdapter.getItem(position));
57646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    }
57746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
5787bdc3750454efe59617b7df945eadd7e59bee954Andy Huang    /**
579b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang     * Measure the height of an adapter view by rendering an adapter item into a temporary
58046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang     * host view, and asking the view to immediately measure itself. This method will reuse
5817bdc3750454efe59617b7df945eadd7e59bee954Andy Huang     * a previous adapter view from {@link ConversationContainer}'s scrap views if one was generated
5827bdc3750454efe59617b7df945eadd7e59bee954Andy Huang     * earlier.
5837bdc3750454efe59617b7df945eadd7e59bee954Andy Huang     * <p>
58446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang     * After measuring the height, this method also saves the height in the
58546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang     * {@link ConversationOverlayItem} for later use in overlay positioning.
5867bdc3750454efe59617b7df945eadd7e59bee954Andy Huang     *
58746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang     * @param convItem adapter item with data to render and measure
58823014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang     * @return height of the rendered view in screen px
5897bdc3750454efe59617b7df945eadd7e59bee954Andy Huang     */
59046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    private int measureOverlayHeight(ConversationOverlayItem convItem) {
5917bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        final int type = convItem.getType();
5927bdc3750454efe59617b7df945eadd7e59bee954Andy Huang
5937bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        final View convertView = mConversationContainer.getScrapView(type);
594b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang        final View hostView = mAdapter.getView(convItem, convertView, mConversationContainer,
595b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang                true /* measureOnly */);
5967bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        if (convertView == null) {
5977bdc3750454efe59617b7df945eadd7e59bee954Andy Huang            mConversationContainer.addScrapView(type, hostView);
5987bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        }
5997bdc3750454efe59617b7df945eadd7e59bee954Andy Huang
6009875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang        final int heightPx = mConversationContainer.measureOverlay(hostView);
6017bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        convItem.setHeight(heightPx);
6029875bb4fc00347fc76f432a6e9ec5e6987679ca9Andy Huang        convItem.markMeasurementValid();
6037bdc3750454efe59617b7df945eadd7e59bee954Andy Huang
60423014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang        return heightPx;
6057bdc3750454efe59617b7df945eadd7e59bee954Andy Huang    }
6067bdc3750454efe59617b7df945eadd7e59bee954Andy Huang
6075ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang    @Override
6085ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang    public void onConversationViewHeaderHeightChange(int newHeight) {
6095ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang        // TODO: propagate the new height to the header's HTML spacer. This can happen when labels
6105ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang        // are added/removed
6115ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang    }
6125ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang
6133233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang    // END conversation header callbacks
6143233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang
6153233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang    // START message header callbacks
6163233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang    @Override
617c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang    public void setMessageSpacerHeight(MessageHeaderItem item, int newSpacerHeightPx) {
618c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang        mConversationContainer.invalidateSpacerGeometry();
619c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang
620c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang        // update message HTML spacer height
62123014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang        final int h = mWebView.screenPxToWebPx(newSpacerHeightPx);
62223014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang        LogUtils.i(LAYOUT_TAG, "setting HTML spacer h=%dwebPx (%dscreenPx)", h,
62323014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang                newSpacerHeightPx);
6245349ce14695dc98615108eb26f96806921b0abbaVikram Aggarwal        mWebView.loadUrl(String.format("javascript:setMessageHeaderSpacerHeight('%s', %s);",
62523014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang                mTemplates.getMessageDomId(item.message), h));
6263233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang    }
6273233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang
6283233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang    @Override
629c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang    public void setMessageExpanded(MessageHeaderItem item, int newSpacerHeightPx) {
630c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang        mConversationContainer.invalidateSpacerGeometry();
631c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang
632c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang        // show/hide the HTML message body and update the spacer height
63323014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang        final int h = mWebView.screenPxToWebPx(newSpacerHeightPx);
63423014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang        LogUtils.i(LAYOUT_TAG, "setting HTML spacer expanded=%s h=%dwebPx (%dscreenPx)",
63523014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang                item.isExpanded(), h, newSpacerHeightPx);
6365349ce14695dc98615108eb26f96806921b0abbaVikram Aggarwal        mWebView.loadUrl(String.format("javascript:setMessageBodyVisible('%s', %s, %s);",
63723014705ca9872cd5004a1aa76e83ae260165ecaAndy Huang                mTemplates.getMessageDomId(item.message), item.isExpanded(), h));
638839ada22ea84251dde3305003d2f8fc5bf14914eAndy Huang
63908098ec4c894d9a15dfe800ad2397494e7e0a79aPaul Westbrook        mViewState.setExpansionState(item.message,
64008098ec4c894d9a15dfe800ad2397494e7e0a79aPaul Westbrook                item.isExpanded() ? ExpansionState.EXPANDED : ExpansionState.COLLAPSED);
6413233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang    }
6423233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang
6433233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang    @Override
6443233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang    public void showExternalResources(Message msg) {
6453233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang        mWebView.getSettings().setBlockNetworkImage(false);
6463233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang        mWebView.loadUrl("javascript:unblockImages('" + mTemplates.getMessageDomId(msg) + "');");
6473233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang    }
6483233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang    // END message header callbacks
6495ff63747a1b5c6e2197528972cbc3ba808b09d8dAndy Huang
65046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    @Override
65146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    public void onSuperCollapsedClick(SuperCollapsedBlockItem item) {
652f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        MessageCursor cursor = getMessageCursor();
653f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        if (cursor == null || !mViewsCreated) {
65446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            return;
65546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        }
65646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
657f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        mTempBodiesHtml = renderCollapsedHeaders(cursor, item);
65846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        mWebView.loadUrl("javascript:replaceSuperCollapsedBlock(" + item.getStart() + ")");
65946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    }
66046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
66147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    private void showNewMessageNotification(NewMessagesInfo info) {
66247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        final TextView descriptionView = (TextView) mNewMessageBar.findViewById(
66347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang                R.id.new_message_description);
66447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        descriptionView.setText(info.getNotificationText());
66547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        mNewMessageBar.setVisibility(View.VISIBLE);
66647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    }
66747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
66847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    private void onNewMessageBarClick() {
66947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        mNewMessageBar.setVisibility(View.GONE);
67047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
671f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        renderConversation(getMessageCursor()); // mCursor is already up-to-date
672f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                                                // per onLoadFinished()
6735fbda023f1b0570e192e03a33834244a05edf200Andy Huang    }
6745fbda023f1b0570e192e03a33834244a05edf200Andy Huang
675b5078b287b1cec38817e342ff054ea901d199329Andy Huang    private static int[] parseInts(final String[] stringArray) {
676b5078b287b1cec38817e342ff054ea901d199329Andy Huang        final int len = stringArray.length;
677b5078b287b1cec38817e342ff054ea901d199329Andy Huang        final int[] ints = new int[len];
678b5078b287b1cec38817e342ff054ea901d199329Andy Huang        for (int i = 0; i < len; i++) {
679b5078b287b1cec38817e342ff054ea901d199329Andy Huang            ints[i] = Integer.parseInt(stringArray[i]);
680b5078b287b1cec38817e342ff054ea901d199329Andy Huang        }
681b5078b287b1cec38817e342ff054ea901d199329Andy Huang        return ints;
682b5078b287b1cec38817e342ff054ea901d199329Andy Huang    }
683b5078b287b1cec38817e342ff054ea901d199329Andy Huang
68447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    @Override
68547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    public String toString() {
68647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        // log extra info at DEBUG level or finer
68747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        final String s = super.toString();
68847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        if (!LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG) || mConversation == null) {
68947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang            return s;
69047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        }
69147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        return "(" + s + " subj=" + mConversation.subject + ")";
69247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    }
69347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
6941617481de7c1f9b8dfcd25ba9828e933cf9d5490Andy Huang    private Address getAddress(String rawFrom) {
6951617481de7c1f9b8dfcd25ba9828e933cf9d5490Andy Huang        Address addr = mAddressCache.get(rawFrom);
6961617481de7c1f9b8dfcd25ba9828e933cf9d5490Andy Huang        if (addr == null) {
6971617481de7c1f9b8dfcd25ba9828e933cf9d5490Andy Huang            addr = Address.getEmailAddress(rawFrom);
6981617481de7c1f9b8dfcd25ba9828e933cf9d5490Andy Huang            mAddressCache.put(rawFrom, addr);
6991617481de7c1f9b8dfcd25ba9828e933cf9d5490Andy Huang        }
7001617481de7c1f9b8dfcd25ba9828e933cf9d5490Andy Huang        return addr;
7011617481de7c1f9b8dfcd25ba9828e933cf9d5490Andy Huang    }
7021617481de7c1f9b8dfcd25ba9828e933cf9d5490Andy Huang
70328b7aee7fa1f7d096d33fc823a88a64f7a3fa79dAndy Huang    @Override
70428b7aee7fa1f7d096d33fc823a88a64f7a3fa79dAndy Huang    public Account getAccount() {
70528b7aee7fa1f7d096d33fc823a88a64f7a3fa79dAndy Huang        return mAccount;
70628b7aee7fa1f7d096d33fc823a88a64f7a3fa79dAndy Huang    }
70728b7aee7fa1f7d096d33fc823a88a64f7a3fa79dAndy Huang
708542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook    private class ConversationWebViewClient extends AbstractConversationWebViewClient {
70917a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huang        @Override
71017a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huang        public void onPageFinished(WebView view, String url) {
711de56e970299fb413e4136dc12a7b08c5cc89e1c1Andy Huang            // Ignore unsafe calls made after a fragment is detached from an activity
712de56e970299fb413e4136dc12a7b08c5cc89e1c1Andy Huang            final ControllableActivity activity = (ControllableActivity) getActivity();
713de56e970299fb413e4136dc12a7b08c5cc89e1c1Andy Huang            if (activity == null || !mViewsCreated) {
714de56e970299fb413e4136dc12a7b08c5cc89e1c1Andy Huang                LogUtils.i(LOG_TAG, "ignoring CVF.onPageFinished, url=%s fragment=%s", url,
715b95da853ffbff343c8ca11470de2f0047e0807a6Andy Huang                        ConversationViewFragment.this);
716b95da853ffbff343c8ca11470de2f0047e0807a6Andy Huang                return;
717b95da853ffbff343c8ca11470de2f0047e0807a6Andy Huang            }
718b95da853ffbff343c8ca11470de2f0047e0807a6Andy Huang
71947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang            LogUtils.i(LOG_TAG, "IN CVF.onPageFinished, url=%s fragment=%s act=%s", url,
72047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang                    ConversationViewFragment.this, getActivity());
721632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang
72217a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huang            super.onPageFinished(view, url);
72317a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huang
72417a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huang            // TODO: save off individual message unread state (here, or in onLoadFinished?) so
72517a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huang            // 'mark unread' restores the original unread state for each individual message
72617a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huang
727632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang            if (mUserVisible) {
728632721e6b3a9ba8c476456f2e0fb1b564561e0b5Andy Huang                onConversationSeen();
72951067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang            }
7303bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp            if (!mEnableContentReadySignal) {
7313bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp                notifyConversationLoaded(mConversation);
7323bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp                dismissLoadingStatus();
7333bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp            }
734089f2628927ed7630187f29165e6e6d9f6615891Paul Westbrook            // We are not able to use the loader manager unless this fragment is added to the
735089f2628927ed7630187f29165e6e6d9f6615891Paul Westbrook            // activity
736089f2628927ed7630187f29165e6e6d9f6615891Paul Westbrook            if (isAdded()) {
737089f2628927ed7630187f29165e6e6d9f6615891Paul Westbrook                final Set<String> emailAddresses = Sets.newHashSet();
738089f2628927ed7630187f29165e6e6d9f6615891Paul Westbrook                for (Address addr : mAddressCache.values()) {
739089f2628927ed7630187f29165e6e6d9f6615891Paul Westbrook                    emailAddresses.add(addr.getAddress());
740089f2628927ed7630187f29165e6e6d9f6615891Paul Westbrook                }
741f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                ContactLoaderCallbacks callbacks = getContactInfoSource();
742f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                getContactInfoSource().setSenders(emailAddresses);
743f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                getLoaderManager().restartLoader(CONTACT_LOADER, Bundle.EMPTY, callbacks);
744b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang            }
74517a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huang        }
74617a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huang
747af5d4e0e2a5ce3300fffa0c484431d83adb89ee8Andy Huang        @Override
748af5d4e0e2a5ce3300fffa0c484431d83adb89ee8Andy Huang        public boolean shouldOverrideUrlLoading(WebView view, String url) {
749542fec98d011c78782b63b33d29cf81044e96f75Paul Westbrook            return mViewsCreated && super.shouldOverrideUrlLoading(view, url);
750af5d4e0e2a5ce3300fffa0c484431d83adb89ee8Andy Huang        }
75117a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huang    }
75217a9cde3b0e28fc98fdeda19de81e18056eb09dbAndy Huang
753f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    /**
7543bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp     * Notifies the {@link ConversationViewable.ConversationCallbacks} that the conversation has
7553bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp     * been loaded.
7563bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp     */
7573bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp    public void notifyConversationLoaded(Conversation c) {
758dde3f9ff76c1a7f9a6e9fa959a6422a8a8d82e81mindyp        if (mWebViewSizeChangeListener == null) {
759dde3f9ff76c1a7f9a6e9fa959a6422a8a8d82e81mindyp            mWebViewSizeChangeListener = new ConversationWebView.ContentSizeChangeListener() {
760dde3f9ff76c1a7f9a6e9fa959a6422a8a8d82e81mindyp                @Override
761dde3f9ff76c1a7f9a6e9fa959a6422a8a8d82e81mindyp                public void onHeightChange(int h) {
762dde3f9ff76c1a7f9a6e9fa959a6422a8a8d82e81mindyp                    // When WebKit says the DOM height has changed, re-measure
763dde3f9ff76c1a7f9a6e9fa959a6422a8a8d82e81mindyp                    // bodies and re-position their headers.
764dde3f9ff76c1a7f9a6e9fa959a6422a8a8d82e81mindyp                    // This is separate from the typical JavaScript DOM change
765dde3f9ff76c1a7f9a6e9fa959a6422a8a8d82e81mindyp                    // listeners because cases like NARROW_COLUMNS text reflow do not trigger DOM
766dde3f9ff76c1a7f9a6e9fa959a6422a8a8d82e81mindyp                    // events.
767dde3f9ff76c1a7f9a6e9fa959a6422a8a8d82e81mindyp                    mWebView.loadUrl("javascript:measurePositions();");
768dde3f9ff76c1a7f9a6e9fa959a6422a8a8d82e81mindyp                }
769dde3f9ff76c1a7f9a6e9fa959a6422a8a8d82e81mindyp            };
770dde3f9ff76c1a7f9a6e9fa959a6422a8a8d82e81mindyp        }
771dde3f9ff76c1a7f9a6e9fa959a6422a8a8d82e81mindyp        mWebView.setContentSizeChangeListener(mWebViewSizeChangeListener);
7723bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp    }
7733bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp
7743bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp    /**
7753bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp     * Notifies the {@link ConversationViewable.ConversationCallbacks} that the conversation has
7763bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp     * failed to load.
7773bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp     */
7783bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp    protected void notifyConversationLoadError(Conversation c) {
7793bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp        mActivity.onConversationLoadError();
7803bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp    }
7813bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp
7823bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp    /**
783f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang     * NOTE: all public methods must be listed in the proguard flags so that they can be accessed
784f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang     * via reflection and not stripped.
785f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang     *
786f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang     */
787f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    private class MailJsBridge {
788f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
789f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        @SuppressWarnings("unused")
790974c966c14000d42f089e2021b098d5cf61f9245Mindy Pereira        @JavascriptInterface
7917bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        public void onWebContentGeometryChange(final String[] overlayBottomStrs) {
79246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            try {
793ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp                getHandler().post(new Runnable() {
79446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                    @Override
79546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                    public void run() {
79646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                        if (!mViewsCreated) {
79746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                            LogUtils.d(LOG_TAG, "ignoring webContentGeometryChange because views" +
79846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                                    " are gone, %s", ConversationViewFragment.this);
79946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                            return;
80046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                        }
80146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
80246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                        mConversationContainer.onGeometryChange(parseInts(overlayBottomStrs));
80351067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang                    }
80446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                });
80546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            } catch (Throwable t) {
80646dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                LogUtils.e(LOG_TAG, t, "Error in MailJsBridge.onWebContentGeometryChange");
80746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            }
80846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        }
80951067133f35911bf4b43f97be52b00e5bd9f07abAndy Huang
81046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        @SuppressWarnings("unused")
811974c966c14000d42f089e2021b098d5cf61f9245Mindy Pereira        @JavascriptInterface
81246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang        public String getTempMessageBodies() {
81346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            try {
81446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                if (!mViewsCreated) {
81546dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                    return "";
816f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang                }
81746dfba6160b55a582b344328067e3dafeb881dd9Andy Huang
81846dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                final String s = mTempBodiesHtml;
81946dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                mTempBodiesHtml = null;
82046dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                return s;
82146dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            } catch (Throwable t) {
82246dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                LogUtils.e(LOG_TAG, t, "Error in MailJsBridge.getTempMessageBodies");
82346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang                return "";
82446dfba6160b55a582b344328067e3dafeb881dd9Andy Huang            }
825f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        }
826f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
8273bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp        private void showConversation(Conversation conv) {
8283bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp            notifyConversationLoaded(conv);
8293bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp            dismissLoadingStatus();
8303bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp        }
8313bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp
8323bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp        @SuppressWarnings("unused")
833974c966c14000d42f089e2021b098d5cf61f9245Mindy Pereira        @JavascriptInterface
8343bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp        public void onContentReady() {
8353bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp            final Conversation conv = mConversation;
8363bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp            try {
837ff282d0ef252dbdaf6e9f4e2a7fd640287c01e6bmindyp                getHandler().post(new Runnable() {
8383bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp                    @Override
8393bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp                    public void run() {
8403bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp                        LogUtils.d(LOG_TAG, "ANIMATION STARTED, ready to draw. t=%s",
8413bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp                                SystemClock.uptimeMillis());
8423bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp                        showConversation(conv);
8433bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp                    }
8443bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp                });
8453bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp            } catch (Throwable t) {
8463bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp                LogUtils.e(LOG_TAG, t, "Error in MailJsBridge.onContentReady");
8473bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp                // Still try to show the conversation.
8483bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp                showConversation(conv);
8493bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp            }
8503bcf180f8104bc27319086a9a6ece5a3c2917c37mindyp        }
851674afa42682908640167fc6109b76f6f843e6fbeMindy Pereira    }
852f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
85347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    private class NewMessagesInfo {
85447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        int count;
85547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        String senderAddress;
85647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
85747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        /**
85847aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang         * Return the display text for the new message notification overlay. It will be formatted
85947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang         * appropriately for a single new message vs. multiple new messages.
86047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang         *
86147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang         * @return display text
86247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang         */
86347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        public String getNotificationText() {
86447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang            final Object param;
86547aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang            if (count > 1) {
86647aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang                param = count;
86747aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang            } else {
8681617481de7c1f9b8dfcd25ba9828e933cf9d5490Andy Huang                final Address addr = getAddress(senderAddress);
86947aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang                param = TextUtils.isEmpty(addr.getName()) ? addr.getAddress() : addr.getName();
87047aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang            }
87147aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang            return getResources().getQuantityString(R.plurals.new_incoming_messages, count, param);
87247aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang        }
87347aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang    }
87447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
875f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    @Override
876f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    public void onMessageCursorLoadFinished(Loader<Cursor> loader, Cursor data, boolean wasNull,
877f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            boolean changed) {
878f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        MessageCursor messageCursor = (MessageCursor) data;
879f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        /*
880f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp         * what kind of changes affect the MessageCursor? 1. new message(s) 2.
881f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp         * read/unread state change 3. deleted message, either regular or draft
882f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp         * 4. updated message, either from self or from others, updated in
883f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp         * content or state or sender 5. star/unstar of message (technically
884f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp         * similar to #1) 6. other label change Use MessageCursor.hashCode() to
885f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp         * sort out interesting vs. no-op cursor updates.
886f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp         */
887f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        if (!wasNull) {
888f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            final NewMessagesInfo info = getNewIncomingMessagesInfo(messageCursor);
889f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
890f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            if (info.count > 0 || !changed) {
891f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
892f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                if (info.count > 0) {
893f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    // don't immediately render new incoming messages from other
894f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    // senders
895f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    // (to avoid a new message from losing the user's focus)
896f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    LogUtils.i(LOG_TAG, "CONV RENDER: conversation updated"
897f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                            + ", holding cursor for new incoming message");
898f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    showNewMessageNotification(info);
8991ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang                } else {
900f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    LogUtils.i(LOG_TAG, "CONV RENDER: uninteresting update"
901f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                            + ", ignoring this conversation update");
9021ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang                }
9031ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang
904f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                // update mCursor reference because the old one is about to be
905f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                // closed by CursorLoader
9061ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang                return;
9071ee96b2b100546b5b69ad42c5bc3755a4293d1a3Andy Huang            }
908b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang        }
909b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang
910f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        // cursors are different, and not due to an incoming message. fall
911f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        // through and render.
912f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        LogUtils.i(LOG_TAG, "CONV RENDER: conversation updated"
913f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                + ", but not due to incoming message. rendering.");
91447aa9c991b33c722a6ba1946fc02e0aba17bc1c9Andy Huang
915f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        // TODO: if this is not user-visible, delay render until user-visible
916f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        // fragment is done. This is needed in addition to the
917f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        // showConversation() delay to speed up rotation and restoration.
918f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        renderConversation(messageCursor);
919b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang    }
920b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang
921f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp    private NewMessagesInfo getNewIncomingMessagesInfo(MessageCursor newCursor) {
922f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        final NewMessagesInfo info = new NewMessagesInfo();
923b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang
924f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        int pos = -1;
925f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        while (newCursor.moveToPosition(++pos)) {
926f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            final Message m = newCursor.getMessage();
927f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp            if (!mViewState.contains(m)) {
928f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                LogUtils.i(LOG_TAG, "conversation diff: found new msg: %s", m.uri);
929f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp
930f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                final Address from = getAddress(m.from);
931f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                // distinguish ours from theirs
932f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                // new messages from the account owner should not trigger a
933f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                // notification
934f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                if (mAccount.ownsFromAddress(from.getAddress())) {
935f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    LogUtils.i(LOG_TAG, "found message from self: %s", m.uri);
936f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                    continue;
937f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                }
938b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang
939f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                info.count++;
940f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                info.senderAddress = m.from;
941b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang            }
942b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang        }
943f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp        return info;
944b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang    }
945b8331b4565566ca733997398e8c07a26cd2bee98Andy Huang
946cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook    private class SetCookieTask extends AsyncTask<Void, Void, Void> {
947cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook        final String mUri;
948cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook        final String mCookie;
949cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook
950cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook        SetCookieTask(String uri, String cookie) {
951cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook            mUri = uri;
952cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook            mCookie = cookie;
953cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook        }
954cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook
955cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook        @Override
956cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook        public Void doInBackground(Void... args) {
957cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook            final CookieSyncManager csm =
958f4fce1227d8b49f45e6569f1590565f2df9e8d6emindyp                CookieSyncManager.createInstance(getContext());
959cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook            CookieManager.getInstance().setCookie(mUri, mCookie);
960cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook            csm.sync();
961cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook            return null;
962cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook        }
963cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook    }
96436280f3c2939bb2dacb4077bd528900346ff4bb9mindyp
96526d4d2d9c43c499f458808f050ec73ea3c28dec4mindyp    @Override
96636280f3c2939bb2dacb4077bd528900346ff4bb9mindyp    public void onConversationUpdated(Conversation conv) {
96736280f3c2939bb2dacb4077bd528900346ff4bb9mindyp        final ConversationViewHeader headerView = (ConversationViewHeader) mConversationContainer
96836280f3c2939bb2dacb4077bd528900346ff4bb9mindyp                .findViewById(R.id.conversation_header);
969b2b98ba97983f225eec19dc9bc5333e6ef80ee15mindyp        mConversation = conv;
9709e0b236be55cf9dab8f1670a9d91ed16e055a9d0mindyp        if (headerView != null) {
9719e0b236be55cf9dab8f1670a9d91ed16e055a9d0mindyp            headerView.onConversationUpdated(conv);
9729e0b236be55cf9dab8f1670a9d91ed16e055a9d0mindyp        }
97336280f3c2939bb2dacb4077bd528900346ff4bb9mindyp    }
9749b87568c9e9f1c32a9672b315229866a58a1e757Mindy Pereira}
975