blob: 233ea9aa1700f9fd00a0f7ff44aaeb968cd7c4b8 [file] [log] [blame]
Mindy Pereira9b875682012-02-15 18:10:54 -08001/*
2 * Copyright (C) 2012 Google Inc.
3 * Licensed to The Android Open Source Project.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18package com.android.mail.ui;
19
Paul Westbrookb8361c92012-09-27 10:57:14 -070020import android.content.ContentResolver;
Mindy Pereira9b875682012-02-15 18:10:54 -080021import android.content.Context;
Mindy Pereira8e915722012-02-16 14:42:56 -080022import android.content.Loader;
mindypad0c30d2012-09-25 12:09:13 -070023import android.content.res.Resources;
Mindy Pereira9b875682012-02-15 18:10:54 -080024import android.database.Cursor;
Andy Huang9d3fd922012-09-26 22:23:58 -070025import android.database.DataSetObserver;
Paul Westbrookb8361c92012-09-27 10:57:14 -070026import android.net.Uri;
Paul Westbrookcebcc642012-08-08 10:06:04 -070027import android.os.AsyncTask;
Mindy Pereira9b875682012-02-15 18:10:54 -080028import android.os.Bundle;
mindyp3bcf1802012-09-09 11:17:00 -070029import android.os.SystemClock;
Andrew Sapperstein3af481c2013-10-30 10:29:38 -070030import android.support.v4.text.BidiFormatter;
Andy Huang47aa9c92012-07-31 15:37:21 -070031import android.text.TextUtils;
Mindy Pereira9b875682012-02-15 18:10:54 -080032import android.view.LayoutInflater;
Andy Huang02f9d182012-11-28 22:38:02 -080033import android.view.ScaleGestureDetector;
Andy Huang02f9d182012-11-28 22:38:02 -080034import android.view.ScaleGestureDetector.OnScaleGestureListener;
Andy Huangc1fb9a92013-02-11 13:09:12 -080035import android.view.View;
Mark Wei4071c2f2012-09-26 14:38:38 -070036import android.view.View.OnLayoutChangeListener;
Mindy Pereira9b875682012-02-15 18:10:54 -080037import android.view.ViewGroup;
Andy Huangf70fc402012-02-17 15:37:42 -080038import android.webkit.ConsoleMessage;
Paul Westbrookcebcc642012-08-08 10:06:04 -070039import android.webkit.CookieManager;
40import android.webkit.CookieSyncManager;
Mindy Pereira974c9662012-09-14 10:02:08 -070041import android.webkit.JavascriptInterface;
Andy Huangf70fc402012-02-17 15:37:42 -080042import android.webkit.WebChromeClient;
43import android.webkit.WebSettings;
Andy Huang17a9cde2012-03-09 18:03:16 -080044import android.webkit.WebView;
Andrew Sapperstein821fa872013-08-21 21:57:39 -070045import android.widget.Button;
Mindy Pereira9b875682012-02-15 18:10:54 -080046
Andy Huang59e0b182012-08-14 14:32:23 -070047import com.android.mail.FormattedDateBuilder;
Mindy Pereira9b875682012-02-15 18:10:54 -080048import com.android.mail.R;
Andy Huange6c9fb62013-11-15 09:56:20 -080049import com.android.mail.analytics.Analytics;
Andy Huang5ff63742012-03-16 20:30:23 -070050import com.android.mail.browse.ConversationContainer;
Andy Huangadbf3e82012-10-13 13:30:19 -070051import com.android.mail.browse.ConversationContainer.OverlayPosition;
Andrew Sapperstein8812d3c2013-06-04 17:06:41 -070052import com.android.mail.browse.ConversationMessage;
Andy Huang46dfba62012-04-19 01:47:32 -070053import com.android.mail.browse.ConversationOverlayItem;
Andy Huang7bdc3752012-03-25 17:18:19 -070054import com.android.mail.browse.ConversationViewAdapter;
Andrew Sapperstein14f93742013-07-25 14:29:56 -070055import com.android.mail.browse.ConversationViewAdapter.BorderItem;
Andy Huang46dfba62012-04-19 01:47:32 -070056import com.android.mail.browse.ConversationViewAdapter.MessageFooterItem;
Andy Huang7bdc3752012-03-25 17:18:19 -070057import com.android.mail.browse.ConversationViewAdapter.MessageHeaderItem;
Andy Huang46dfba62012-04-19 01:47:32 -070058import com.android.mail.browse.ConversationViewAdapter.SuperCollapsedBlockItem;
Andy Huang5ff63742012-03-16 20:30:23 -070059import com.android.mail.browse.ConversationViewHeader;
60import com.android.mail.browse.ConversationWebView;
Andy Huangc1fb9a92013-02-11 13:09:12 -080061import com.android.mail.browse.MailWebView.ContentSizeChangeListener;
Andy Huang7bdc3752012-03-25 17:18:19 -070062import com.android.mail.browse.MessageCursor;
Andy Huang59e0b182012-08-14 14:32:23 -070063import com.android.mail.browse.MessageHeaderView;
Andy Huangadbf3e82012-10-13 13:30:19 -070064import com.android.mail.browse.ScrollIndicatorsView;
Andy Huang46dfba62012-04-19 01:47:32 -070065import com.android.mail.browse.SuperCollapsedBlock;
Andy Huang0b7ed6f2012-07-25 19:23:26 -070066import com.android.mail.browse.WebViewContextMenu;
Paul Westbrookc42ad5e2013-05-09 16:52:15 -070067import com.android.mail.content.ObjectCursor;
Andrew Sappersteine8221482013-10-02 18:14:58 -070068import com.android.mail.preferences.AccountPreferences;
Andrew Sapperstein562c5ba2013-10-09 18:31:50 -070069import com.android.mail.print.PrintUtils;
Mindy Pereira9b875682012-02-15 18:10:54 -080070import com.android.mail.providers.Account;
Andy Huang65fe28f2012-04-06 18:08:53 -070071import com.android.mail.providers.Address;
Mindy Pereira9b875682012-02-15 18:10:54 -080072import com.android.mail.providers.Conversation;
Andy Huangf70fc402012-02-17 15:37:42 -080073import com.android.mail.providers.Message;
Alice Yangf323c042013-10-30 00:15:02 -070074import com.android.mail.providers.Settings;
Paul Westbrookb8361c92012-09-27 10:57:14 -070075import com.android.mail.providers.UIProvider;
Andy Huangcd5c5ee2012-08-12 19:03:51 -070076import com.android.mail.ui.ConversationViewState.ExpansionState;
Andrew Sapperstein376294b2013-06-06 16:04:26 -070077import com.android.mail.utils.ConversationViewUtils;
Paul Westbrookb334c902012-06-25 11:42:46 -070078import com.android.mail.utils.LogTag;
Mindy Pereira9b875682012-02-15 18:10:54 -080079import com.android.mail.utils.LogUtils;
Andy Huang2e9acfe2012-03-15 22:39:36 -070080import com.android.mail.utils.Utils;
Andy Huang543e7092013-04-22 11:44:56 -070081import com.google.common.collect.ImmutableList;
Andy Huang46dfba62012-04-19 01:47:32 -070082import com.google.common.collect.Lists;
Andy Huang05c70c82013-03-14 15:15:50 -070083import com.google.common.collect.Maps;
Andy Huangb8331b42012-07-16 19:08:53 -070084import com.google.common.collect.Sets;
Andy Huang65fe28f2012-04-06 18:08:53 -070085
Scott Kennedyeb9a4bd2012-11-12 10:33:04 -080086import java.util.ArrayList;
Andy Huang46dfba62012-04-19 01:47:32 -070087import java.util.List;
Andy Huang05c70c82013-03-14 15:15:50 -070088import java.util.Map;
Andy Huangb8331b42012-07-16 19:08:53 -070089import java.util.Set;
Mindy Pereira9b875682012-02-15 18:10:54 -080090
91/**
92 * The conversation view UI component.
93 */
Andrew Sapperstein9f957f32013-07-19 15:18:18 -070094public class ConversationViewFragment extends AbstractConversationViewFragment implements
Andrew Sapperstein4ddda2f2013-06-10 11:15:38 -070095 SuperCollapsedBlock.OnClickListener, OnLayoutChangeListener,
96 MessageHeaderView.MessageHeaderViewCallbacks {
Mindy Pereira8e915722012-02-16 14:42:56 -080097
Paul Westbrookb334c902012-06-25 11:42:46 -070098 private static final String LOG_TAG = LogTag.getLogTag();
Andy Huang632721e2012-04-11 16:57:26 -070099 public static final String LAYOUT_TAG = "ConvLayout";
Mindy Pereira9b875682012-02-15 18:10:54 -0800100
Andy Huang3c276bf2013-02-19 14:24:43 -0800101 private static final boolean ENABLE_CSS_ZOOM = false;
102
Andy Huang9d3fd922012-09-26 22:23:58 -0700103 /**
mindyp1b3cc472012-09-27 11:32:59 -0700104 * Difference in the height of the message header whose details have been expanded/collapsed
105 */
106 private int mDiff = 0;
107
108 /**
Andy Huang9d3fd922012-09-26 22:23:58 -0700109 * Default value for {@link #mLoadWaitReason}. Conversation load will happen immediately.
110 */
111 private final int LOAD_NOW = 0;
112 /**
113 * Value for {@link #mLoadWaitReason} that means we are offscreen and waiting for the visible
114 * conversation to finish loading before beginning our load.
115 * <p>
116 * When this value is set, the fragment should register with {@link ConversationListCallbacks}
117 * to know when the visible conversation is loaded. When it is unset, it should unregister.
118 */
119 private final int LOAD_WAIT_FOR_INITIAL_CONVERSATION = 1;
120 /**
121 * Value for {@link #mLoadWaitReason} used when a conversation is too heavyweight to load at
122 * all when not visible (e.g. requires network fetch, or too complex). Conversation load will
123 * wait until this fragment is visible.
124 */
125 private final int LOAD_WAIT_UNTIL_VISIBLE = 2;
mindyp3bcf1802012-09-09 11:17:00 -0700126
Andrew Sapperstein9f957f32013-07-19 15:18:18 -0700127 protected ConversationContainer mConversationContainer;
Mindy Pereira9b875682012-02-15 18:10:54 -0800128
Andrew Sapperstein9f957f32013-07-19 15:18:18 -0700129 protected ConversationWebView mWebView;
Mindy Pereira9b875682012-02-15 18:10:54 -0800130
Mark Wei56d83852012-09-19 14:28:50 -0700131 private ScrollIndicatorsView mScrollIndicators;
132
Andrew Sapperstein376294b2013-06-06 16:04:26 -0700133 private ConversationViewProgressController mProgressController;
134
Andrew Sapperstein821fa872013-08-21 21:57:39 -0700135 private Button mNewMessageBar;
Andy Huang47aa9c92012-07-31 15:37:21 -0700136
Andrew Sapperstein9f957f32013-07-19 15:18:18 -0700137 protected HtmlConversationTemplates mTemplates;
Andy Huangf70fc402012-02-17 15:37:42 -0800138
Andy Huangf70fc402012-02-17 15:37:42 -0800139 private final MailJsBridge mJsBridge = new MailJsBridge();
140
Andrew Sapperstein9f957f32013-07-19 15:18:18 -0700141 protected ConversationViewAdapter mAdapter;
Andy Huang51067132012-03-12 20:08:19 -0700142
Andrew Sappersteinb1d184d2013-08-09 14:14:31 -0700143 protected boolean mViewsCreated;
Mark Wei4071c2f2012-09-26 14:38:38 -0700144 // True if we attempted to render before the views were laid out
145 // We will render immediately once layout is done
146 private boolean mNeedRender;
Andy Huang51067132012-03-12 20:08:19 -0700147
Andy Huang46dfba62012-04-19 01:47:32 -0700148 /**
149 * Temporary string containing the message bodies of the messages within a super-collapsed
150 * block, for one-time use during block expansion. We cannot easily pass the body HTML
151 * into JS without problematic escaping, so hold onto it momentarily and signal JS to fetch it
152 * using {@link MailJsBridge}.
153 */
154 private String mTempBodiesHtml;
155
Andy Huang632721e2012-04-11 16:57:26 -0700156 private int mMaxAutoLoadMessages;
157
Andrew Sapperstein9f957f32013-07-19 15:18:18 -0700158 protected int mSideMarginPx;
Andy Huang02f9d182012-11-28 22:38:02 -0800159
Andy Huang9d3fd922012-09-26 22:23:58 -0700160 /**
161 * If this conversation fragment is not visible, and it's inappropriate to load up front,
162 * this is the reason we are waiting. This flag should be cleared once it's okay to load
163 * the conversation.
164 */
165 private int mLoadWaitReason = LOAD_NOW;
Andy Huang632721e2012-04-11 16:57:26 -0700166
mindyp3bcf1802012-09-09 11:17:00 -0700167 private boolean mEnableContentReadySignal;
Andy Huang28b7aee2012-08-20 20:27:32 -0700168
mindypdde3f9f2012-09-10 17:35:35 -0700169 private ContentSizeChangeListener mWebViewSizeChangeListener;
170
Andy Huange964eee2012-10-02 19:24:58 -0700171 private float mWebViewYPercent;
172
173 /**
174 * Has loadData been called on the WebView yet?
175 */
176 private boolean mWebViewLoadedData;
177
Andy Huang63b3c672012-10-05 19:27:28 -0700178 private long mWebViewLoadStartMs;
179
Andy Huang05c70c82013-03-14 15:15:50 -0700180 private final Map<String, String> mMessageTransforms = Maps.newHashMap();
181
Andy Huang9d3fd922012-09-26 22:23:58 -0700182 private final DataSetObserver mLoadedObserver = new DataSetObserver() {
183 @Override
184 public void onChanged() {
Andrew Sapperstein376294b2013-06-06 16:04:26 -0700185 getHandler().post(new FragmentRunnable("delayedConversationLoad",
186 ConversationViewFragment.this) {
Andy Huang9d3fd922012-09-26 22:23:58 -0700187 @Override
188 public void go() {
189 LogUtils.d(LOG_TAG, "CVF load observer fired, this=%s",
190 ConversationViewFragment.this);
191 handleDelayedConversationLoad();
192 }
193 });
194 }
195 };
Andy Huangf70fc402012-02-17 15:37:42 -0800196
Andrew Sapperstein376294b2013-06-06 16:04:26 -0700197 private final Runnable mOnProgressDismiss = new FragmentRunnable("onProgressDismiss", this) {
Andy Huang7d4746e2012-10-17 17:03:17 -0700198 @Override
199 public void go() {
Scott Kennedy58192e52013-05-08 16:35:57 -0700200 LogUtils.d(LOG_TAG, "onProgressDismiss go() - isUserVisible() = %b", isUserVisible());
Andy Huang7d4746e2012-10-17 17:03:17 -0700201 if (isUserVisible()) {
202 onConversationSeen();
203 }
Andy Huang30bcfe72012-10-18 18:09:03 -0700204 mWebView.onRenderComplete();
Andy Huang7d4746e2012-10-17 17:03:17 -0700205 }
206 };
207
Andy Huangbd544e32012-05-29 15:56:51 -0700208 private static final boolean DEBUG_DUMP_CONVERSATION_HTML = false;
Andy Huang47aa9c92012-07-31 15:37:21 -0700209 private static final boolean DISABLE_OFFSCREEN_LOADING = false;
Andy Huang06c03622012-10-22 18:59:45 -0700210 private static final boolean DEBUG_DUMP_CURSOR_CONTENTS = false;
Andy Huange964eee2012-10-02 19:24:58 -0700211
212 private static final String BUNDLE_KEY_WEBVIEW_Y_PERCENT =
213 ConversationViewFragment.class.getName() + "webview-y-percent";
Andy Huangbd544e32012-05-29 15:56:51 -0700214
Andrew Sapperstein3af481c2013-10-30 10:29:38 -0700215 private BidiFormatter sBidiFormatter;
216
Vikram Aggarwal6c511582012-02-27 10:59:47 -0800217 /**
218 * Constructor needs to be public to handle orientation changes and activity lifecycle events.
219 */
Paul Westbrookf0ea4842013-08-13 16:41:18 -0700220 public ConversationViewFragment() {}
Mindy Pereira9b875682012-02-15 18:10:54 -0800221
222 /**
223 * Creates a new instance of {@link ConversationViewFragment}, initialized
Andy Huang632721e2012-04-11 16:57:26 -0700224 * to display a conversation with other parameters inherited/copied from an existing bundle,
225 * typically one created using {@link #makeBasicArgs}.
226 */
227 public static ConversationViewFragment newInstance(Bundle existingArgs,
228 Conversation conversation) {
229 ConversationViewFragment f = new ConversationViewFragment();
230 Bundle args = new Bundle(existingArgs);
231 args.putParcelable(ARG_CONVERSATION, conversation);
232 f.setArguments(args);
233 return f;
234 }
235
mindypf4fce122012-09-14 15:55:33 -0700236 @Override
Andy Huangadbf3e82012-10-13 13:30:19 -0700237 public void onAccountChanged(Account newAccount, Account oldAccount) {
238 // if overview mode has changed, re-render completely (no need to also update headers)
239 if (isOverviewMode(newAccount) != isOverviewMode(oldAccount)) {
240 setupOverviewMode();
241 final MessageCursor c = getMessageCursor();
242 if (c != null) {
243 renderConversation(c);
244 } else {
245 // Null cursor means this fragment is either waiting to load or in the middle of
246 // loading. Either way, a future render will happen anyway, and the new setting
247 // will take effect when that happens.
248 }
249 return;
250 }
251
mindypf4fce122012-09-14 15:55:33 -0700252 // settings may have been updated; refresh views that are known to
253 // depend on settings
mindypf4fce122012-09-14 15:55:33 -0700254 mAdapter.notifyDataSetChanged();
Andy Huang632721e2012-04-11 16:57:26 -0700255 }
256
Mindy Pereira9b875682012-02-15 18:10:54 -0800257 @Override
258 public void onActivityCreated(Bundle savedInstanceState) {
Andy Huang9d3fd922012-09-26 22:23:58 -0700259 LogUtils.d(LOG_TAG, "IN CVF.onActivityCreated, this=%s visible=%s", this, isUserVisible());
Mindy Pereira9b875682012-02-15 18:10:54 -0800260 super.onActivityCreated(savedInstanceState);
Mark Wei1abfcaf2012-09-27 11:11:07 -0700261
262 if (mActivity == null || mActivity.isFinishing()) {
263 // Activity is finishing, just bail.
264 return;
265 }
266
mindypf4fce122012-09-14 15:55:33 -0700267 Context context = getContext();
268 mTemplates = new HtmlConversationTemplates(context);
Andy Huang59e0b182012-08-14 14:32:23 -0700269
mindypf4fce122012-09-14 15:55:33 -0700270 final FormattedDateBuilder dateBuilder = new FormattedDateBuilder(context);
Andy Huang59e0b182012-08-14 14:32:23 -0700271
Paul Westbrook8081df42012-09-10 15:43:36 -0700272 mAdapter = new ConversationViewAdapter(mActivity, this,
mindypf4fce122012-09-14 15:55:33 -0700273 getLoaderManager(), this, getContactInfoSource(), this,
Paul Westbrook8081df42012-09-10 15:43:36 -0700274 this, mAddressCache, dateBuilder);
Andy Huang51067132012-03-12 20:08:19 -0700275 mConversationContainer.setOverlayAdapter(mAdapter);
276
Andy Huang59e0b182012-08-14 14:32:23 -0700277 // set up snap header (the adapter usually does this with the other ones)
Andrew Sapperstein85ea6182013-10-14 18:21:08 -0700278 mConversationContainer.getSnapHeader().initialize(
279 this, mAddressCache, this, getContactInfoSource(),
280 mActivity.getAccountController().getVeiledAddressMatcher());
Andy Huangc1fb9a92013-02-11 13:09:12 -0800281
Andrew Sapperstein90eccf92013-08-22 11:53:23 -0700282 final Resources resources = getResources();
283 mMaxAutoLoadMessages = resources.getInteger(R.integer.max_auto_load_messages);
Andy Huang632721e2012-04-11 16:57:26 -0700284
Andrew Sapperstein90eccf92013-08-22 11:53:23 -0700285 mSideMarginPx = resources.getDimensionPixelOffset(
Andy Huang02f9d182012-11-28 22:38:02 -0800286 R.dimen.conversation_message_content_margin_side);
287
mindypf4fce122012-09-14 15:55:33 -0700288 mWebView.setOnCreateContextMenuListener(new WebViewContextMenu(getActivity()));
Andy Huang0b7ed6f2012-07-25 19:23:26 -0700289
Andy Huangadbf3e82012-10-13 13:30:19 -0700290 // set this up here instead of onCreateView to ensure the latest Account is loaded
291 setupOverviewMode();
292
Andy Huang9d3fd922012-09-26 22:23:58 -0700293 // Defer the call to initLoader with a Handler.
294 // We want to wait until we know which fragments are present and their final visibility
295 // states before going off and doing work. This prevents extraneous loading from occurring
296 // as the ViewPager shifts about before the initial position is set.
297 //
298 // e.g. click on item #10
299 // ViewPager.setAdapter() actually first loads #0 and #1 under the assumption that #0 is
300 // the initial primary item
301 // Then CPC immediately sets the primary item to #10, which tears down #0/#1 and sets up
302 // #9/#10/#11.
Andrew Sapperstein376294b2013-06-06 16:04:26 -0700303 getHandler().post(new FragmentRunnable("showConversation", this) {
Andy Huang9d3fd922012-09-26 22:23:58 -0700304 @Override
305 public void go() {
306 showConversation();
307 }
308 });
Paul Westbrookcebcc642012-08-08 10:06:04 -0700309
Andrew Sapperstein606dbd72013-07-30 19:14:23 -0700310 if (mConversation != null && mConversation.conversationBaseUri != null &&
Paul Westbrookb8361c92012-09-27 10:57:14 -0700311 !Utils.isEmpty(mAccount.accoutCookieQueryUri)) {
Paul Westbrookcebcc642012-08-08 10:06:04 -0700312 // Set the cookie for this base url
Paul Westbrookb8361c92012-09-27 10:57:14 -0700313 new SetCookieTask(getContext(), mConversation.conversationBaseUri,
314 mAccount.accoutCookieQueryUri).execute();
Paul Westbrookcebcc642012-08-08 10:06:04 -0700315 }
Mindy Pereira9b875682012-02-15 18:10:54 -0800316 }
317
Mindy Pereira9b875682012-02-15 18:10:54 -0800318 @Override
Andy Huange964eee2012-10-02 19:24:58 -0700319 public void onCreate(Bundle savedState) {
320 super.onCreate(savedState);
321
Andrew Sappersteinb1d184d2013-08-09 14:14:31 -0700322 mWebViewClient = createConversationWebViewClient();
Andrew Sapperstein376294b2013-06-06 16:04:26 -0700323
Andy Huange964eee2012-10-02 19:24:58 -0700324 if (savedState != null) {
325 mWebViewYPercent = savedState.getFloat(BUNDLE_KEY_WEBVIEW_Y_PERCENT);
326 }
Andrew Sapperstein3af481c2013-10-30 10:29:38 -0700327
328 if (sBidiFormatter == null) {
329 sBidiFormatter = BidiFormatter.getInstance();
330 }
Andy Huange964eee2012-10-02 19:24:58 -0700331 }
332
Andrew Sappersteinb1d184d2013-08-09 14:14:31 -0700333 protected ConversationWebViewClient createConversationWebViewClient() {
334 return new ConversationWebViewClient(mAccount);
335 }
336
Andy Huange964eee2012-10-02 19:24:58 -0700337 @Override
Mindy Pereira9b875682012-02-15 18:10:54 -0800338 public View onCreateView(LayoutInflater inflater,
339 ViewGroup container, Bundle savedInstanceState) {
Andy Huang839ada22012-07-20 15:48:40 -0700340
Andy Huang632721e2012-04-11 16:57:26 -0700341 View rootView = inflater.inflate(R.layout.conversation_view, container, false);
Andy Huangf70fc402012-02-17 15:37:42 -0800342 mConversationContainer = (ConversationContainer) rootView
343 .findViewById(R.id.conversation_container);
Andy Huang8f187782012-11-06 17:49:25 -0800344 mConversationContainer.setAccountController(this);
Andy Huang47aa9c92012-07-31 15:37:21 -0700345
Andrew Sapperstein85ea6182013-10-14 18:21:08 -0700346 final ViewGroup topmostOverlay =
347 (ViewGroup) mConversationContainer.findViewById(R.id.conversation_topmost_overlay);
348 inflateSnapHeader(topmostOverlay, inflater);
349 mConversationContainer.setupSnapHeader();
350
351 setupNewMessageBar();
Andy Huang47aa9c92012-07-31 15:37:21 -0700352
Andrew Sapperstein376294b2013-06-06 16:04:26 -0700353 mProgressController = new ConversationViewProgressController(this, getHandler());
354 mProgressController.instantiateProgressIndicators(rootView);
mindyp3bcf1802012-09-09 11:17:00 -0700355
Andy Huang5ff63742012-03-16 20:30:23 -0700356 mWebView = (ConversationWebView) mConversationContainer.findViewById(R.id.webview);
Andy Huangf70fc402012-02-17 15:37:42 -0800357
Andy Huangf70fc402012-02-17 15:37:42 -0800358 mWebView.addJavascriptInterface(mJsBridge, "mail");
mindyp3bcf1802012-09-09 11:17:00 -0700359 // On JB or newer, we use the 'webkitAnimationStart' DOM event to signal load complete
360 // Below JB, try to speed up initial render by having the webview do supplemental draws to
361 // custom a software canvas.
mindypb941fdb2012-09-11 08:28:23 -0700362 // TODO(mindyp):
363 //PAGE READINESS SIGNAL FOR JELLYBEAN AND NEWER
364 // Notify the app on 'webkitAnimationStart' of a simple dummy element with a simple no-op
365 // animation that immediately runs on page load. The app uses this as a signal that the
366 // content is loaded and ready to draw, since WebView delays firing this event until the
367 // layers are composited and everything is ready to draw.
368 // This signal does not seem to be reliable, so just use the old method for now.
Andy Huangf7ac83f2013-07-15 15:48:31 -0700369 final boolean isJBOrLater = Utils.isRunningJellybeanOrLater();
370 final boolean isUserVisible = isUserVisible();
371 mWebView.setUseSoftwareLayer(!isJBOrLater);
372 mEnableContentReadySignal = isJBOrLater && isUserVisible;
373 mWebView.onUserVisibilityChanged(isUserVisible);
Andy Huang17a9cde2012-03-09 18:03:16 -0800374 mWebView.setWebViewClient(mWebViewClient);
Andy Huangc1fb9a92013-02-11 13:09:12 -0800375 final WebChromeClient wcc = new WebChromeClient() {
Andy Huangf70fc402012-02-17 15:37:42 -0800376 @Override
377 public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
Andy Huang5ea5a832013-03-07 16:49:09 -0800378 LogUtils.i(LOG_TAG, "JS: %s (%s:%d) f=%s", consoleMessage.message(),
379 consoleMessage.sourceId(), consoleMessage.lineNumber(),
380 ConversationViewFragment.this);
Andy Huangf70fc402012-02-17 15:37:42 -0800381 return true;
382 }
Andy Huangc1fb9a92013-02-11 13:09:12 -0800383 };
384 mWebView.setWebChromeClient(wcc);
Andy Huangf70fc402012-02-17 15:37:42 -0800385
Andy Huang3233bff2012-03-20 19:38:45 -0700386 final WebSettings settings = mWebView.getSettings();
Andy Huangf70fc402012-02-17 15:37:42 -0800387
Mark Wei56d83852012-09-19 14:28:50 -0700388 mScrollIndicators = (ScrollIndicatorsView) rootView.findViewById(R.id.scroll_indicators);
389 mScrollIndicators.setSourceView(mWebView);
390
Andy Huangf70fc402012-02-17 15:37:42 -0800391 settings.setJavaScriptEnabled(true);
Andy Huangf70fc402012-02-17 15:37:42 -0800392
Andrew Sapperstein376294b2013-06-06 16:04:26 -0700393 ConversationViewUtils.setTextZoom(getResources(), settings);
Andy Huangc319b552012-04-25 19:53:50 -0700394
Andy Huang51067132012-03-12 20:08:19 -0700395 mViewsCreated = true;
Andy Huange964eee2012-10-02 19:24:58 -0700396 mWebViewLoadedData = false;
Andy Huang51067132012-03-12 20:08:19 -0700397
Mindy Pereira9b875682012-02-15 18:10:54 -0800398 return rootView;
399 }
400
Andrew Sapperstein85ea6182013-10-14 18:21:08 -0700401 protected void inflateSnapHeader(ViewGroup topmostOverlay, LayoutInflater inflater) {
402 inflater.inflate(R.layout.conversation_topmost_overlay_items, topmostOverlay, true);
403 }
404
405 protected void setupNewMessageBar() {
406 mNewMessageBar = (Button) mConversationContainer.findViewById(
407 R.id.new_message_notification_bar);
408 mNewMessageBar.setOnClickListener(new View.OnClickListener() {
409 @Override
410 public void onClick(View v) {
411 onNewMessageBarClick();
412 }
413 });
414 }
415
Mindy Pereira9b875682012-02-15 18:10:54 -0800416 @Override
Andy Huangf7ac83f2013-07-15 15:48:31 -0700417 public void onResume() {
418 super.onResume();
419 if (mWebView != null) {
420 mWebView.onResume();
421 }
422 }
423
424 @Override
425 public void onPause() {
426 super.onPause();
427 if (mWebView != null) {
428 mWebView.onPause();
429 }
430 }
431
432 @Override
Mindy Pereira9b875682012-02-15 18:10:54 -0800433 public void onDestroyView() {
Mindy Pereira9b875682012-02-15 18:10:54 -0800434 super.onDestroyView();
Andy Huang46dfba62012-04-19 01:47:32 -0700435 mConversationContainer.setOverlayAdapter(null);
436 mAdapter = null;
Andy Huang9d3fd922012-09-26 22:23:58 -0700437 resetLoadWaiting(); // be sure to unregister any active load observer
Andy Huang51067132012-03-12 20:08:19 -0700438 mViewsCreated = false;
Mindy Pereira9b875682012-02-15 18:10:54 -0800439 }
440
Andy Huange964eee2012-10-02 19:24:58 -0700441 @Override
442 public void onSaveInstanceState(Bundle outState) {
443 super.onSaveInstanceState(outState);
444
445 outState.putFloat(BUNDLE_KEY_WEBVIEW_Y_PERCENT, calculateScrollYPercent());
446 }
447
448 private float calculateScrollYPercent() {
Paul Westbrook1b56a672013-04-19 01:19:05 -0700449 final float p;
450 if (mWebView == null) {
451 // onCreateView hasn't been called, return 0 as the user hasn't scrolled the view.
452 return 0;
453 }
454
455 final int scrollY = mWebView.getScrollY();
456 final int viewH = mWebView.getHeight();
457 final int webH = (int) (mWebView.getContentHeight() * mWebView.getScale());
Andy Huange964eee2012-10-02 19:24:58 -0700458
459 if (webH == 0 || webH <= viewH) {
460 p = 0;
461 } else if (scrollY + viewH >= webH) {
462 // The very bottom is a special case, it acts as a stronger anchor than the scroll top
463 // at that point.
464 p = 1.0f;
465 } else {
466 p = (float) scrollY / webH;
467 }
468 return p;
469 }
470
Andy Huang9d3fd922012-09-26 22:23:58 -0700471 private void resetLoadWaiting() {
472 if (mLoadWaitReason == LOAD_WAIT_FOR_INITIAL_CONVERSATION) {
473 getListController().unregisterConversationLoadedObserver(mLoadedObserver);
474 }
475 mLoadWaitReason = LOAD_NOW;
476 }
477
Andy Huang5ff63742012-03-16 20:30:23 -0700478 @Override
mindypf4fce122012-09-14 15:55:33 -0700479 protected void markUnread() {
Vikram Aggarwald82a31f2013-02-05 15:03:00 -0800480 super.markUnread();
Andy Huang839ada22012-07-20 15:48:40 -0700481 // Ignore unsafe calls made after a fragment is detached from an activity
482 final ControllableActivity activity = (ControllableActivity) getActivity();
483 if (activity == null) {
484 LogUtils.w(LOG_TAG, "ignoring markUnread for conv=%s", mConversation.id);
485 return;
486 }
487
Andy Huang28e31e22012-07-26 16:33:15 -0700488 if (mViewState == null) {
489 LogUtils.i(LOG_TAG, "ignoring markUnread for conv with no view state (%d)",
490 mConversation.id);
491 return;
492 }
Andy Huang839ada22012-07-20 15:48:40 -0700493 activity.getConversationUpdater().markConversationMessagesUnread(mConversation,
Vikram Aggarwal4a878b62012-07-31 15:09:25 -0700494 mViewState.getUnreadMessageUris(), mViewState.getConversationInfo());
Andy Huang839ada22012-07-20 15:48:40 -0700495 }
496
mindypf4fce122012-09-14 15:55:33 -0700497 @Override
498 public void onUserVisibleHintChanged() {
Andy Huang9d3fd922012-09-26 22:23:58 -0700499 final boolean userVisible = isUserVisible();
Scott Kennedy58192e52013-05-08 16:35:57 -0700500 LogUtils.d(LOG_TAG, "ConversationViewFragment#onUserVisibleHintChanged(), userVisible = %b",
501 userVisible);
Andy Huang9d3fd922012-09-26 22:23:58 -0700502
503 if (!userVisible) {
Andrew Sapperstein376294b2013-06-06 16:04:26 -0700504 mProgressController.dismissLoadingStatus();
Andy Huang9d3fd922012-09-26 22:23:58 -0700505 } else if (mViewsCreated) {
Andy Huange6c9fb62013-11-15 09:56:20 -0800506 String loadTag = null;
507 final boolean isInitialLoading = mActivity.getConversationUpdater()
508 .isInitialConversationLoading();
509
Andy Huang9d3fd922012-09-26 22:23:58 -0700510 if (getMessageCursor() != null) {
511 LogUtils.d(LOG_TAG, "Fragment is now user-visible, onConversationSeen: %s", this);
Andy Huange6c9fb62013-11-15 09:56:20 -0800512 if (!isInitialLoading) {
513 loadTag = "preloaded";
514 }
Andy Huang9d3fd922012-09-26 22:23:58 -0700515 onConversationSeen();
516 } else if (isLoadWaiting()) {
517 LogUtils.d(LOG_TAG, "Fragment is now user-visible, showing conversation: %s", this);
Andy Huange6c9fb62013-11-15 09:56:20 -0800518 if (!isInitialLoading) {
519 loadTag = "load_deferred";
520 }
Andy Huang9d3fd922012-09-26 22:23:58 -0700521 handleDelayedConversationLoad();
522 }
Andy Huange6c9fb62013-11-15 09:56:20 -0800523
524 if (loadTag != null) {
525 // pager swipes are visibility transitions to 'visible' except during initial
526 // pager load on A) enter conversation mode B) rotate C) 2-pane conv-mode list-tap
527 Analytics.getInstance().sendEvent("pager_swipe", loadTag,
528 getCurrentFolderTypeDesc(), 0);
529 }
Andy Huang632721e2012-04-11 16:57:26 -0700530 }
Andy Huang632721e2012-04-11 16:57:26 -0700531
Andy Huang30bcfe72012-10-18 18:09:03 -0700532 if (mWebView != null) {
533 mWebView.onUserVisibilityChanged(userVisible);
534 }
Andy Huangf8cf5462012-10-17 18:29:14 -0700535 }
536
Andy Huang9d3fd922012-09-26 22:23:58 -0700537 /**
538 * Will either call initLoader now to begin loading, or set {@link #mLoadWaitReason} and do
539 * nothing (in which case you should later call {@link #handleDelayedConversationLoad()}).
540 */
Mindy Pereira9b875682012-02-15 18:10:54 -0800541 private void showConversation() {
Andy Huang9d3fd922012-09-26 22:23:58 -0700542 final int reason;
543
544 if (isUserVisible()) {
545 LogUtils.i(LOG_TAG,
546 "SHOWCONV: CVF is user-visible, immediately loading conversation (%s)", this);
547 reason = LOAD_NOW;
Andy Huang243c2362013-03-01 17:50:35 -0800548 timerMark("CVF.showConversation");
Andy Huang9d3fd922012-09-26 22:23:58 -0700549 } else {
550 final boolean disableOffscreenLoading = DISABLE_OFFSCREEN_LOADING
Alice Yang0b8c0802013-09-16 14:35:18 -0700551 || Utils.isLowRamDevice(getContext())
Andrew Sapperstein606dbd72013-07-30 19:14:23 -0700552 || (mConversation != null && (mConversation.isRemote
553 || mConversation.getNumMessages() > mMaxAutoLoadMessages));
Andy Huang9d3fd922012-09-26 22:23:58 -0700554
555 // When not visible, we should not immediately load if either this conversation is
556 // too heavyweight, or if the main/initial conversation is busy loading.
557 if (disableOffscreenLoading) {
558 reason = LOAD_WAIT_UNTIL_VISIBLE;
559 LogUtils.i(LOG_TAG, "SHOWCONV: CVF waiting until visible to load (%s)", this);
560 } else if (getListController().isInitialConversationLoading()) {
561 reason = LOAD_WAIT_FOR_INITIAL_CONVERSATION;
562 LogUtils.i(LOG_TAG, "SHOWCONV: CVF waiting for initial to finish (%s)", this);
563 getListController().registerConversationLoadedObserver(mLoadedObserver);
564 } else {
565 LogUtils.i(LOG_TAG,
566 "SHOWCONV: CVF is not visible, but no reason to wait. loading now. (%s)",
567 this);
568 reason = LOAD_NOW;
569 }
Andy Huang632721e2012-04-11 16:57:26 -0700570 }
Andy Huang9d3fd922012-09-26 22:23:58 -0700571
572 mLoadWaitReason = reason;
573 if (mLoadWaitReason == LOAD_NOW) {
574 startConversationLoad();
575 }
576 }
577
578 private void handleDelayedConversationLoad() {
579 resetLoadWaiting();
580 startConversationLoad();
581 }
582
583 private void startConversationLoad() {
mindyp3bcf1802012-09-09 11:17:00 -0700584 mWebView.setVisibility(View.VISIBLE);
Andrew Sapperstein606dbd72013-07-30 19:14:23 -0700585 loadContent();
mindyp3bcf1802012-09-09 11:17:00 -0700586 // TODO(mindyp): don't show loading status for a previously rendered
587 // conversation. Ielieve this is better done by making sure don't show loading status
588 // until XX ms have passed without loading completed.
Andrew Sapperstein376294b2013-06-06 16:04:26 -0700589 mProgressController.showLoadingStatus(isUserVisible());
Mindy Pereira8e915722012-02-16 14:42:56 -0800590 }
591
Andrew Sapperstein606dbd72013-07-30 19:14:23 -0700592 /**
593 * Can be overridden in case a subclass needs to load something other than
594 * the messages of a conversation.
595 */
596 protected void loadContent() {
597 getLoaderManager().initLoader(MESSAGE_LOADER, Bundle.EMPTY, getMessageLoaderCallbacks());
598 }
599
Andy Huang7d4746e2012-10-17 17:03:17 -0700600 private void revealConversation() {
Andy Huang243c2362013-03-01 17:50:35 -0800601 timerMark("revealing conversation");
Andrew Sapperstein376294b2013-06-06 16:04:26 -0700602 mProgressController.dismissLoadingStatus(mOnProgressDismiss);
Andy Huang7d4746e2012-10-17 17:03:17 -0700603 }
604
Andy Huang9d3fd922012-09-26 22:23:58 -0700605 private boolean isLoadWaiting() {
606 return mLoadWaitReason != LOAD_NOW;
607 }
608
Andy Huang51067132012-03-12 20:08:19 -0700609 private void renderConversation(MessageCursor messageCursor) {
mindyp3bcf1802012-09-09 11:17:00 -0700610 final String convHtml = renderMessageBodies(messageCursor, mEnableContentReadySignal);
Andy Huang243c2362013-03-01 17:50:35 -0800611 timerMark("rendered conversation");
Andy Huangbd544e32012-05-29 15:56:51 -0700612
613 if (DEBUG_DUMP_CONVERSATION_HTML) {
614 java.io.FileWriter fw = null;
615 try {
Andrew Sappersteinf1566b12013-09-19 15:34:26 -0700616 fw = new java.io.FileWriter(getSdCardFilePath());
Andy Huangbd544e32012-05-29 15:56:51 -0700617 fw.write(convHtml);
618 } catch (java.io.IOException e) {
619 e.printStackTrace();
620 } finally {
621 if (fw != null) {
622 try {
623 fw.close();
624 } catch (java.io.IOException e) {
625 e.printStackTrace();
626 }
627 }
628 }
629 }
630
Andy Huange964eee2012-10-02 19:24:58 -0700631 // save off existing scroll position before re-rendering
632 if (mWebViewLoadedData) {
633 mWebViewYPercent = calculateScrollYPercent();
634 }
635
Andy Huangbd544e32012-05-29 15:56:51 -0700636 mWebView.loadDataWithBaseURL(mBaseUri, convHtml, "text/html", "utf-8", null);
Andy Huange964eee2012-10-02 19:24:58 -0700637 mWebViewLoadedData = true;
Andy Huang63b3c672012-10-05 19:27:28 -0700638 mWebViewLoadStartMs = SystemClock.uptimeMillis();
Andy Huang51067132012-03-12 20:08:19 -0700639 }
640
Andrew Sappersteinf1566b12013-09-19 15:34:26 -0700641 protected String getSdCardFilePath() {
642 return "/sdcard/conv" + mConversation.id + ".html";
643 }
644
Andy Huang7bdc3752012-03-25 17:18:19 -0700645 /**
646 * Populate the adapter with overlay views (message headers, super-collapsed blocks, a
647 * conversation header), and return an HTML document with spacer divs inserted for all overlays.
648 *
649 */
Andrew Sapperstein9f957f32013-07-19 15:18:18 -0700650 protected String renderMessageBodies(MessageCursor messageCursor,
mindyp3bcf1802012-09-09 11:17:00 -0700651 boolean enableContentReadySignal) {
Andy Huangf70fc402012-02-17 15:37:42 -0800652 int pos = -1;
Andy Huang632721e2012-04-11 16:57:26 -0700653
Andy Huang1ee96b22012-08-24 20:19:53 -0700654 LogUtils.d(LOG_TAG, "IN renderMessageBodies, fragment=%s", this);
Andy Huang7bdc3752012-03-25 17:18:19 -0700655 boolean allowNetworkImages = false;
656
Andy Huangc7543572012-04-03 15:34:29 -0700657 // TODO: re-use any existing adapter item state (expanded, details expanded, show pics)
Andy Huang28b7aee2012-08-20 20:27:32 -0700658
Andy Huang7bdc3752012-03-25 17:18:19 -0700659 // Walk through the cursor and build up an overlay adapter as you go.
660 // Each overlay has an entry in the adapter for easy scroll handling in the container.
661 // Items are not necessarily 1:1 in cursor and adapter because of super-collapsed blocks.
662 // When adding adapter items, also add their heights to help the container later determine
663 // overlay dimensions.
664
Andy Huangdb620fe2012-08-24 15:45:28 -0700665 // When re-rendering, prevent ConversationContainer from laying out overlays until after
666 // the new spacers are positioned by WebView.
667 mConversationContainer.invalidateSpacerGeometry();
668
Andy Huang7bdc3752012-03-25 17:18:19 -0700669 mAdapter.clear();
670
Andy Huang47aa9c92012-07-31 15:37:21 -0700671 // re-evaluate the message parts of the view state, since the messages may have changed
672 // since the previous render
673 final ConversationViewState prevState = mViewState;
674 mViewState = new ConversationViewState(prevState);
675
Andy Huang5ff63742012-03-16 20:30:23 -0700676 // N.B. the units of height for spacers are actually dp and not px because WebView assumes
Andy Huang2e9acfe2012-03-15 22:39:36 -0700677 // a pixel is an mdpi pixel, unless you set device-dpi.
Andy Huang5ff63742012-03-16 20:30:23 -0700678
Andy Huang7bdc3752012-03-25 17:18:19 -0700679 // add a single conversation header item
680 final int convHeaderPos = mAdapter.addConversationHeader(mConversation);
Andy Huang23014702012-07-09 12:50:36 -0700681 final int convHeaderPx = measureOverlayHeight(convHeaderPos);
Andy Huang5ff63742012-03-16 20:30:23 -0700682
Andy Huang02f9d182012-11-28 22:38:02 -0800683 mTemplates.startConversation(mWebView.screenPxToWebPx(mSideMarginPx),
Andy Huang256b35c2012-08-22 15:19:13 -0700684 mWebView.screenPxToWebPx(convHeaderPx));
Andy Huang3233bff2012-03-20 19:38:45 -0700685
Andy Huang46dfba62012-04-19 01:47:32 -0700686 int collapsedStart = -1;
Andy Huang839ada22012-07-20 15:48:40 -0700687 ConversationMessage prevCollapsedMsg = null;
Andrew Sappersteine8221482013-10-02 18:14:58 -0700688
Alice Yangf323c042013-10-30 00:15:02 -0700689 final boolean alwaysShowImages = (mAccount != null) &&
690 (mAccount.settings.showImages == Settings.ShowImages.ALWAYS);
691
Andrew Sappersteine8221482013-10-02 18:14:58 -0700692 boolean prevSafeForImages = alwaysShowImages;
Andy Huang46dfba62012-04-19 01:47:32 -0700693
Andrew Sappersteincee3c902013-07-31 10:52:02 -0700694 // Store the previous expanded state so that the border between
695 // the previous and current message can be properly initialized.
696 int previousExpandedState = ExpansionState.NONE;
Andy Huangf70fc402012-02-17 15:37:42 -0800697 while (messageCursor.moveToPosition(++pos)) {
Andy Huang839ada22012-07-20 15:48:40 -0700698 final ConversationMessage msg = messageCursor.getMessage();
Andy Huang46dfba62012-04-19 01:47:32 -0700699
Andrew Sappersteine8221482013-10-02 18:14:58 -0700700 final boolean safeForImages = alwaysShowImages ||
Scott Kennedy20273842012-11-07 11:16:21 -0800701 msg.alwaysShowImages || prevState.getShouldShowImages(msg);
Andy Huang3233bff2012-03-20 19:38:45 -0700702 allowNetworkImages |= safeForImages;
Andy Huang24055282012-03-27 17:37:06 -0700703
Paul Westbrook08098ec2012-08-12 15:30:28 -0700704 final Integer savedExpanded = prevState.getExpansionState(msg);
705 final int expandedState;
Andy Huang839ada22012-07-20 15:48:40 -0700706 if (savedExpanded != null) {
Andy Huang1ee96b22012-08-24 20:19:53 -0700707 if (ExpansionState.isSuperCollapsed(savedExpanded) && messageCursor.isLast()) {
708 // override saved state when this is now the new last message
709 // this happens to the second-to-last message when you discard a draft
710 expandedState = ExpansionState.EXPANDED;
711 } else {
712 expandedState = savedExpanded;
713 }
Andy Huang839ada22012-07-20 15:48:40 -0700714 } else {
Andy Huangcd5c5ee2012-08-12 19:03:51 -0700715 // new messages that are not expanded default to being eligible for super-collapse
Paul Westbrook08098ec2012-08-12 15:30:28 -0700716 expandedState = (!msg.read || msg.starred || messageCursor.isLast()) ?
Andy Huangcd5c5ee2012-08-12 19:03:51 -0700717 ExpansionState.EXPANDED : ExpansionState.SUPER_COLLAPSED;
Andy Huang839ada22012-07-20 15:48:40 -0700718 }
Scott Kennedy20273842012-11-07 11:16:21 -0800719 mViewState.setShouldShowImages(msg, prevState.getShouldShowImages(msg));
Paul Westbrook08098ec2012-08-12 15:30:28 -0700720 mViewState.setExpansionState(msg, expandedState);
Andy Huangc7543572012-04-03 15:34:29 -0700721
Andy Huang839ada22012-07-20 15:48:40 -0700722 // save off "read" state from the cursor
723 // later, the view may not match the cursor (e.g. conversation marked read on open)
Andy Huang423bea22012-08-21 12:00:49 -0700724 // however, if a previous state indicated this message was unread, trust that instead
725 // so "mark unread" marks all originally unread messages
726 mViewState.setReadState(msg, msg.read && !prevState.isUnread(msg));
Andy Huang839ada22012-07-20 15:48:40 -0700727
Andy Huangcd5c5ee2012-08-12 19:03:51 -0700728 // We only want to consider this for inclusion in the super collapsed block if
729 // 1) The we don't have previous state about this message (The first time that the
730 // user opens a conversation)
731 // 2) The previously saved state for this message indicates that this message is
732 // in the super collapsed block.
733 if (ExpansionState.isSuperCollapsed(expandedState)) {
734 // contribute to a super-collapsed block that will be emitted just before the
735 // next expanded header
736 if (collapsedStart < 0) {
737 collapsedStart = pos;
Andy Huang46dfba62012-04-19 01:47:32 -0700738 }
Andy Huangcd5c5ee2012-08-12 19:03:51 -0700739 prevCollapsedMsg = msg;
740 prevSafeForImages = safeForImages;
Andrew Sapperstein7dc7fa02013-05-08 16:39:31 -0700741
742 // This line puts the from address in the address cache so that
743 // we get the sender image for it if it's in a super-collapsed block.
744 getAddress(msg.getFrom());
Andrew Sappersteincee3c902013-07-31 10:52:02 -0700745 previousExpandedState = expandedState;
Andy Huangcd5c5ee2012-08-12 19:03:51 -0700746 continue;
Andy Huang46dfba62012-04-19 01:47:32 -0700747 }
Andy Huang24055282012-03-27 17:37:06 -0700748
Andy Huang46dfba62012-04-19 01:47:32 -0700749 // resolve any deferred decisions on previous collapsed items
750 if (collapsedStart >= 0) {
751 if (pos - collapsedStart == 1) {
Andrew Sappersteincee3c902013-07-31 10:52:02 -0700752 // Special-case for a single collapsed message: no need to super-collapse it.
753 // Since it is super-collapsed, there is no previous message to be
754 // collapsed and the border above it is the first border.
755 renderMessage(prevCollapsedMsg, false /* previousCollapsed */,
756 false /* expanded */, prevSafeForImages, true /* firstBorder */);
Andy Huang46dfba62012-04-19 01:47:32 -0700757 } else {
758 renderSuperCollapsedBlock(collapsedStart, pos - 1);
759 }
760 prevCollapsedMsg = null;
761 collapsedStart = -1;
762 }
Andy Huang7bdc3752012-03-25 17:18:19 -0700763
Andrew Sappersteincee3c902013-07-31 10:52:02 -0700764 renderMessage(msg, ExpansionState.isCollapsed(previousExpandedState),
765 ExpansionState.isExpanded(expandedState), safeForImages,
Andrew Sappersteine5398802013-07-29 20:55:38 -0700766 pos == 0 /* firstBorder */);
Andrew Sappersteincee3c902013-07-31 10:52:02 -0700767
768 previousExpandedState = expandedState;
Mindy Pereira9b875682012-02-15 18:10:54 -0800769 }
Andy Huang3233bff2012-03-20 19:38:45 -0700770
771 mWebView.getSettings().setBlockNetworkImage(!allowNetworkImages);
772
Andrew Sapperstein2fc67302013-04-29 18:24:56 -0700773 final boolean applyTransforms = shouldApplyTransforms();
Andy Huang57f354c2013-04-11 17:23:40 -0700774
Andrew Sappersteincee3c902013-07-31 10:52:02 -0700775 renderBorder(true /* contiguous */, true /* expanded */,
776 false /* firstBorder */, true /* lastBorder */);
Andrew Sapperstein14f93742013-07-25 14:29:56 -0700777
Andy Huangc1fb9a92013-02-11 13:09:12 -0800778 // If the conversation has specified a base uri, use it here, otherwise use mBaseUri
Andrew Sapperstein68141df2013-08-19 19:07:14 -0700779 return mTemplates.endConversation(mBaseUri, mConversation.getBaseUri(mBaseUri),
Andy Huang5ea5a832013-03-07 16:49:09 -0800780 mWebView.getViewportWidth(), enableContentReadySignal, isOverviewMode(mAccount),
Andy Huang57f354c2013-04-11 17:23:40 -0700781 applyTransforms, applyTransforms);
Mindy Pereira9b875682012-02-15 18:10:54 -0800782 }
Mindy Pereira674afa42012-02-17 14:05:24 -0800783
Andy Huang46dfba62012-04-19 01:47:32 -0700784 private void renderSuperCollapsedBlock(int start, int end) {
Andrew Sappersteincee3c902013-07-31 10:52:02 -0700785 renderBorder(true /* contiguous */, true /* expanded */,
786 true /* firstBorder */, false /* lastBorder */);
Andy Huang46dfba62012-04-19 01:47:32 -0700787 final int blockPos = mAdapter.addSuperCollapsedBlock(start, end);
Andy Huang23014702012-07-09 12:50:36 -0700788 final int blockPx = measureOverlayHeight(blockPos);
789 mTemplates.appendSuperCollapsedHtml(start, mWebView.screenPxToWebPx(blockPx));
Andy Huang46dfba62012-04-19 01:47:32 -0700790 }
791
Andrew Sappersteincee3c902013-07-31 10:52:02 -0700792 protected void renderBorder(
793 boolean contiguous, boolean expanded, boolean firstBorder, boolean lastBorder) {
794 final int blockPos = mAdapter.addBorder(contiguous, expanded, firstBorder, lastBorder);
Andrew Sapperstein14f93742013-07-25 14:29:56 -0700795 final int blockPx = measureOverlayHeight(blockPos);
796 mTemplates.appendBorder(mWebView.screenPxToWebPx(blockPx));
797 }
798
Andrew Sappersteincee3c902013-07-31 10:52:02 -0700799 private void renderMessage(ConversationMessage msg, boolean previousCollapsed,
800 boolean expanded, boolean safeForImages, boolean firstBorder) {
801 renderMessage(msg, previousCollapsed, expanded, safeForImages,
802 true /* renderBorder */, firstBorder);
Andrew Sapperstein1f082232013-07-29 11:16:07 -0700803 }
804
Andrew Sappersteincee3c902013-07-31 10:52:02 -0700805 private void renderMessage(ConversationMessage msg, boolean previousCollapsed,
806 boolean expanded, boolean safeForImages, boolean renderBorder, boolean firstBorder) {
Andrew Sapperstein1f082232013-07-29 11:16:07 -0700807 if (renderBorder) {
Andrew Sappersteincee3c902013-07-31 10:52:02 -0700808 // The border should be collapsed only if both the current
809 // and previous messages are collapsed.
810 renderBorder(true /* contiguous */, !previousCollapsed || expanded,
811 firstBorder, false /* lastBorder */);
Andrew Sapperstein1f082232013-07-29 11:16:07 -0700812 }
Andrew Sapperstein14f93742013-07-25 14:29:56 -0700813
Scott Kennedy20273842012-11-07 11:16:21 -0800814 final int headerPos = mAdapter.addMessageHeader(msg, expanded,
815 mViewState.getShouldShowImages(msg));
Andy Huang46dfba62012-04-19 01:47:32 -0700816 final MessageHeaderItem headerItem = (MessageHeaderItem) mAdapter.getItem(headerPos);
817
818 final int footerPos = mAdapter.addMessageFooter(headerItem);
819
820 // Measure item header and footer heights to allocate spacers in HTML
821 // But since the views themselves don't exist yet, render each item temporarily into
822 // a host view for measurement.
Andy Huang23014702012-07-09 12:50:36 -0700823 final int headerPx = measureOverlayHeight(headerPos);
824 final int footerPx = measureOverlayHeight(footerPos);
Andy Huang46dfba62012-04-19 01:47:32 -0700825
Andy Huang256b35c2012-08-22 15:19:13 -0700826 mTemplates.appendMessageHtml(msg, expanded, safeForImages,
Andy Huang23014702012-07-09 12:50:36 -0700827 mWebView.screenPxToWebPx(headerPx), mWebView.screenPxToWebPx(footerPx));
Andy Huang243c2362013-03-01 17:50:35 -0800828 timerMark("rendered message");
Andy Huang46dfba62012-04-19 01:47:32 -0700829 }
830
831 private String renderCollapsedHeaders(MessageCursor cursor,
832 SuperCollapsedBlockItem blockToReplace) {
833 final List<ConversationOverlayItem> replacements = Lists.newArrayList();
834
835 mTemplates.reset();
836
Alice Yangf323c042013-10-30 00:15:02 -0700837 final boolean alwaysShowImages = (mAccount != null) &&
838 (mAccount.settings.showImages == Settings.ShowImages.ALWAYS);
Andrew Sappersteine8221482013-10-02 18:14:58 -0700839
Mark Wei2b24e992012-09-10 16:40:07 -0700840 // In devices with non-integral density multiplier, screen pixels translate to non-integral
841 // web pixels. Keep track of the error that occurs when we cast all heights to int
842 float error = 0f;
Andrew Sapperstein14f93742013-07-25 14:29:56 -0700843 boolean first = true;
Andy Huang46dfba62012-04-19 01:47:32 -0700844 for (int i = blockToReplace.getStart(), end = blockToReplace.getEnd(); i <= end; i++) {
845 cursor.moveToPosition(i);
Andy Huang839ada22012-07-20 15:48:40 -0700846 final ConversationMessage msg = cursor.getMessage();
Andrew Sapperstein14f93742013-07-25 14:29:56 -0700847
848 final int borderPx;
849 if (first) {
850 borderPx = 0;
851 first = false;
852 } else {
Andrew Sappersteincee3c902013-07-31 10:52:02 -0700853 // When replacing the super-collapsed block,
854 // the border is always collapsed between messages.
855 final BorderItem border = mAdapter.newBorderItem(
856 true /* contiguous */, false /* expanded */);
Andrew Sapperstein14f93742013-07-25 14:29:56 -0700857 borderPx = measureOverlayHeight(border);
858 replacements.add(border);
859 mTemplates.appendBorder(mWebView.screenPxToWebPx(borderPx));
860 }
861
Andrew Sapperstein4ddda2f2013-06-10 11:15:38 -0700862 final MessageHeaderItem header = ConversationViewAdapter.newMessageHeaderItem(
Andrew Sapperstein14f93742013-07-25 14:29:56 -0700863 mAdapter, mAdapter.getDateBuilder(), msg, false /* expanded */,
Andrew Sappersteine8221482013-10-02 18:14:58 -0700864 alwaysShowImages || mViewState.getShouldShowImages(msg));
Andy Huang46dfba62012-04-19 01:47:32 -0700865 final MessageFooterItem footer = mAdapter.newMessageFooterItem(header);
866
Andy Huang23014702012-07-09 12:50:36 -0700867 final int headerPx = measureOverlayHeight(header);
868 final int footerPx = measureOverlayHeight(footer);
Mark Wei2b24e992012-09-10 16:40:07 -0700869 error += mWebView.screenPxToWebPxError(headerPx)
Andrew Sapperstein14f93742013-07-25 14:29:56 -0700870 + mWebView.screenPxToWebPxError(footerPx)
871 + mWebView.screenPxToWebPxError(borderPx);
Mark Wei2b24e992012-09-10 16:40:07 -0700872
873 // When the error becomes greater than 1 pixel, make the next header 1 pixel taller
874 int correction = 0;
875 if (error >= 1) {
876 correction = 1;
877 error -= 1;
878 }
Andy Huang46dfba62012-04-19 01:47:32 -0700879
Andrew Sappersteine8221482013-10-02 18:14:58 -0700880 mTemplates.appendMessageHtml(msg, false /* expanded */,
881 alwaysShowImages || msg.alwaysShowImages,
Mark Wei2b24e992012-09-10 16:40:07 -0700882 mWebView.screenPxToWebPx(headerPx) + correction,
883 mWebView.screenPxToWebPx(footerPx));
Andy Huang46dfba62012-04-19 01:47:32 -0700884 replacements.add(header);
885 replacements.add(footer);
Andy Huang839ada22012-07-20 15:48:40 -0700886
Paul Westbrook08098ec2012-08-12 15:30:28 -0700887 mViewState.setExpansionState(msg, ExpansionState.COLLAPSED);
Andy Huang46dfba62012-04-19 01:47:32 -0700888 }
889
890 mAdapter.replaceSuperCollapsedBlock(blockToReplace, replacements);
Andy Huang06c03622012-10-22 18:59:45 -0700891 mAdapter.notifyDataSetChanged();
Andy Huang46dfba62012-04-19 01:47:32 -0700892
893 return mTemplates.emit();
894 }
895
Andrew Sapperstein9f957f32013-07-19 15:18:18 -0700896 protected int measureOverlayHeight(int position) {
Andy Huang46dfba62012-04-19 01:47:32 -0700897 return measureOverlayHeight(mAdapter.getItem(position));
898 }
899
Andy Huang7bdc3752012-03-25 17:18:19 -0700900 /**
Andy Huangb8331b42012-07-16 19:08:53 -0700901 * Measure the height of an adapter view by rendering an adapter item into a temporary
Andy Huang46dfba62012-04-19 01:47:32 -0700902 * host view, and asking the view to immediately measure itself. This method will reuse
Andy Huang7bdc3752012-03-25 17:18:19 -0700903 * a previous adapter view from {@link ConversationContainer}'s scrap views if one was generated
904 * earlier.
905 * <p>
Andy Huang46dfba62012-04-19 01:47:32 -0700906 * After measuring the height, this method also saves the height in the
907 * {@link ConversationOverlayItem} for later use in overlay positioning.
Andy Huang7bdc3752012-03-25 17:18:19 -0700908 *
Andy Huang46dfba62012-04-19 01:47:32 -0700909 * @param convItem adapter item with data to render and measure
Andy Huang23014702012-07-09 12:50:36 -0700910 * @return height of the rendered view in screen px
Andy Huang7bdc3752012-03-25 17:18:19 -0700911 */
Andy Huang46dfba62012-04-19 01:47:32 -0700912 private int measureOverlayHeight(ConversationOverlayItem convItem) {
Andy Huang7bdc3752012-03-25 17:18:19 -0700913 final int type = convItem.getType();
914
915 final View convertView = mConversationContainer.getScrapView(type);
Andy Huangb8331b42012-07-16 19:08:53 -0700916 final View hostView = mAdapter.getView(convItem, convertView, mConversationContainer,
917 true /* measureOnly */);
Andy Huang7bdc3752012-03-25 17:18:19 -0700918 if (convertView == null) {
919 mConversationContainer.addScrapView(type, hostView);
920 }
921
Andy Huang9875bb42012-04-04 20:36:21 -0700922 final int heightPx = mConversationContainer.measureOverlay(hostView);
Andy Huang7bdc3752012-03-25 17:18:19 -0700923 convItem.setHeight(heightPx);
Andy Huang9875bb42012-04-04 20:36:21 -0700924 convItem.markMeasurementValid();
Andy Huang7bdc3752012-03-25 17:18:19 -0700925
Andy Huang23014702012-07-09 12:50:36 -0700926 return heightPx;
Andy Huang7bdc3752012-03-25 17:18:19 -0700927 }
928
Andy Huang5ff63742012-03-16 20:30:23 -0700929 @Override
930 public void onConversationViewHeaderHeightChange(int newHeight) {
Mark Weiab2d9982012-09-25 13:06:17 -0700931 final int h = mWebView.screenPxToWebPx(newHeight);
932
933 mWebView.loadUrl(String.format("javascript:setConversationHeaderSpacerHeight(%s);", h));
Andy Huang5ff63742012-03-16 20:30:23 -0700934 }
935
Andy Huang3233bff2012-03-20 19:38:45 -0700936 // END conversation header callbacks
937
938 // START message header callbacks
939 @Override
Andy Huangc7543572012-04-03 15:34:29 -0700940 public void setMessageSpacerHeight(MessageHeaderItem item, int newSpacerHeightPx) {
941 mConversationContainer.invalidateSpacerGeometry();
942
943 // update message HTML spacer height
Andy Huang23014702012-07-09 12:50:36 -0700944 final int h = mWebView.screenPxToWebPx(newSpacerHeightPx);
945 LogUtils.i(LAYOUT_TAG, "setting HTML spacer h=%dwebPx (%dscreenPx)", h,
946 newSpacerHeightPx);
Vikram Aggarwal5349ce12012-09-24 14:12:40 -0700947 mWebView.loadUrl(String.format("javascript:setMessageHeaderSpacerHeight('%s', %s);",
Andy Huang014ea4c2012-09-25 14:50:54 -0700948 mTemplates.getMessageDomId(item.getMessage()), h));
Andy Huang3233bff2012-03-20 19:38:45 -0700949 }
950
951 @Override
Andrew Sappersteincee3c902013-07-31 10:52:02 -0700952 public void setMessageExpanded(MessageHeaderItem item, int newSpacerHeightPx,
953 int topBorderHeight, int bottomBorderHeight) {
Andy Huangc7543572012-04-03 15:34:29 -0700954 mConversationContainer.invalidateSpacerGeometry();
955
956 // show/hide the HTML message body and update the spacer height
Andy Huang23014702012-07-09 12:50:36 -0700957 final int h = mWebView.screenPxToWebPx(newSpacerHeightPx);
Andrew Sappersteincee3c902013-07-31 10:52:02 -0700958 final int topHeight = mWebView.screenPxToWebPx(topBorderHeight);
959 final int bottomHeight = mWebView.screenPxToWebPx(bottomBorderHeight);
Andy Huang23014702012-07-09 12:50:36 -0700960 LogUtils.i(LAYOUT_TAG, "setting HTML spacer expanded=%s h=%dwebPx (%dscreenPx)",
961 item.isExpanded(), h, newSpacerHeightPx);
Andrew Sappersteincee3c902013-07-31 10:52:02 -0700962 mWebView.loadUrl(String.format("javascript:setMessageBodyVisible('%s', %s, %s, %s, %s);",
963 mTemplates.getMessageDomId(item.getMessage()), item.isExpanded(),
964 h, topHeight, bottomHeight));
Andy Huang839ada22012-07-20 15:48:40 -0700965
Andy Huang014ea4c2012-09-25 14:50:54 -0700966 mViewState.setExpansionState(item.getMessage(),
Paul Westbrook08098ec2012-08-12 15:30:28 -0700967 item.isExpanded() ? ExpansionState.EXPANDED : ExpansionState.COLLAPSED);
Andy Huang3233bff2012-03-20 19:38:45 -0700968 }
969
970 @Override
Scott Kennedyeb9a4bd2012-11-12 10:33:04 -0800971 public void showExternalResources(final Message msg) {
Scott Kennedy20273842012-11-07 11:16:21 -0800972 mViewState.setShouldShowImages(msg, true);
Andy Huang3233bff2012-03-20 19:38:45 -0700973 mWebView.getSettings().setBlockNetworkImage(false);
Scott Kennedyeb9a4bd2012-11-12 10:33:04 -0800974 mWebView.loadUrl("javascript:unblockImages(['" + mTemplates.getMessageDomId(msg) + "']);");
975 }
976
977 @Override
978 public void showExternalResources(final String senderRawAddress) {
979 mWebView.getSettings().setBlockNetworkImage(false);
980
981 final Address sender = getAddress(senderRawAddress);
982 final MessageCursor cursor = getMessageCursor();
983
984 final List<String> messageDomIds = new ArrayList<String>();
985
986 int pos = -1;
987 while (cursor.moveToPosition(++pos)) {
988 final ConversationMessage message = cursor.getMessage();
989 if (sender.equals(getAddress(message.getFrom()))) {
990 message.alwaysShowImages = true;
991
992 mViewState.setShouldShowImages(message, true);
993 messageDomIds.add(mTemplates.getMessageDomId(message));
994 }
995 }
996
997 final String url = String.format(
998 "javascript:unblockImages(['%s']);", TextUtils.join("','", messageDomIds));
999 mWebView.loadUrl(url);
Andy Huang3233bff2012-03-20 19:38:45 -07001000 }
Alice Yang1ebc2db2013-03-14 21:21:44 -07001001
1002 @Override
Andy Huang75b52a52013-03-15 15:40:24 -07001003 public boolean supportsMessageTransforms() {
1004 return true;
1005 }
1006
1007 @Override
Alice Yang1ebc2db2013-03-14 21:21:44 -07001008 public String getMessageTransforms(final Message msg) {
1009 final String domId = mTemplates.getMessageDomId(msg);
1010 return (domId == null) ? null : mMessageTransforms.get(domId);
1011 }
1012
Andy Huang3233bff2012-03-20 19:38:45 -07001013 // END message header callbacks
Andy Huang5ff63742012-03-16 20:30:23 -07001014
Andy Huang46dfba62012-04-19 01:47:32 -07001015 @Override
Andrew Sapperstein2fc67302013-04-29 18:24:56 -07001016 public void showUntransformedConversation() {
1017 super.showUntransformedConversation();
1018 renderConversation(getMessageCursor());
1019 }
1020
1021 @Override
Andy Huang46dfba62012-04-19 01:47:32 -07001022 public void onSuperCollapsedClick(SuperCollapsedBlockItem item) {
mindypf4fce122012-09-14 15:55:33 -07001023 MessageCursor cursor = getMessageCursor();
1024 if (cursor == null || !mViewsCreated) {
Andy Huang46dfba62012-04-19 01:47:32 -07001025 return;
1026 }
1027
mindypf4fce122012-09-14 15:55:33 -07001028 mTempBodiesHtml = renderCollapsedHeaders(cursor, item);
Andy Huang46dfba62012-04-19 01:47:32 -07001029 mWebView.loadUrl("javascript:replaceSuperCollapsedBlock(" + item.getStart() + ")");
1030 }
1031
Andy Huang47aa9c92012-07-31 15:37:21 -07001032 private void showNewMessageNotification(NewMessagesInfo info) {
Andrew Sapperstein821fa872013-08-21 21:57:39 -07001033 mNewMessageBar.setText(info.getNotificationText());
Andy Huang47aa9c92012-07-31 15:37:21 -07001034 mNewMessageBar.setVisibility(View.VISIBLE);
1035 }
1036
1037 private void onNewMessageBarClick() {
1038 mNewMessageBar.setVisibility(View.GONE);
1039
mindypf4fce122012-09-14 15:55:33 -07001040 renderConversation(getMessageCursor()); // mCursor is already up-to-date
1041 // per onLoadFinished()
Andy Huang5fbda022012-02-28 18:22:03 -08001042 }
1043
Andy Huangadbf3e82012-10-13 13:30:19 -07001044 private static OverlayPosition[] parsePositions(final String[] topArray,
1045 final String[] bottomArray) {
1046 final int len = topArray.length;
1047 final OverlayPosition[] positions = new OverlayPosition[len];
Andy Huangb5078b22012-03-05 19:52:29 -08001048 for (int i = 0; i < len; i++) {
Andy Huangadbf3e82012-10-13 13:30:19 -07001049 positions[i] = new OverlayPosition(
1050 Integer.parseInt(topArray[i]), Integer.parseInt(bottomArray[i]));
Andy Huangb5078b22012-03-05 19:52:29 -08001051 }
Andy Huangadbf3e82012-10-13 13:30:19 -07001052 return positions;
Andy Huangb5078b22012-03-05 19:52:29 -08001053 }
1054
Andrew Sapperstein9f957f32013-07-19 15:18:18 -07001055 protected Address getAddress(String rawFrom) {
Paul Westbrook0dfae692013-10-02 00:51:29 -07001056 return Utils.getAddress(mAddressCache, rawFrom);
Andy Huang16174812012-08-16 16:40:35 -07001057 }
1058
Andy Huang9d3fd922012-09-26 22:23:58 -07001059 private void ensureContentSizeChangeListener() {
1060 if (mWebViewSizeChangeListener == null) {
Andy Huangc1fb9a92013-02-11 13:09:12 -08001061 mWebViewSizeChangeListener = new ContentSizeChangeListener() {
Andy Huang9d3fd922012-09-26 22:23:58 -07001062 @Override
1063 public void onHeightChange(int h) {
1064 // When WebKit says the DOM height has changed, re-measure
1065 // bodies and re-position their headers.
1066 // This is separate from the typical JavaScript DOM change
1067 // listeners because cases like NARROW_COLUMNS text reflow do not trigger DOM
1068 // events.
1069 mWebView.loadUrl("javascript:measurePositions();");
1070 }
1071 };
1072 }
1073 mWebView.setContentSizeChangeListener(mWebViewSizeChangeListener);
1074 }
1075
Andrew Sapperstein9f957f32013-07-19 15:18:18 -07001076 public static boolean isOverviewMode(Account acct) {
Andy Huangccf67802013-03-15 14:31:57 -07001077 return acct.settings.isOverviewMode();
Andy Huangadbf3e82012-10-13 13:30:19 -07001078 }
1079
1080 private void setupOverviewMode() {
Andy Huang02f9d182012-11-28 22:38:02 -08001081 // for now, overview mode means use the built-in WebView zoom and disable custom scale
1082 // gesture handling
Andy Huangadbf3e82012-10-13 13:30:19 -07001083 final boolean overviewMode = isOverviewMode(mAccount);
1084 final WebSettings settings = mWebView.getSettings();
Andy Huang06def562012-10-14 00:19:11 -07001085 settings.setUseWideViewPort(overviewMode);
Andy Huangc1fb9a92013-02-11 13:09:12 -08001086
1087 final OnScaleGestureListener listener;
1088
Andy Huang57f354c2013-04-11 17:23:40 -07001089 settings.setSupportZoom(overviewMode);
1090 settings.setBuiltInZoomControls(overviewMode);
1091 if (overviewMode) {
1092 settings.setDisplayZoomControls(false);
Andy Huangadbf3e82012-10-13 13:30:19 -07001093 }
Andy Huang57f354c2013-04-11 17:23:40 -07001094 listener = ENABLE_CSS_ZOOM && !overviewMode ? new CssScaleInterceptor() : null;
Andy Huangc1fb9a92013-02-11 13:09:12 -08001095
Andy Huang57f354c2013-04-11 17:23:40 -07001096 mWebView.setOnScaleGestureListener(listener);
Andy Huangadbf3e82012-10-13 13:30:19 -07001097 }
1098
Andrew Sappersteinb1d184d2013-08-09 14:14:31 -07001099 public class ConversationWebViewClient extends AbstractConversationWebViewClient {
Andrew Sapperstein4ddda2f2013-06-10 11:15:38 -07001100 public ConversationWebViewClient(Account account) {
1101 super(account);
Andrew Sapperstein376294b2013-06-06 16:04:26 -07001102 }
1103
Andy Huang17a9cde2012-03-09 18:03:16 -08001104 @Override
1105 public void onPageFinished(WebView view, String url) {
Andy Huang9a8bc1e2012-10-23 19:48:25 -07001106 // Ignore unsafe calls made after a fragment is detached from an activity.
1107 // This method needs to, for example, get at the loader manager, which needs
1108 // the fragment to be added.
Andy Huang9a8bc1e2012-10-23 19:48:25 -07001109 if (!isAdded() || !mViewsCreated) {
Paul Westbrook006e13c2013-07-24 18:40:20 -07001110 LogUtils.d(LOG_TAG, "ignoring CVF.onPageFinished, url=%s fragment=%s", url,
Andy Huangb95da852012-07-18 14:16:58 -07001111 ConversationViewFragment.this);
1112 return;
1113 }
1114
Paul Westbrook006e13c2013-07-24 18:40:20 -07001115 LogUtils.d(LOG_TAG, "IN CVF.onPageFinished, url=%s fragment=%s wv=%s t=%sms", url,
Andy Huang30bcfe72012-10-18 18:09:03 -07001116 ConversationViewFragment.this, view,
Andy Huang63b3c672012-10-05 19:27:28 -07001117 (SystemClock.uptimeMillis() - mWebViewLoadStartMs));
Andy Huang632721e2012-04-11 16:57:26 -07001118
Andy Huang9d3fd922012-09-26 22:23:58 -07001119 ensureContentSizeChangeListener();
1120
mindyp3bcf1802012-09-09 11:17:00 -07001121 if (!mEnableContentReadySignal) {
Andy Huang7d4746e2012-10-17 17:03:17 -07001122 revealConversation();
mindyp3bcf1802012-09-09 11:17:00 -07001123 }
Andy Huang9d3fd922012-09-26 22:23:58 -07001124
Andy Huang9a8bc1e2012-10-23 19:48:25 -07001125 final Set<String> emailAddresses = Sets.newHashSet();
Andy Huang543e7092013-04-22 11:44:56 -07001126 final List<Address> cacheCopy;
1127 synchronized (mAddressCache) {
1128 cacheCopy = ImmutableList.copyOf(mAddressCache.values());
1129 }
1130 for (Address addr : cacheCopy) {
Andy Huang9a8bc1e2012-10-23 19:48:25 -07001131 emailAddresses.add(addr.getAddress());
Andy Huangb8331b42012-07-16 19:08:53 -07001132 }
Andrew Sapperstein4ddda2f2013-06-10 11:15:38 -07001133 final ContactLoaderCallbacks callbacks = getContactInfoSource();
1134 callbacks.setSenders(emailAddresses);
Andy Huang9a8bc1e2012-10-23 19:48:25 -07001135 getLoaderManager().restartLoader(CONTACT_LOADER, Bundle.EMPTY, callbacks);
Andy Huang17a9cde2012-03-09 18:03:16 -08001136 }
1137
Andy Huangaf5d4e02012-03-19 19:02:12 -07001138 @Override
1139 public boolean shouldOverrideUrlLoading(WebView view, String url) {
Paul Westbrook542fec92012-09-18 14:47:51 -07001140 return mViewsCreated && super.shouldOverrideUrlLoading(view, url);
Andy Huangaf5d4e02012-03-19 19:02:12 -07001141 }
Andy Huang17a9cde2012-03-09 18:03:16 -08001142 }
1143
Andy Huangf70fc402012-02-17 15:37:42 -08001144 /**
1145 * NOTE: all public methods must be listed in the proguard flags so that they can be accessed
1146 * via reflection and not stripped.
1147 *
1148 */
1149 private class MailJsBridge {
1150
1151 @SuppressWarnings("unused")
Mindy Pereira974c9662012-09-14 10:02:08 -07001152 @JavascriptInterface
Andy Huangadbf3e82012-10-13 13:30:19 -07001153 public void onWebContentGeometryChange(final String[] overlayTopStrs,
1154 final String[] overlayBottomStrs) {
Andrew Sapperstein376294b2013-06-06 16:04:26 -07001155 getHandler().post(new FragmentRunnable("onWebContentGeometryChange",
1156 ConversationViewFragment.this) {
mindyp1b3cc472012-09-27 11:32:59 -07001157
Andy Huang9a8bc1e2012-10-23 19:48:25 -07001158 @Override
1159 public void go() {
1160 try {
Andy Huang46dfba62012-04-19 01:47:32 -07001161 if (!mViewsCreated) {
mindyp1b3cc472012-09-27 11:32:59 -07001162 LogUtils.d(LOG_TAG, "ignoring webContentGeometryChange because views"
1163 + " are gone, %s", ConversationViewFragment.this);
Andy Huang46dfba62012-04-19 01:47:32 -07001164 return;
1165 }
Andy Huangadbf3e82012-10-13 13:30:19 -07001166 mConversationContainer.onGeometryChange(
1167 parsePositions(overlayTopStrs, overlayBottomStrs));
mindyp1b3cc472012-09-27 11:32:59 -07001168 if (mDiff != 0) {
1169 // SCROLL!
1170 int scale = (int) (mWebView.getScale() / mWebView.getInitialScale());
1171 if (scale > 1) {
1172 mWebView.scrollBy(0, (mDiff * (scale - 1)));
1173 }
1174 mDiff = 0;
1175 }
Andy Huang9a8bc1e2012-10-23 19:48:25 -07001176 } catch (Throwable t) {
1177 LogUtils.e(LOG_TAG, t, "Error in MailJsBridge.onWebContentGeometryChange");
Andy Huang46dfba62012-04-19 01:47:32 -07001178 }
Andy Huang9a8bc1e2012-10-23 19:48:25 -07001179 }
1180 });
Andy Huang46dfba62012-04-19 01:47:32 -07001181 }
1182
1183 @SuppressWarnings("unused")
Mindy Pereira974c9662012-09-14 10:02:08 -07001184 @JavascriptInterface
Andy Huang46dfba62012-04-19 01:47:32 -07001185 public String getTempMessageBodies() {
1186 try {
1187 if (!mViewsCreated) {
1188 return "";
Andy Huangf70fc402012-02-17 15:37:42 -08001189 }
Andy Huang46dfba62012-04-19 01:47:32 -07001190
1191 final String s = mTempBodiesHtml;
1192 mTempBodiesHtml = null;
1193 return s;
1194 } catch (Throwable t) {
1195 LogUtils.e(LOG_TAG, t, "Error in MailJsBridge.getTempMessageBodies");
1196 return "";
1197 }
Andy Huangf70fc402012-02-17 15:37:42 -08001198 }
1199
Andy Huang014ea4c2012-09-25 14:50:54 -07001200 @SuppressWarnings("unused")
1201 @JavascriptInterface
1202 public String getMessageBody(String domId) {
1203 try {
1204 final MessageCursor cursor = getMessageCursor();
1205 if (!mViewsCreated || cursor == null) {
1206 return "";
1207 }
1208
1209 int pos = -1;
1210 while (cursor.moveToPosition(++pos)) {
1211 final ConversationMessage msg = cursor.getMessage();
1212 if (TextUtils.equals(domId, mTemplates.getMessageDomId(msg))) {
1213 return msg.getBodyAsHtml();
1214 }
1215 }
1216
1217 return "";
1218
1219 } catch (Throwable t) {
1220 LogUtils.e(LOG_TAG, t, "Error in MailJsBridge.getMessageBody");
1221 return "";
1222 }
1223 }
1224
mindyp3bcf1802012-09-09 11:17:00 -07001225 @SuppressWarnings("unused")
Mindy Pereira974c9662012-09-14 10:02:08 -07001226 @JavascriptInterface
Andy Huang543e7092013-04-22 11:44:56 -07001227 public String getMessageSender(String domId) {
1228 try {
1229 final MessageCursor cursor = getMessageCursor();
1230 if (!mViewsCreated || cursor == null) {
1231 return "";
1232 }
1233
1234 int pos = -1;
1235 while (cursor.moveToPosition(++pos)) {
1236 final ConversationMessage msg = cursor.getMessage();
1237 if (TextUtils.equals(domId, mTemplates.getMessageDomId(msg))) {
1238 return getAddress(msg.getFrom()).getAddress();
1239 }
1240 }
1241
1242 return "";
1243
1244 } catch (Throwable t) {
1245 LogUtils.e(LOG_TAG, t, "Error in MailJsBridge.getMessageSender");
1246 return "";
1247 }
1248 }
1249
1250 @SuppressWarnings("unused")
1251 @JavascriptInterface
mindyp3bcf1802012-09-09 11:17:00 -07001252 public void onContentReady() {
Andrew Sapperstein376294b2013-06-06 16:04:26 -07001253 getHandler().post(new FragmentRunnable("onContentReady",
1254 ConversationViewFragment.this) {
Andy Huang9a8bc1e2012-10-23 19:48:25 -07001255 @Override
1256 public void go() {
1257 try {
Andy Huang63b3c672012-10-05 19:27:28 -07001258 if (mWebViewLoadStartMs != 0) {
1259 LogUtils.i(LOG_TAG, "IN CVF.onContentReady, f=%s vis=%s t=%sms",
1260 ConversationViewFragment.this,
1261 isUserVisible(),
1262 (SystemClock.uptimeMillis() - mWebViewLoadStartMs));
1263 }
Andy Huang7d4746e2012-10-17 17:03:17 -07001264 revealConversation();
Andy Huang9a8bc1e2012-10-23 19:48:25 -07001265 } catch (Throwable t) {
1266 LogUtils.e(LOG_TAG, t, "Error in MailJsBridge.onContentReady");
1267 // Still try to show the conversation.
1268 revealConversation();
mindyp3bcf1802012-09-09 11:17:00 -07001269 }
Andy Huang9a8bc1e2012-10-23 19:48:25 -07001270 }
1271 });
mindyp3bcf1802012-09-09 11:17:00 -07001272 }
Andy Huange964eee2012-10-02 19:24:58 -07001273
1274 @SuppressWarnings("unused")
1275 @JavascriptInterface
1276 public float getScrollYPercent() {
1277 try {
1278 return mWebViewYPercent;
1279 } catch (Throwable t) {
1280 LogUtils.e(LOG_TAG, t, "Error in MailJsBridge.getScrollYPercent");
1281 return 0f;
1282 }
1283 }
Andy Huang05c70c82013-03-14 15:15:50 -07001284
1285 @SuppressWarnings("unused")
1286 @JavascriptInterface
1287 public void onMessageTransform(String messageDomId, String transformText) {
Andrew Sappersteinae92e152013-05-03 13:55:18 -07001288 try {
1289 LogUtils.i(LOG_TAG, "TRANSFORM: (%s) %s", messageDomId, transformText);
1290 mMessageTransforms.put(messageDomId, transformText);
1291 onConversationTransformed();
1292 } catch (Throwable t) {
1293 LogUtils.e(LOG_TAG, t, "Error in MailJsBridge.onMessageTransform");
1294 return;
1295 }
Andy Huang05c70c82013-03-14 15:15:50 -07001296 }
Andy Huangf70fc402012-02-17 15:37:42 -08001297 }
1298
Andy Huang47aa9c92012-07-31 15:37:21 -07001299 private class NewMessagesInfo {
1300 int count;
Andy Huang06c03622012-10-22 18:59:45 -07001301 int countFromSelf;
Andy Huang47aa9c92012-07-31 15:37:21 -07001302 String senderAddress;
1303
1304 /**
1305 * Return the display text for the new message notification overlay. It will be formatted
1306 * appropriately for a single new message vs. multiple new messages.
1307 *
1308 * @return display text
1309 */
1310 public String getNotificationText() {
mindypad0c30d2012-09-25 12:09:13 -07001311 Resources res = getResources();
Andy Huang47aa9c92012-07-31 15:37:21 -07001312 if (count > 1) {
Andrew Sapperstein66d69112013-08-23 12:24:44 -07001313 return res.getQuantityString(R.plurals.new_incoming_messages_many, count, count);
Andy Huang47aa9c92012-07-31 15:37:21 -07001314 } else {
Andy Huang16174812012-08-16 16:40:35 -07001315 final Address addr = getAddress(senderAddress);
mindypad0c30d2012-09-25 12:09:13 -07001316 return res.getString(R.string.new_incoming_messages_one,
Andrew Sapperstein3af481c2013-10-30 10:29:38 -07001317 sBidiFormatter.unicodeWrap(TextUtils.isEmpty(addr.getName())
1318 ? addr.getAddress() : addr.getName()));
Andy Huang47aa9c92012-07-31 15:37:21 -07001319 }
Andy Huang47aa9c92012-07-31 15:37:21 -07001320 }
1321 }
1322
mindypf4fce122012-09-14 15:55:33 -07001323 @Override
Paul Westbrookc42ad5e2013-05-09 16:52:15 -07001324 public void onMessageCursorLoadFinished(Loader<ObjectCursor<ConversationMessage>> loader,
1325 MessageCursor newCursor, MessageCursor oldCursor) {
mindypf4fce122012-09-14 15:55:33 -07001326 /*
1327 * what kind of changes affect the MessageCursor? 1. new message(s) 2.
1328 * read/unread state change 3. deleted message, either regular or draft
1329 * 4. updated message, either from self or from others, updated in
1330 * content or state or sender 5. star/unstar of message (technically
1331 * similar to #1) 6. other label change Use MessageCursor.hashCode() to
1332 * sort out interesting vs. no-op cursor updates.
1333 */
Andy Huangb8331b42012-07-16 19:08:53 -07001334
Andy Huang233d4352012-10-18 14:00:24 -07001335 if (oldCursor != null && !oldCursor.isClosed()) {
Andy Huang014ea4c2012-09-25 14:50:54 -07001336 final NewMessagesInfo info = getNewIncomingMessagesInfo(newCursor);
Andy Huangb8331b42012-07-16 19:08:53 -07001337
Andy Huang014ea4c2012-09-25 14:50:54 -07001338 if (info.count > 0) {
1339 // don't immediately render new incoming messages from other
1340 // senders
1341 // (to avoid a new message from losing the user's focus)
1342 LogUtils.i(LOG_TAG, "CONV RENDER: conversation updated"
Andy Huang9d3fd922012-09-26 22:23:58 -07001343 + ", holding cursor for new incoming message (%s)", this);
Andy Huang014ea4c2012-09-25 14:50:54 -07001344 showNewMessageNotification(info);
1345 return;
1346 }
1347
Andy Huang06c03622012-10-22 18:59:45 -07001348 final int oldState = oldCursor.getStateHashCode();
1349 final boolean changed = newCursor.getStateHashCode() != oldState;
Andy Huang233d4352012-10-18 14:00:24 -07001350
Andy Huang014ea4c2012-09-25 14:50:54 -07001351 if (!changed) {
1352 final boolean processedInPlace = processInPlaceUpdates(newCursor, oldCursor);
1353 if (processedInPlace) {
Andy Huang9d3fd922012-09-26 22:23:58 -07001354 LogUtils.i(LOG_TAG, "CONV RENDER: processed update(s) in place (%s)", this);
Andy Huang1ee96b22012-08-24 20:19:53 -07001355 } else {
mindypf4fce122012-09-14 15:55:33 -07001356 LogUtils.i(LOG_TAG, "CONV RENDER: uninteresting update"
Andy Huang9d3fd922012-09-26 22:23:58 -07001357 + ", ignoring this conversation update (%s)", this);
Andy Huang1ee96b22012-08-24 20:19:53 -07001358 }
Andy Huangb8331b42012-07-16 19:08:53 -07001359 return;
Andy Huang06c03622012-10-22 18:59:45 -07001360 } else if (info.countFromSelf == 1) {
1361 // Special-case the very common case of a new cursor that is the same as the old
1362 // one, except that there is a new message from yourself. This happens upon send.
1363 final boolean sameExceptNewLast = newCursor.getStateHashCode(1) == oldState;
1364 if (sameExceptNewLast) {
1365 LogUtils.i(LOG_TAG, "CONV RENDER: update is a single new message from self"
1366 + " (%s)", this);
1367 newCursor.moveToLast();
1368 processNewOutgoingMessage(newCursor.getMessage());
1369 return;
1370 }
Andy Huangb8331b42012-07-16 19:08:53 -07001371 }
Andy Huang6766b6e2012-09-28 12:43:52 -07001372 // cursors are different, and not due to an incoming message. fall
1373 // through and render.
1374 LogUtils.i(LOG_TAG, "CONV RENDER: conversation updated"
1375 + ", but not due to incoming message. rendering. (%s)", this);
Andy Huang06c03622012-10-22 18:59:45 -07001376
1377 if (DEBUG_DUMP_CURSOR_CONTENTS) {
1378 LogUtils.i(LOG_TAG, "old cursor: %s", oldCursor.getDebugDump());
1379 LogUtils.i(LOG_TAG, "new cursor: %s", newCursor.getDebugDump());
1380 }
Andy Huang6766b6e2012-09-28 12:43:52 -07001381 } else {
1382 LogUtils.i(LOG_TAG, "CONV RENDER: initial render. (%s)", this);
Andy Huang243c2362013-03-01 17:50:35 -08001383 timerMark("message cursor load finished");
Andy Huangb8331b42012-07-16 19:08:53 -07001384 }
1385
Andrew Sapperstein606dbd72013-07-30 19:14:23 -07001386 renderContent(newCursor);
1387 }
1388
1389 protected void renderContent(MessageCursor messageCursor) {
Mark Wei4071c2f2012-09-26 14:38:38 -07001390 // if layout hasn't happened, delay render
1391 // This is needed in addition to the showConversation() delay to speed
1392 // up rotation and restoration.
1393 if (mConversationContainer.getWidth() == 0) {
1394 mNeedRender = true;
1395 mConversationContainer.addOnLayoutChangeListener(this);
1396 } else {
Andrew Sapperstein606dbd72013-07-30 19:14:23 -07001397 renderConversation(messageCursor);
Mark Wei4071c2f2012-09-26 14:38:38 -07001398 }
Andy Huangb8331b42012-07-16 19:08:53 -07001399 }
1400
mindypf4fce122012-09-14 15:55:33 -07001401 private NewMessagesInfo getNewIncomingMessagesInfo(MessageCursor newCursor) {
1402 final NewMessagesInfo info = new NewMessagesInfo();
Andy Huangb8331b42012-07-16 19:08:53 -07001403
mindypf4fce122012-09-14 15:55:33 -07001404 int pos = -1;
1405 while (newCursor.moveToPosition(++pos)) {
1406 final Message m = newCursor.getMessage();
1407 if (!mViewState.contains(m)) {
1408 LogUtils.i(LOG_TAG, "conversation diff: found new msg: %s", m.uri);
Andy Huangb8331b42012-07-16 19:08:53 -07001409
Scott Kennedy8960f0a2012-11-07 15:35:50 -08001410 final Address from = getAddress(m.getFrom());
mindypf4fce122012-09-14 15:55:33 -07001411 // distinguish ours from theirs
1412 // new messages from the account owner should not trigger a
1413 // notification
1414 if (mAccount.ownsFromAddress(from.getAddress())) {
1415 LogUtils.i(LOG_TAG, "found message from self: %s", m.uri);
Andy Huang06c03622012-10-22 18:59:45 -07001416 info.countFromSelf++;
mindypf4fce122012-09-14 15:55:33 -07001417 continue;
1418 }
Andy Huangb8331b42012-07-16 19:08:53 -07001419
mindypf4fce122012-09-14 15:55:33 -07001420 info.count++;
Scott Kennedy8960f0a2012-11-07 15:35:50 -08001421 info.senderAddress = m.getFrom();
Andy Huangb8331b42012-07-16 19:08:53 -07001422 }
Andy Huangb8331b42012-07-16 19:08:53 -07001423 }
mindypf4fce122012-09-14 15:55:33 -07001424 return info;
Andy Huangb8331b42012-07-16 19:08:53 -07001425 }
1426
Andy Huang014ea4c2012-09-25 14:50:54 -07001427 private boolean processInPlaceUpdates(MessageCursor newCursor, MessageCursor oldCursor) {
1428 final Set<String> idsOfChangedBodies = Sets.newHashSet();
Andy Huang6b3d0d92012-10-30 15:46:48 -07001429 final List<Integer> changedOverlayPositions = Lists.newArrayList();
1430
Andy Huang014ea4c2012-09-25 14:50:54 -07001431 boolean changed = false;
1432
1433 int pos = 0;
1434 while (true) {
1435 if (!newCursor.moveToPosition(pos) || !oldCursor.moveToPosition(pos)) {
1436 break;
1437 }
1438
1439 final ConversationMessage newMsg = newCursor.getMessage();
1440 final ConversationMessage oldMsg = oldCursor.getMessage();
1441
Scott Kennedy8960f0a2012-11-07 15:35:50 -08001442 if (!TextUtils.equals(newMsg.getFrom(), oldMsg.getFrom()) ||
Andy Huang2a1e8e32012-10-23 18:54:57 -07001443 newMsg.isSending != oldMsg.isSending) {
Andy Huang6b3d0d92012-10-30 15:46:48 -07001444 mAdapter.updateItemsForMessage(newMsg, changedOverlayPositions);
Andy Huang2a1e8e32012-10-23 18:54:57 -07001445 LogUtils.i(LOG_TAG, "msg #%d (%d): detected from/sending change. isSending=%s",
1446 pos, newMsg.id, newMsg.isSending);
Andy Huang014ea4c2012-09-25 14:50:54 -07001447 }
1448
1449 // update changed message bodies in-place
1450 if (!TextUtils.equals(newMsg.bodyHtml, oldMsg.bodyHtml) ||
1451 !TextUtils.equals(newMsg.bodyText, oldMsg.bodyText)) {
1452 // maybe just set a flag to notify JS to re-request changed bodies
1453 idsOfChangedBodies.add('"' + mTemplates.getMessageDomId(newMsg) + '"');
1454 LogUtils.i(LOG_TAG, "msg #%d (%d): detected body change", pos, newMsg.id);
1455 }
1456
1457 pos++;
1458 }
1459
Andy Huang6b3d0d92012-10-30 15:46:48 -07001460
1461 if (!changedOverlayPositions.isEmpty()) {
Andy Huang06c03622012-10-22 18:59:45 -07001462 // notify once after the entire adapter is updated
Andy Huang6b3d0d92012-10-30 15:46:48 -07001463 mConversationContainer.onOverlayModelUpdate(changedOverlayPositions);
1464 changed = true;
Andy Huang06c03622012-10-22 18:59:45 -07001465 }
1466
Andy Huang014ea4c2012-09-25 14:50:54 -07001467 if (!idsOfChangedBodies.isEmpty()) {
1468 mWebView.loadUrl(String.format("javascript:replaceMessageBodies([%s]);",
1469 TextUtils.join(",", idsOfChangedBodies)));
1470 changed = true;
1471 }
1472
1473 return changed;
1474 }
1475
Andy Huang06c03622012-10-22 18:59:45 -07001476 private void processNewOutgoingMessage(ConversationMessage msg) {
Andrew Sapperstein99ee4562013-08-22 16:19:42 -07001477 // if there are items in the adapter and the last item is a border,
1478 // make the last border no longer be the last border
1479 if (mAdapter.getCount() > 0) {
1480 final ConversationOverlayItem item = mAdapter.getItem(mAdapter.getCount() - 1);
1481 if (item.getType() == ConversationViewAdapter.VIEW_TYPE_BORDER) {
1482 ((BorderItem) item).setIsLastBorder(false);
1483 }
1484 }
Andrew Sappersteincee3c902013-07-31 10:52:02 -07001485
Andy Huang06c03622012-10-22 18:59:45 -07001486 mTemplates.reset();
1487 // this method will add some items to mAdapter, but we deliberately want to avoid notifying
1488 // adapter listeners (i.e. ConversationContainer) until onWebContentGeometryChange is next
1489 // called, to prevent N+1 headers rendering with N message bodies.
Andrew Sappersteincee3c902013-07-31 10:52:02 -07001490
1491 // We can just call previousCollapsed false here since the border
1492 // above the message we're about to render should always show
1493 // (which it also will since the message being render is expanded).
1494 renderMessage(msg, false /* previousCollapsed */, true /* expanded */,
1495 msg.alwaysShowImages, false /* renderBorder */, false /* firstBorder */);
1496 renderBorder(true /* contiguous */, true /* expanded */,
1497 false /* firstBorder */, true /* lastBorder */);
Andy Huang06c03622012-10-22 18:59:45 -07001498 mTempBodiesHtml = mTemplates.emit();
1499
1500 mViewState.setExpansionState(msg, ExpansionState.EXPANDED);
1501 // FIXME: should the provider set this as initial state?
1502 mViewState.setReadState(msg, false /* read */);
1503
Andy Huang91d782a2012-10-25 12:37:29 -07001504 // From now until the updated spacer geometry is returned, the adapter items are mismatched
1505 // with the existing spacers. Do not let them layout.
1506 mConversationContainer.invalidateSpacerGeometry();
1507
Andy Huang06c03622012-10-22 18:59:45 -07001508 mWebView.loadUrl("javascript:appendMessageHtml();");
1509 }
1510
Paul Westbrookcebcc642012-08-08 10:06:04 -07001511 private class SetCookieTask extends AsyncTask<Void, Void, Void> {
1512 final String mUri;
Paul Westbrookb8361c92012-09-27 10:57:14 -07001513 final Uri mAccountCookieQueryUri;
1514 final ContentResolver mResolver;
Paul Westbrookcebcc642012-08-08 10:06:04 -07001515
Paul Westbrookb8361c92012-09-27 10:57:14 -07001516 SetCookieTask(Context context, Uri baseUri, Uri accountCookieQueryUri) {
1517 mUri = baseUri.toString();
1518 mAccountCookieQueryUri = accountCookieQueryUri;
1519 mResolver = context.getContentResolver();
Paul Westbrookcebcc642012-08-08 10:06:04 -07001520 }
1521
1522 @Override
1523 public Void doInBackground(Void... args) {
Paul Westbrookb8361c92012-09-27 10:57:14 -07001524 // First query for the coookie string from the UI provider
1525 final Cursor cookieCursor = mResolver.query(mAccountCookieQueryUri,
1526 UIProvider.ACCOUNT_COOKIE_PROJECTION, null, null, null);
1527 if (cookieCursor == null) {
1528 return null;
1529 }
1530
1531 try {
1532 if (cookieCursor.moveToFirst()) {
1533 final String cookie = cookieCursor.getString(
1534 cookieCursor.getColumnIndex(UIProvider.AccountCookieColumns.COOKIE));
1535
1536 if (cookie != null) {
1537 final CookieSyncManager csm =
1538 CookieSyncManager.createInstance(getContext());
1539 CookieManager.getInstance().setCookie(mUri, cookie);
1540 csm.sync();
1541 }
1542 }
1543
1544 } finally {
1545 cookieCursor.close();
1546 }
1547
1548
Paul Westbrookcebcc642012-08-08 10:06:04 -07001549 return null;
1550 }
1551 }
mindyp36280f32012-09-09 16:11:23 -07001552
mindyp26d4d2d2012-09-18 17:30:32 -07001553 @Override
mindyp36280f32012-09-09 16:11:23 -07001554 public void onConversationUpdated(Conversation conv) {
1555 final ConversationViewHeader headerView = (ConversationViewHeader) mConversationContainer
1556 .findViewById(R.id.conversation_header);
mindypb2b98ba2012-09-24 14:13:58 -07001557 mConversation = conv;
mindyp9e0b2362012-09-09 16:31:21 -07001558 if (headerView != null) {
1559 headerView.onConversationUpdated(conv);
Vikram Aggarwal51ad9042013-01-17 13:18:09 -08001560 headerView.setSubject(conv.subject);
mindyp9e0b2362012-09-09 16:31:21 -07001561 }
mindyp36280f32012-09-09 16:11:23 -07001562 }
Mark Wei4071c2f2012-09-26 14:38:38 -07001563
1564 @Override
1565 public void onLayoutChange(View v, int left, int top, int right,
1566 int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
1567 boolean sizeChanged = mNeedRender
1568 && mConversationContainer.getWidth() != 0;
1569 if (sizeChanged) {
1570 mNeedRender = false;
1571 mConversationContainer.removeOnLayoutChangeListener(this);
1572 renderConversation(getMessageCursor());
1573 }
1574 }
mindyp1b3cc472012-09-27 11:32:59 -07001575
1576 @Override
1577 public void setMessageDetailsExpanded(MessageHeaderItem i, boolean expanded,
1578 int heightBefore) {
1579 mDiff = (expanded ? 1 : -1) * Math.abs(i.getHeight() - heightBefore);
1580 }
Andy Huang02f9d182012-11-28 22:38:02 -08001581
Andy Huangc1fb9a92013-02-11 13:09:12 -08001582 private class CssScaleInterceptor implements OnScaleGestureListener {
Andy Huang02f9d182012-11-28 22:38:02 -08001583
1584 private float getFocusXWebPx(ScaleGestureDetector detector) {
1585 return (detector.getFocusX() - mSideMarginPx) / mWebView.getInitialScale();
1586 }
1587
1588 private float getFocusYWebPx(ScaleGestureDetector detector) {
1589 return detector.getFocusY() / mWebView.getInitialScale();
1590 }
1591
1592 @Override
1593 public boolean onScale(ScaleGestureDetector detector) {
1594 mWebView.loadUrl(String.format("javascript:onScale(%s, %s, %s);",
1595 detector.getScaleFactor(), getFocusXWebPx(detector),
1596 getFocusYWebPx(detector)));
1597 return false;
1598 }
1599
1600 @Override
1601 public boolean onScaleBegin(ScaleGestureDetector detector) {
1602 mWebView.loadUrl(String.format("javascript:onScaleBegin(%s, %s);",
1603 getFocusXWebPx(detector), getFocusYWebPx(detector)));
1604 return true;
1605 }
1606
1607 @Override
1608 public void onScaleEnd(ScaleGestureDetector detector) {
1609 mWebView.loadUrl(String.format("javascript:onScaleEnd(%s, %s);",
1610 getFocusXWebPx(detector), getFocusYWebPx(detector)));
1611 }
1612
1613 }
Andrew Sapperstein5c1692a2013-09-16 11:56:13 -07001614
1615 protected void printConversation() {
Andrew Sapperstein234d3532013-10-29 14:54:04 -07001616 PrintUtils.printConversation(mActivity.getActivityContext(), getMessageCursor(),
1617 mAddressCache, mConversation.getBaseUri(mBaseUri), true /* useJavascript */);
Andrew Sapperstein5c1692a2013-09-16 11:56:13 -07001618 }
Mindy Pereira9b875682012-02-15 18:10:54 -08001619}