blob: 8ea082e4cae6b90c3f3b85b27e2ebf2b4370f76f [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
20import android.app.Activity;
21import android.app.Fragment;
Mindy Pereira8e915722012-02-16 14:42:56 -080022import android.app.LoaderManager;
Andy Huangaf5d4e02012-03-19 19:02:12 -070023import android.content.ActivityNotFoundException;
Mindy Pereira9b875682012-02-15 18:10:54 -080024import android.content.Context;
Mindy Pereira465d03d2012-02-16 15:17:46 -080025import android.content.CursorLoader;
Andy Huangaf5d4e02012-03-19 19:02:12 -070026import android.content.Intent;
Mindy Pereira8e915722012-02-16 14:42:56 -080027import android.content.Loader;
Mindy Pereira9b875682012-02-15 18:10:54 -080028import android.database.Cursor;
Andy Huangb8331b42012-07-16 19:08:53 -070029import android.database.DataSetObservable;
30import android.database.DataSetObserver;
Mindy Pereira465d03d2012-02-16 15:17:46 -080031import android.net.Uri;
Paul Westbrookcebcc642012-08-08 10:06:04 -070032import android.os.AsyncTask;
Mindy Pereira9b875682012-02-15 18:10:54 -080033import android.os.Bundle;
Andy Huangf70fc402012-02-17 15:37:42 -080034import android.os.Handler;
Andy Huangaf5d4e02012-03-19 19:02:12 -070035import android.provider.Browser;
Andy Huang47aa9c92012-07-31 15:37:21 -070036import android.text.TextUtils;
Mindy Pereira9b875682012-02-15 18:10:54 -080037import android.view.LayoutInflater;
Andy Huang5ff63742012-03-16 20:30:23 -070038import android.view.Menu;
39import android.view.MenuInflater;
40import android.view.MenuItem;
Mindy Pereira9b875682012-02-15 18:10:54 -080041import android.view.View;
42import android.view.ViewGroup;
Andy Huangf70fc402012-02-17 15:37:42 -080043import android.webkit.ConsoleMessage;
Paul Westbrookcebcc642012-08-08 10:06:04 -070044import android.webkit.CookieManager;
45import android.webkit.CookieSyncManager;
Andy Huangf70fc402012-02-17 15:37:42 -080046import android.webkit.WebChromeClient;
47import android.webkit.WebSettings;
Andy Huang17a9cde2012-03-09 18:03:16 -080048import android.webkit.WebView;
49import android.webkit.WebViewClient;
Andy Huang47aa9c92012-07-31 15:37:21 -070050import android.widget.TextView;
Mindy Pereira9b875682012-02-15 18:10:54 -080051
Andy Huangb8331b42012-07-16 19:08:53 -070052import com.android.mail.ContactInfo;
53import com.android.mail.ContactInfoSource;
Andy Huang59e0b182012-08-14 14:32:23 -070054import com.android.mail.FormattedDateBuilder;
Mindy Pereira9b875682012-02-15 18:10:54 -080055import com.android.mail.R;
Andy Huangb8331b42012-07-16 19:08:53 -070056import com.android.mail.SenderInfoLoader;
Andy Huang5ff63742012-03-16 20:30:23 -070057import com.android.mail.browse.ConversationContainer;
Andy Huang46dfba62012-04-19 01:47:32 -070058import com.android.mail.browse.ConversationOverlayItem;
Andy Huang7bdc3752012-03-25 17:18:19 -070059import com.android.mail.browse.ConversationViewAdapter;
Andy Huang46dfba62012-04-19 01:47:32 -070060import com.android.mail.browse.ConversationViewAdapter.MessageFooterItem;
Andy Huang7bdc3752012-03-25 17:18:19 -070061import com.android.mail.browse.ConversationViewAdapter.MessageHeaderItem;
Andy Huang46dfba62012-04-19 01:47:32 -070062import com.android.mail.browse.ConversationViewAdapter.SuperCollapsedBlockItem;
Andy Huang5ff63742012-03-16 20:30:23 -070063import com.android.mail.browse.ConversationViewHeader;
64import com.android.mail.browse.ConversationWebView;
Andy Huang7bdc3752012-03-25 17:18:19 -070065import com.android.mail.browse.MessageCursor;
Andy Huang839ada22012-07-20 15:48:40 -070066import com.android.mail.browse.MessageCursor.ConversationMessage;
Andy Huangcd5c5ee2012-08-12 19:03:51 -070067import com.android.mail.browse.MessageCursor.ConversationController;
Andy Huang59e0b182012-08-14 14:32:23 -070068import com.android.mail.browse.MessageHeaderView;
Andy Huang3233bff2012-03-20 19:38:45 -070069import com.android.mail.browse.MessageHeaderView.MessageHeaderViewCallbacks;
Andy Huang46dfba62012-04-19 01:47:32 -070070import com.android.mail.browse.SuperCollapsedBlock;
Andy Huang0b7ed6f2012-07-25 19:23:26 -070071import com.android.mail.browse.WebViewContextMenu;
Mindy Pereira9b875682012-02-15 18:10:54 -080072import com.android.mail.providers.Account;
Vikram Aggarwal7c401b72012-08-13 16:43:47 -070073import com.android.mail.providers.AccountObserver;
Andy Huang65fe28f2012-04-06 18:08:53 -070074import com.android.mail.providers.Address;
Mindy Pereira9b875682012-02-15 18:10:54 -080075import com.android.mail.providers.Conversation;
Mindy Pereira863e4412012-03-23 12:21:38 -070076import com.android.mail.providers.Folder;
Paul Westbrook2d82d612012-03-07 09:21:14 -080077import com.android.mail.providers.ListParams;
Andy Huangf70fc402012-02-17 15:37:42 -080078import com.android.mail.providers.Message;
Vikram Aggarwal7d816002012-04-17 17:06:41 -070079import com.android.mail.providers.Settings;
Mindy Pereira9b875682012-02-15 18:10:54 -080080import com.android.mail.providers.UIProvider;
Mindy Pereira863e4412012-03-23 12:21:38 -070081import com.android.mail.providers.UIProvider.AccountCapabilities;
82import com.android.mail.providers.UIProvider.FolderCapabilities;
Andy Huangcd5c5ee2012-08-12 19:03:51 -070083import com.android.mail.ui.ConversationViewState.ExpansionState;
Paul Westbrookb334c902012-06-25 11:42:46 -070084import com.android.mail.utils.LogTag;
Mindy Pereira9b875682012-02-15 18:10:54 -080085import com.android.mail.utils.LogUtils;
Andy Huang2e9acfe2012-03-15 22:39:36 -070086import com.android.mail.utils.Utils;
Andy Huangb8331b42012-07-16 19:08:53 -070087import com.google.common.collect.ImmutableMap;
Andy Huang46dfba62012-04-19 01:47:32 -070088import com.google.common.collect.Lists;
Andy Huang65fe28f2012-04-06 18:08:53 -070089import com.google.common.collect.Maps;
Andy Huangb8331b42012-07-16 19:08:53 -070090import com.google.common.collect.Sets;
Andy Huang65fe28f2012-04-06 18:08:53 -070091
Andy Huang839ada22012-07-20 15:48:40 -070092import java.util.Arrays;
Andy Huang46dfba62012-04-19 01:47:32 -070093import java.util.List;
Andy Huang65fe28f2012-04-06 18:08:53 -070094import java.util.Map;
Andy Huangb8331b42012-07-16 19:08:53 -070095import java.util.Set;
Mindy Pereira9b875682012-02-15 18:10:54 -080096
Andy Huangf70fc402012-02-17 15:37:42 -080097
Mindy Pereira9b875682012-02-15 18:10:54 -080098/**
99 * The conversation view UI component.
100 */
Mindy Pereira8e915722012-02-16 14:42:56 -0800101public final class ConversationViewFragment extends Fragment implements
Andy Huang3233bff2012-03-20 19:38:45 -0700102 ConversationViewHeader.ConversationViewHeaderCallbacks,
Andy Huang46dfba62012-04-19 01:47:32 -0700103 MessageHeaderViewCallbacks,
Andy Huangcd5c5ee2012-08-12 19:03:51 -0700104 SuperCollapsedBlock.OnClickListener,
105 ConversationController {
Mindy Pereira8e915722012-02-16 14:42:56 -0800106
Paul Westbrookb334c902012-06-25 11:42:46 -0700107 private static final String LOG_TAG = LogTag.getLogTag();
Andy Huang632721e2012-04-11 16:57:26 -0700108 public static final String LAYOUT_TAG = "ConvLayout";
Mindy Pereira9b875682012-02-15 18:10:54 -0800109
Mindy Pereira8e915722012-02-16 14:42:56 -0800110 private static final int MESSAGE_LOADER_ID = 0;
Andy Huangb8331b42012-07-16 19:08:53 -0700111 private static final int CONTACT_LOADER_ID = 1;
Mindy Pereira9b875682012-02-15 18:10:54 -0800112
Mindy Pereira8e915722012-02-16 14:42:56 -0800113 private ControllableActivity mActivity;
Mindy Pereira9b875682012-02-15 18:10:54 -0800114
Andy Huangf70fc402012-02-17 15:37:42 -0800115 private Context mContext;
116
117 private Conversation mConversation;
Mindy Pereira9b875682012-02-15 18:10:54 -0800118
Andy Huangf70fc402012-02-17 15:37:42 -0800119 private ConversationContainer mConversationContainer;
Mindy Pereira9b875682012-02-15 18:10:54 -0800120
Andy Huangf70fc402012-02-17 15:37:42 -0800121 private Account mAccount;
Mindy Pereira9b875682012-02-15 18:10:54 -0800122
Andy Huangf70fc402012-02-17 15:37:42 -0800123 private ConversationWebView mWebView;
Mindy Pereira9b875682012-02-15 18:10:54 -0800124
Andy Huang47aa9c92012-07-31 15:37:21 -0700125 private View mNewMessageBar;
126
Andy Huangf70fc402012-02-17 15:37:42 -0800127 private HtmlConversationTemplates mTemplates;
128
129 private String mBaseUri;
130
131 private final Handler mHandler = new Handler();
132
133 private final MailJsBridge mJsBridge = new MailJsBridge();
134
Andy Huang17a9cde2012-03-09 18:03:16 -0800135 private final WebViewClient mWebViewClient = new ConversationWebViewClient();
136
Andy Huang7bdc3752012-03-25 17:18:19 -0700137 private ConversationViewAdapter mAdapter;
138 private MessageCursor mCursor;
Andy Huang51067132012-03-12 20:08:19 -0700139
140 private boolean mViewsCreated;
141
Andy Huang5ff63742012-03-16 20:30:23 -0700142 private MenuItem mChangeFoldersMenuItem;
143
Vikram Aggarwal7c401b72012-08-13 16:43:47 -0700144 private final AccountObserver mAccountObserver = new AccountObserver() {
145 @Override
146 public void onChanged(Account newAccount) {
147 mAccount = newAccount;
148 }
149 };
150
Andy Huang632721e2012-04-11 16:57:26 -0700151 /**
152 * Folder is used to help determine valid menu actions for this conversation.
153 */
Mindy Pereira863e4412012-03-23 12:21:38 -0700154 private Folder mFolder;
155
Andy Huang65fe28f2012-04-06 18:08:53 -0700156 private final Map<String, Address> mAddressCache = Maps.newHashMap();
157
Andy Huang46dfba62012-04-19 01:47:32 -0700158 /**
159 * Temporary string containing the message bodies of the messages within a super-collapsed
160 * block, for one-time use during block expansion. We cannot easily pass the body HTML
161 * into JS without problematic escaping, so hold onto it momentarily and signal JS to fetch it
162 * using {@link MailJsBridge}.
163 */
164 private String mTempBodiesHtml;
165
Andy Huang632721e2012-04-11 16:57:26 -0700166 private boolean mUserVisible;
167
168 private int mMaxAutoLoadMessages;
169
170 private boolean mDeferredConversationLoad;
171
Andy Huang839ada22012-07-20 15:48:40 -0700172 /**
Andy Huangc11011d2012-07-24 18:50:34 -0700173 * Handles a deferred 'mark read' operation, necessary when the conversation view has finished
174 * loading before the conversation cursor. Normally null unless this situation occurs.
175 * When finally able to 'mark read', this observer will also be unregistered and cleaned up.
176 */
177 private MarkReadObserver mMarkReadObserver;
178
179 /**
Andy Huang839ada22012-07-20 15:48:40 -0700180 * Parcelable state of the conversation view. Can safely be used without null checking any time
181 * after {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}.
182 */
183 private ConversationViewState mViewState;
184
Andy Huangb8331b42012-07-16 19:08:53 -0700185 private final MessageLoaderCallbacks mMessageLoaderCallbacks = new MessageLoaderCallbacks();
186 private final ContactLoaderCallbacks mContactLoaderCallbacks = new ContactLoaderCallbacks();
187
Andy Huangf70fc402012-02-17 15:37:42 -0800188 private static final String ARG_ACCOUNT = "account";
Andy Huang632721e2012-04-11 16:57:26 -0700189 public static final String ARG_CONVERSATION = "conversation";
Mindy Pereira863e4412012-03-23 12:21:38 -0700190 private static final String ARG_FOLDER = "folder";
Andy Huang839ada22012-07-20 15:48:40 -0700191 private static final String BUNDLE_VIEW_STATE = "viewstate";
Andy Huangf70fc402012-02-17 15:37:42 -0800192
Andy Huangbd544e32012-05-29 15:56:51 -0700193 private static final boolean DEBUG_DUMP_CONVERSATION_HTML = false;
Andy Huang47aa9c92012-07-31 15:37:21 -0700194 private static final boolean DISABLE_OFFSCREEN_LOADING = false;
Andy Huangbd544e32012-05-29 15:56:51 -0700195
Vikram Aggarwal6c511582012-02-27 10:59:47 -0800196 /**
197 * Constructor needs to be public to handle orientation changes and activity lifecycle events.
198 */
Andy Huangf70fc402012-02-17 15:37:42 -0800199 public ConversationViewFragment() {
Vikram Aggarwal6c511582012-02-27 10:59:47 -0800200 super();
Mindy Pereira9b875682012-02-15 18:10:54 -0800201 }
202
203 /**
204 * Creates a new instance of {@link ConversationViewFragment}, initialized
Andy Huang632721e2012-04-11 16:57:26 -0700205 * to display a conversation with other parameters inherited/copied from an existing bundle,
206 * typically one created using {@link #makeBasicArgs}.
207 */
208 public static ConversationViewFragment newInstance(Bundle existingArgs,
209 Conversation conversation) {
210 ConversationViewFragment f = new ConversationViewFragment();
211 Bundle args = new Bundle(existingArgs);
212 args.putParcelable(ARG_CONVERSATION, conversation);
213 f.setArguments(args);
214 return f;
215 }
216
217 public static Bundle makeBasicArgs(Account account, Folder folder) {
218 Bundle args = new Bundle();
219 args.putParcelable(ARG_ACCOUNT, account);
220 args.putParcelable(ARG_FOLDER, folder);
221 return args;
222 }
223
Mindy Pereira9b875682012-02-15 18:10:54 -0800224 @Override
225 public void onActivityCreated(Bundle savedInstanceState) {
Andy Huang632721e2012-04-11 16:57:26 -0700226 LogUtils.d(LOG_TAG, "IN CVF.onActivityCreated, this=%s subj=%s", this,
227 mConversation.subject);
Mindy Pereira9b875682012-02-15 18:10:54 -0800228 super.onActivityCreated(savedInstanceState);
229 // Strictly speaking, we get back an android.app.Activity from getActivity. However, the
230 // only activity creating a ConversationListContext is a MailActivity which is of type
231 // ControllableActivity, so this cast should be safe. If this cast fails, some other
232 // activity is creating ConversationListFragments. This activity must be of type
233 // ControllableActivity.
234 final Activity activity = getActivity();
Mindy Pereira863e4412012-03-23 12:21:38 -0700235 if (!(activity instanceof ControllableActivity)) {
Andy Huangf70fc402012-02-17 15:37:42 -0800236 LogUtils.wtf(LOG_TAG, "ConversationViewFragment expects only a ControllableActivity to"
237 + "create it. Cannot proceed.");
Mindy Pereira9b875682012-02-15 18:10:54 -0800238 }
239 mActivity = (ControllableActivity) activity;
Andy Huangf70fc402012-02-17 15:37:42 -0800240 mContext = mActivity.getApplicationContext();
Mindy Pereira9b875682012-02-15 18:10:54 -0800241 if (mActivity.isFinishing()) {
242 // Activity is finishing, just bail.
243 return;
244 }
Andy Huangf70fc402012-02-17 15:37:42 -0800245 mTemplates = new HtmlConversationTemplates(mContext);
Vikram Aggarwal7c401b72012-08-13 16:43:47 -0700246 mAccount = mAccountObserver.initialize(mActivity.getAccountController());
Andy Huang59e0b182012-08-14 14:32:23 -0700247
248 final FormattedDateBuilder dateBuilder = new FormattedDateBuilder(mContext);
249
Andy Huang7bdc3752012-03-25 17:18:19 -0700250 mAdapter = new ConversationViewAdapter(mActivity.getActivityContext(), mAccount,
Andy Huang59e0b182012-08-14 14:32:23 -0700251 getLoaderManager(), this, mContactLoaderCallbacks, this, this, mAddressCache,
252 dateBuilder);
Andy Huang51067132012-03-12 20:08:19 -0700253 mConversationContainer.setOverlayAdapter(mAdapter);
254
Andy Huang59e0b182012-08-14 14:32:23 -0700255 // set up snap header (the adapter usually does this with the other ones)
256 final MessageHeaderView snapHeader = mConversationContainer.getSnapHeader();
257 snapHeader.initialize(dateBuilder, mAccount, mAddressCache);
258 snapHeader.setCallbacks(this);
259 snapHeader.setContactInfoSource(mContactLoaderCallbacks);
260
Andy Huang632721e2012-04-11 16:57:26 -0700261 mMaxAutoLoadMessages = getResources().getInteger(R.integer.max_auto_load_messages);
262
Andy Huang0b7ed6f2012-07-25 19:23:26 -0700263 mWebView.setOnCreateContextMenuListener(new WebViewContextMenu(activity));
264
Mindy Pereira9b875682012-02-15 18:10:54 -0800265 showConversation();
Paul Westbrookcebcc642012-08-08 10:06:04 -0700266
267 if (mConversation.conversationBaseUri != null &&
268 !TextUtils.isEmpty(mConversation.conversationCookie)) {
269 // Set the cookie for this base url
270 new SetCookieTask(mConversation.conversationBaseUri.toString(),
271 mConversation.conversationCookie).execute();
272 }
Mindy Pereira9b875682012-02-15 18:10:54 -0800273 }
274
275 @Override
276 public void onCreate(Bundle savedState) {
Andy Huang632721e2012-04-11 16:57:26 -0700277 LogUtils.d(LOG_TAG, "onCreate in ConversationViewFragment (this=%s)", this);
Mindy Pereira9b875682012-02-15 18:10:54 -0800278 super.onCreate(savedState);
Andy Huangf70fc402012-02-17 15:37:42 -0800279
Vikram Aggarwalf76bf4c2012-08-01 11:21:05 -0700280 final Bundle args = getArguments();
Andy Huangf70fc402012-02-17 15:37:42 -0800281 mAccount = args.getParcelable(ARG_ACCOUNT);
282 mConversation = args.getParcelable(ARG_CONVERSATION);
Mindy Pereira863e4412012-03-23 12:21:38 -0700283 mFolder = args.getParcelable(ARG_FOLDER);
Paul Westbrookcebcc642012-08-08 10:06:04 -0700284 // Since the uri specified in the conversation base uri may not be unique, we specify a
285 // base uri that us guaranteed to be unique for this conversation.
286 mBaseUri = "x-thread://" + mAccount.name + "/" + mConversation.id;
Andy Huang5ff63742012-03-16 20:30:23 -0700287
Vikram Aggarwald503df42012-05-11 10:13:35 -0700288 // Not really, we just want to get a crack to store a reference to the change_folder item
Andy Huang5ff63742012-03-16 20:30:23 -0700289 setHasOptionsMenu(true);
Mindy Pereira9b875682012-02-15 18:10:54 -0800290 }
291
292 @Override
293 public View onCreateView(LayoutInflater inflater,
294 ViewGroup container, Bundle savedInstanceState) {
Andy Huang839ada22012-07-20 15:48:40 -0700295
296 if (savedInstanceState != null) {
297 mViewState = savedInstanceState.getParcelable(BUNDLE_VIEW_STATE);
298 } else {
299 mViewState = new ConversationViewState();
300 }
301
Andy Huang632721e2012-04-11 16:57:26 -0700302 View rootView = inflater.inflate(R.layout.conversation_view, container, false);
Andy Huangf70fc402012-02-17 15:37:42 -0800303 mConversationContainer = (ConversationContainer) rootView
304 .findViewById(R.id.conversation_container);
Andy Huang47aa9c92012-07-31 15:37:21 -0700305
306 mNewMessageBar = mConversationContainer.findViewById(R.id.new_message_notification_bar);
307 mNewMessageBar.setOnClickListener(new View.OnClickListener() {
308 @Override
309 public void onClick(View v) {
310 onNewMessageBarClick();
311 }
312 });
313
Andy Huang5ff63742012-03-16 20:30:23 -0700314 mWebView = (ConversationWebView) mConversationContainer.findViewById(R.id.webview);
Andy Huangf70fc402012-02-17 15:37:42 -0800315
Andy Huangf70fc402012-02-17 15:37:42 -0800316 mWebView.addJavascriptInterface(mJsBridge, "mail");
Andy Huang17a9cde2012-03-09 18:03:16 -0800317 mWebView.setWebViewClient(mWebViewClient);
Andy Huangf70fc402012-02-17 15:37:42 -0800318 mWebView.setWebChromeClient(new WebChromeClient() {
319 @Override
320 public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
321 LogUtils.i(LOG_TAG, "JS: %s (%s:%d)", consoleMessage.message(),
322 consoleMessage.sourceId(), consoleMessage.lineNumber());
323 return true;
324 }
325 });
Andy Huang23014702012-07-09 12:50:36 -0700326 mWebView.setContentSizeChangeListener(new ConversationWebView.ContentSizeChangeListener() {
327 @Override
328 public void onHeightChange(int h) {
329 // When WebKit says the DOM height has changed, re-measure bodies and re-position
330 // their headers.
331 // This is separate from the typical JavaScript DOM change listeners because
332 // cases like NARROW_COLUMNS text reflow do not trigger DOM events.
333 mWebView.loadUrl("javascript:measurePositions();");
334 }
335 });
Andy Huangf70fc402012-02-17 15:37:42 -0800336
Andy Huang3233bff2012-03-20 19:38:45 -0700337 final WebSettings settings = mWebView.getSettings();
Andy Huangf70fc402012-02-17 15:37:42 -0800338
339 settings.setJavaScriptEnabled(true);
340 settings.setUseWideViewPort(true);
Andy Huang23014702012-07-09 12:50:36 -0700341 settings.setLoadWithOverviewMode(true);
Andy Huangf70fc402012-02-17 15:37:42 -0800342
343 settings.setSupportZoom(true);
344 settings.setBuiltInZoomControls(true);
345 settings.setDisplayZoomControls(false);
Mindy Pereira9b875682012-02-15 18:10:54 -0800346
Andy Huangc319b552012-04-25 19:53:50 -0700347 final float fontScale = getResources().getConfiguration().fontScale;
Andy Huangba283732012-06-25 19:14:10 -0700348 final int desiredFontSizePx = getResources()
349 .getInteger(R.integer.conversation_desired_font_size_px);
350 final int unstyledFontSizePx = getResources()
351 .getInteger(R.integer.conversation_unstyled_font_size_px);
Andy Huangc319b552012-04-25 19:53:50 -0700352
Andy Huangba283732012-06-25 19:14:10 -0700353 int textZoom = settings.getTextZoom();
354 // apply a correction to the default body text style to get regular text to the size we want
355 textZoom = textZoom * desiredFontSizePx / unstyledFontSizePx;
356 // then apply any system font scaling
Andy Huangc319b552012-04-25 19:53:50 -0700357 textZoom = (int) (textZoom * fontScale);
358 settings.setTextZoom(textZoom);
359
Andy Huang51067132012-03-12 20:08:19 -0700360 mViewsCreated = true;
361
Mindy Pereira9b875682012-02-15 18:10:54 -0800362 return rootView;
363 }
364
365 @Override
Andy Huang839ada22012-07-20 15:48:40 -0700366 public void onSaveInstanceState(Bundle outState) {
367 if (mViewState != null) {
368 outState.putParcelable(BUNDLE_VIEW_STATE, mViewState);
369 }
370 }
371
372 @Override
Mindy Pereira9b875682012-02-15 18:10:54 -0800373 public void onDestroyView() {
Mindy Pereira9b875682012-02-15 18:10:54 -0800374 super.onDestroyView();
Andy Huang46dfba62012-04-19 01:47:32 -0700375 mConversationContainer.setOverlayAdapter(null);
376 mAdapter = null;
Andy Huangc11011d2012-07-24 18:50:34 -0700377 if (mMarkReadObserver != null) {
378 mActivity.getConversationUpdater().unregisterConversationListObserver(
379 mMarkReadObserver);
380 mMarkReadObserver = null;
381 }
Andy Huang51067132012-03-12 20:08:19 -0700382 mViewsCreated = false;
Vikram Aggarwal7c401b72012-08-13 16:43:47 -0700383 mAccountObserver.unregisterAndDestroy();
Mindy Pereira9b875682012-02-15 18:10:54 -0800384 }
385
Andy Huang5ff63742012-03-16 20:30:23 -0700386 @Override
387 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
388 super.onCreateOptionsMenu(menu, inflater);
389
Vikram Aggarwald503df42012-05-11 10:13:35 -0700390 mChangeFoldersMenuItem = menu.findItem(R.id.change_folder);
Andy Huang5ff63742012-03-16 20:30:23 -0700391 }
392
Andy Huang5a929072012-03-23 20:17:10 -0700393 @Override
Mindy Pereirac9d59182012-03-22 16:06:46 -0700394 public void onPrepareOptionsMenu(Menu menu) {
395 super.onPrepareOptionsMenu(menu);
Paul Westbrook76b20622012-07-12 11:45:43 -0700396 final boolean showMarkImportant = !mConversation.isImportant();
Andy Huang991f4532012-08-14 13:32:55 -0700397 Utils.setMenuItemVisibility(menu, R.id.mark_important, showMarkImportant
398 && mAccount.supportsCapability(UIProvider.AccountCapabilities.MARK_IMPORTANT));
399 Utils.setMenuItemVisibility(menu, R.id.mark_not_important, !showMarkImportant
400 && mAccount.supportsCapability(UIProvider.AccountCapabilities.MARK_IMPORTANT));
mindypeccde432012-08-20 08:24:48 -0700401 Utils.setMenuItemVisibility(menu, R.id.delete,
402 mFolder != null && mFolder.supportsCapability(
403 UIProvider.FolderCapabilities.DELETE));
Andy Huang991f4532012-08-14 13:32:55 -0700404 final boolean archiveVisible = mAccount.supportsCapability(AccountCapabilities.ARCHIVE)
Mindy Pereirab68e4ae2012-08-17 09:23:44 -0700405 && mFolder != null && mFolder.supportsCapability(FolderCapabilities.ARCHIVE)
406 && !mFolder.isTrash();
Andy Huang991f4532012-08-14 13:32:55 -0700407 Utils.setMenuItemVisibility(menu, R.id.archive, archiveVisible);
Mindy Pereira01f30502012-08-14 10:30:51 -0700408 Utils.setMenuItemVisibility(menu, R.id.remove_folder, !archiveVisible && mFolder != null
Mindy Pereirab68e4ae2012-08-17 09:23:44 -0700409 && mFolder.supportsCapability(FolderCapabilities.CAN_ACCEPT_MOVED_MESSAGES)
410 && !mFolder.isProviderFolder());
Andy Huang7bf339a2012-08-14 14:34:43 -0700411 final MenuItem removeFolder = menu.findItem(R.id.remove_folder);
412 if (removeFolder != null) {
413 removeFolder.setTitle(getString(R.string.remove_folder, mFolder.name));
414 }
Mindy Pereira863e4412012-03-23 12:21:38 -0700415 Utils.setMenuItemVisibility(menu, R.id.report_spam,
416 mAccount.supportsCapability(AccountCapabilities.REPORT_SPAM) && mFolder != null
417 && mFolder.supportsCapability(FolderCapabilities.REPORT_SPAM)
418 && !mConversation.spam);
Paul Westbrook77eee622012-07-10 13:41:57 -0700419 Utils.setMenuItemVisibility(menu, R.id.mark_not_spam,
420 mAccount.supportsCapability(AccountCapabilities.REPORT_SPAM) && mFolder != null
421 && mFolder.supportsCapability(FolderCapabilities.MARK_NOT_SPAM)
422 && mConversation.spam);
Paul Westbrook76b20622012-07-12 11:45:43 -0700423 Utils.setMenuItemVisibility(menu, R.id.report_phishing,
424 mAccount.supportsCapability(AccountCapabilities.REPORT_PHISHING) && mFolder != null
425 && mFolder.supportsCapability(FolderCapabilities.REPORT_PHISHING)
426 && !mConversation.phishing);
Andy Huang991f4532012-08-14 13:32:55 -0700427 Utils.setMenuItemVisibility(menu, R.id.mute,
428 mAccount.supportsCapability(AccountCapabilities.MUTE) && mFolder != null
Mindy Pereira863e4412012-03-23 12:21:38 -0700429 && mFolder.supportsCapability(FolderCapabilities.DESTRUCTIVE_MUTE)
430 && !mConversation.muted);
Mindy Pereirac9d59182012-03-22 16:06:46 -0700431 }
Andy Huang632721e2012-04-11 16:57:26 -0700432
Andy Huang839ada22012-07-20 15:48:40 -0700433 @Override
434 public boolean onOptionsItemSelected(MenuItem item) {
435 boolean handled = false;
436
437 switch (item.getItemId()) {
438 case R.id.inside_conversation_unread:
439 markUnread();
440 handled = true;
441 break;
442 }
443
444 return handled;
445 }
446
Andy Huangcd5c5ee2012-08-12 19:03:51 -0700447 @Override
448 public ConversationUpdater getListController() {
449 final ControllableActivity activity = (ControllableActivity) getActivity();
450 return activity != null ? activity.getConversationUpdater() : null;
451 }
452
453 @Override
454 public MessageCursor getMessageCursor() {
455 return mCursor;
456 }
457
Andy Huang839ada22012-07-20 15:48:40 -0700458 private void markUnread() {
459 // Ignore unsafe calls made after a fragment is detached from an activity
460 final ControllableActivity activity = (ControllableActivity) getActivity();
461 if (activity == null) {
462 LogUtils.w(LOG_TAG, "ignoring markUnread for conv=%s", mConversation.id);
463 return;
464 }
465
Andy Huang28e31e22012-07-26 16:33:15 -0700466 if (mViewState == null) {
467 LogUtils.i(LOG_TAG, "ignoring markUnread for conv with no view state (%d)",
468 mConversation.id);
469 return;
470 }
Andy Huang839ada22012-07-20 15:48:40 -0700471 activity.getConversationUpdater().markConversationMessagesUnread(mConversation,
Vikram Aggarwal4a878b62012-07-31 15:09:25 -0700472 mViewState.getUnreadMessageUris(), mViewState.getConversationInfo());
Andy Huang839ada22012-07-20 15:48:40 -0700473 }
474
Andy Huang632721e2012-04-11 16:57:26 -0700475 /**
476 * {@link #setUserVisibleHint(boolean)} only works on API >= 15, so implement our own for
477 * reliability on older platforms.
478 */
479 public void setExtraUserVisibleHint(boolean isVisibleToUser) {
480 LogUtils.v(LOG_TAG, "in CVF.setHint, val=%s (%s)", isVisibleToUser, this);
481
482 if (mUserVisible != isVisibleToUser) {
483 mUserVisible = isVisibleToUser;
484
485 if (isVisibleToUser && mViewsCreated) {
486
487 if (mCursor == null && mDeferredConversationLoad) {
488 // load
489 LogUtils.v(LOG_TAG, "Fragment is now user-visible, showing conversation: %s",
490 mConversation.uri);
491 showConversation();
492 mDeferredConversationLoad = false;
493 } else {
494 onConversationSeen();
495 }
496
497 }
498 }
499 }
500
Mindy Pereira9b875682012-02-15 18:10:54 -0800501 /**
502 * Handles a request to show a new conversation list, either from a search query or for viewing
Mindy Pereira30fd47b2012-03-09 09:24:00 -0800503 * a folder. This will initiate a data load, and hence must be called on the UI thread.
Mindy Pereira9b875682012-02-15 18:10:54 -0800504 */
505 private void showConversation() {
Andy Huangf98bc892012-08-07 18:22:09 -0700506 final boolean disableOffscreenLoading = DISABLE_OFFSCREEN_LOADING ||
507 (mConversation.isRemote || mConversation.getNumMessages() > mMaxAutoLoadMessages);
Andy Huang47aa9c92012-07-31 15:37:21 -0700508 if (!mUserVisible && disableOffscreenLoading) {
Andy Huang632721e2012-04-11 16:57:26 -0700509 LogUtils.v(LOG_TAG, "Fragment not user-visible, not showing conversation: %s",
510 mConversation.uri);
511 mDeferredConversationLoad = true;
512 return;
513 }
514 LogUtils.v(LOG_TAG,
515 "Fragment is short or user-visible, immediately rendering conversation: %s",
516 mConversation.uri);
Andy Huangb8331b42012-07-16 19:08:53 -0700517 getLoaderManager().initLoader(MESSAGE_LOADER_ID, Bundle.EMPTY, mMessageLoaderCallbacks);
Andy Huang5460ce42012-08-16 19:38:27 -0700518
519 if (mUserVisible) {
520 final SubjectDisplayChanger sdc = mActivity.getSubjectDisplayChanger();
521 if (sdc != null) {
522 sdc.setSubject(mConversation.subject);
523 }
524 }
Mindy Pereira8e915722012-02-16 14:42:56 -0800525 }
526
Andy Huang632721e2012-04-11 16:57:26 -0700527 public Conversation getConversation() {
528 return mConversation;
529 }
530
Andy Huang51067132012-03-12 20:08:19 -0700531 private void renderConversation(MessageCursor messageCursor) {
Andy Huangbd544e32012-05-29 15:56:51 -0700532 final String convHtml = renderMessageBodies(messageCursor);
533
534 if (DEBUG_DUMP_CONVERSATION_HTML) {
535 java.io.FileWriter fw = null;
536 try {
537 fw = new java.io.FileWriter("/sdcard/conv" + mConversation.id
538 + ".html");
539 fw.write(convHtml);
540 } catch (java.io.IOException e) {
541 e.printStackTrace();
542 } finally {
543 if (fw != null) {
544 try {
545 fw.close();
546 } catch (java.io.IOException e) {
547 e.printStackTrace();
548 }
549 }
550 }
551 }
552
553 mWebView.loadDataWithBaseURL(mBaseUri, convHtml, "text/html", "utf-8", null);
Andy Huang7bdc3752012-03-25 17:18:19 -0700554 mCursor = messageCursor;
Andy Huang51067132012-03-12 20:08:19 -0700555 }
556
Andy Huang7bdc3752012-03-25 17:18:19 -0700557 /**
558 * Populate the adapter with overlay views (message headers, super-collapsed blocks, a
559 * conversation header), and return an HTML document with spacer divs inserted for all overlays.
560 *
561 */
Andy Huangf70fc402012-02-17 15:37:42 -0800562 private String renderMessageBodies(MessageCursor messageCursor) {
563 int pos = -1;
Andy Huang632721e2012-04-11 16:57:26 -0700564
565 LogUtils.d(LOG_TAG, "IN renderMessageBodies, fragment=%s subj=%s", this,
566 mConversation.subject);
Andy Huang7bdc3752012-03-25 17:18:19 -0700567 boolean allowNetworkImages = false;
568
Andy Huangc7543572012-04-03 15:34:29 -0700569 // TODO: re-use any existing adapter item state (expanded, details expanded, show pics)
Vikram Aggarwal7c401b72012-08-13 16:43:47 -0700570 final Settings settings = mAccount.settings;
Vikram Aggarwal7d816002012-04-17 17:06:41 -0700571 if (settings != null) {
572 mAdapter.setDefaultReplyAll(settings.replyBehavior ==
573 UIProvider.DefaultReplyBehavior.REPLY_ALL);
574 }
Andy Huang7bdc3752012-03-25 17:18:19 -0700575 // Walk through the cursor and build up an overlay adapter as you go.
576 // Each overlay has an entry in the adapter for easy scroll handling in the container.
577 // Items are not necessarily 1:1 in cursor and adapter because of super-collapsed blocks.
578 // When adding adapter items, also add their heights to help the container later determine
579 // overlay dimensions.
580
581 mAdapter.clear();
582
Andy Huang47aa9c92012-07-31 15:37:21 -0700583 // re-evaluate the message parts of the view state, since the messages may have changed
584 // since the previous render
585 final ConversationViewState prevState = mViewState;
586 mViewState = new ConversationViewState(prevState);
587
Andy Huang5ff63742012-03-16 20:30:23 -0700588 // N.B. the units of height for spacers are actually dp and not px because WebView assumes
Andy Huang2e9acfe2012-03-15 22:39:36 -0700589 // a pixel is an mdpi pixel, unless you set device-dpi.
Andy Huang5ff63742012-03-16 20:30:23 -0700590
Andy Huang7bdc3752012-03-25 17:18:19 -0700591 // add a single conversation header item
592 final int convHeaderPos = mAdapter.addConversationHeader(mConversation);
Andy Huang23014702012-07-09 12:50:36 -0700593 final int convHeaderPx = measureOverlayHeight(convHeaderPos);
Andy Huang5ff63742012-03-16 20:30:23 -0700594
Andy Huang23014702012-07-09 12:50:36 -0700595 mTemplates.startConversation(mWebView.screenPxToWebPx(convHeaderPx));
Andy Huang3233bff2012-03-20 19:38:45 -0700596
Andy Huang46dfba62012-04-19 01:47:32 -0700597 int collapsedStart = -1;
Andy Huang839ada22012-07-20 15:48:40 -0700598 ConversationMessage prevCollapsedMsg = null;
Andy Huang46dfba62012-04-19 01:47:32 -0700599 boolean prevSafeForImages = false;
600
Andy Huangf70fc402012-02-17 15:37:42 -0800601 while (messageCursor.moveToPosition(++pos)) {
Andy Huang839ada22012-07-20 15:48:40 -0700602 final ConversationMessage msg = messageCursor.getMessage();
Andy Huang46dfba62012-04-19 01:47:32 -0700603
Andy Huang3233bff2012-03-20 19:38:45 -0700604 // TODO: save/restore 'show pics' state
605 final boolean safeForImages = msg.alwaysShowImages /* || savedStateSaysSafe */;
606 allowNetworkImages |= safeForImages;
Andy Huang24055282012-03-27 17:37:06 -0700607
Paul Westbrook08098ec2012-08-12 15:30:28 -0700608 final Integer savedExpanded = prevState.getExpansionState(msg);
609 final int expandedState;
Andy Huang839ada22012-07-20 15:48:40 -0700610 if (savedExpanded != null) {
Paul Westbrook08098ec2012-08-12 15:30:28 -0700611 expandedState = savedExpanded;
Andy Huang839ada22012-07-20 15:48:40 -0700612 } else {
Andy Huangcd5c5ee2012-08-12 19:03:51 -0700613 // new messages that are not expanded default to being eligible for super-collapse
Paul Westbrook08098ec2012-08-12 15:30:28 -0700614 expandedState = (!msg.read || msg.starred || messageCursor.isLast()) ?
Andy Huangcd5c5ee2012-08-12 19:03:51 -0700615 ExpansionState.EXPANDED : ExpansionState.SUPER_COLLAPSED;
Andy Huang839ada22012-07-20 15:48:40 -0700616 }
Paul Westbrook08098ec2012-08-12 15:30:28 -0700617 mViewState.setExpansionState(msg, expandedState);
Andy Huangc7543572012-04-03 15:34:29 -0700618
Andy Huang839ada22012-07-20 15:48:40 -0700619 // save off "read" state from the cursor
620 // later, the view may not match the cursor (e.g. conversation marked read on open)
Andy Huang423bea22012-08-21 12:00:49 -0700621 // however, if a previous state indicated this message was unread, trust that instead
622 // so "mark unread" marks all originally unread messages
623 mViewState.setReadState(msg, msg.read && !prevState.isUnread(msg));
Andy Huang839ada22012-07-20 15:48:40 -0700624
Andy Huangcd5c5ee2012-08-12 19:03:51 -0700625 // We only want to consider this for inclusion in the super collapsed block if
626 // 1) The we don't have previous state about this message (The first time that the
627 // user opens a conversation)
628 // 2) The previously saved state for this message indicates that this message is
629 // in the super collapsed block.
630 if (ExpansionState.isSuperCollapsed(expandedState)) {
631 // contribute to a super-collapsed block that will be emitted just before the
632 // next expanded header
633 if (collapsedStart < 0) {
634 collapsedStart = pos;
Andy Huang46dfba62012-04-19 01:47:32 -0700635 }
Andy Huangcd5c5ee2012-08-12 19:03:51 -0700636 prevCollapsedMsg = msg;
637 prevSafeForImages = safeForImages;
638 continue;
Andy Huang46dfba62012-04-19 01:47:32 -0700639 }
Andy Huang24055282012-03-27 17:37:06 -0700640
Andy Huang46dfba62012-04-19 01:47:32 -0700641 // resolve any deferred decisions on previous collapsed items
642 if (collapsedStart >= 0) {
643 if (pos - collapsedStart == 1) {
644 // special-case for a single collapsed message: no need to super-collapse it
645 renderMessage(prevCollapsedMsg, false /* expanded */,
646 prevSafeForImages);
647 } else {
648 renderSuperCollapsedBlock(collapsedStart, pos - 1);
649 }
650 prevCollapsedMsg = null;
651 collapsedStart = -1;
652 }
Andy Huang7bdc3752012-03-25 17:18:19 -0700653
Paul Westbrook08098ec2012-08-12 15:30:28 -0700654 renderMessage(msg, ExpansionState.isExpanded(expandedState), safeForImages);
Mindy Pereira9b875682012-02-15 18:10:54 -0800655 }
Andy Huang3233bff2012-03-20 19:38:45 -0700656
657 mWebView.getSettings().setBlockNetworkImage(!allowNetworkImages);
658
Paul Westbrookcebcc642012-08-08 10:06:04 -0700659 // If the conversation has specified a base uri, use it here, use mBaseUri
660 final String conversationBaseUri = mConversation.conversationBaseUri != null ?
661 mConversation.conversationBaseUri.toString() : mBaseUri;
662 return mTemplates.endConversation(mBaseUri, conversationBaseUri, 320,
663 mWebView.getViewportWidth());
Mindy Pereira9b875682012-02-15 18:10:54 -0800664 }
Mindy Pereira674afa42012-02-17 14:05:24 -0800665
Andy Huang46dfba62012-04-19 01:47:32 -0700666 private void renderSuperCollapsedBlock(int start, int end) {
667 final int blockPos = mAdapter.addSuperCollapsedBlock(start, end);
Andy Huang23014702012-07-09 12:50:36 -0700668 final int blockPx = measureOverlayHeight(blockPos);
669 mTemplates.appendSuperCollapsedHtml(start, mWebView.screenPxToWebPx(blockPx));
Andy Huang46dfba62012-04-19 01:47:32 -0700670 }
671
Andy Huang839ada22012-07-20 15:48:40 -0700672 private void renderMessage(ConversationMessage msg, boolean expanded,
673 boolean safeForImages) {
Andy Huang46dfba62012-04-19 01:47:32 -0700674 final int headerPos = mAdapter.addMessageHeader(msg, expanded);
675 final MessageHeaderItem headerItem = (MessageHeaderItem) mAdapter.getItem(headerPos);
676
677 final int footerPos = mAdapter.addMessageFooter(headerItem);
678
679 // Measure item header and footer heights to allocate spacers in HTML
680 // But since the views themselves don't exist yet, render each item temporarily into
681 // a host view for measurement.
Andy Huang23014702012-07-09 12:50:36 -0700682 final int headerPx = measureOverlayHeight(headerPos);
683 final int footerPx = measureOverlayHeight(footerPos);
Andy Huang46dfba62012-04-19 01:47:32 -0700684
Andy Huang23014702012-07-09 12:50:36 -0700685 mTemplates.appendMessageHtml(msg, expanded, safeForImages, 1.0f,
686 mWebView.screenPxToWebPx(headerPx), mWebView.screenPxToWebPx(footerPx));
Andy Huang46dfba62012-04-19 01:47:32 -0700687 }
688
689 private String renderCollapsedHeaders(MessageCursor cursor,
690 SuperCollapsedBlockItem blockToReplace) {
691 final List<ConversationOverlayItem> replacements = Lists.newArrayList();
692
693 mTemplates.reset();
694
695 for (int i = blockToReplace.getStart(), end = blockToReplace.getEnd(); i <= end; i++) {
696 cursor.moveToPosition(i);
Andy Huang839ada22012-07-20 15:48:40 -0700697 final ConversationMessage msg = cursor.getMessage();
Andy Huang46dfba62012-04-19 01:47:32 -0700698 final MessageHeaderItem header = mAdapter.newMessageHeaderItem(msg,
699 false /* expanded */);
700 final MessageFooterItem footer = mAdapter.newMessageFooterItem(header);
701
Andy Huang23014702012-07-09 12:50:36 -0700702 final int headerPx = measureOverlayHeight(header);
703 final int footerPx = measureOverlayHeight(footer);
Andy Huang46dfba62012-04-19 01:47:32 -0700704
705 mTemplates.appendMessageHtml(msg, false /* expanded */, msg.alwaysShowImages, 1.0f,
Andy Huang23014702012-07-09 12:50:36 -0700706 mWebView.screenPxToWebPx(headerPx), mWebView.screenPxToWebPx(footerPx));
Andy Huang46dfba62012-04-19 01:47:32 -0700707 replacements.add(header);
708 replacements.add(footer);
Andy Huang839ada22012-07-20 15:48:40 -0700709
Paul Westbrook08098ec2012-08-12 15:30:28 -0700710 mViewState.setExpansionState(msg, ExpansionState.COLLAPSED);
Andy Huang46dfba62012-04-19 01:47:32 -0700711 }
712
713 mAdapter.replaceSuperCollapsedBlock(blockToReplace, replacements);
714
715 return mTemplates.emit();
716 }
717
718 private int measureOverlayHeight(int position) {
719 return measureOverlayHeight(mAdapter.getItem(position));
720 }
721
Andy Huang7bdc3752012-03-25 17:18:19 -0700722 /**
Andy Huangb8331b42012-07-16 19:08:53 -0700723 * Measure the height of an adapter view by rendering an adapter item into a temporary
Andy Huang46dfba62012-04-19 01:47:32 -0700724 * host view, and asking the view to immediately measure itself. This method will reuse
Andy Huang7bdc3752012-03-25 17:18:19 -0700725 * a previous adapter view from {@link ConversationContainer}'s scrap views if one was generated
726 * earlier.
727 * <p>
Andy Huang46dfba62012-04-19 01:47:32 -0700728 * After measuring the height, this method also saves the height in the
729 * {@link ConversationOverlayItem} for later use in overlay positioning.
Andy Huang7bdc3752012-03-25 17:18:19 -0700730 *
Andy Huang46dfba62012-04-19 01:47:32 -0700731 * @param convItem adapter item with data to render and measure
Andy Huang23014702012-07-09 12:50:36 -0700732 * @return height of the rendered view in screen px
Andy Huang7bdc3752012-03-25 17:18:19 -0700733 */
Andy Huang46dfba62012-04-19 01:47:32 -0700734 private int measureOverlayHeight(ConversationOverlayItem convItem) {
Andy Huang7bdc3752012-03-25 17:18:19 -0700735 final int type = convItem.getType();
736
737 final View convertView = mConversationContainer.getScrapView(type);
Andy Huangb8331b42012-07-16 19:08:53 -0700738 final View hostView = mAdapter.getView(convItem, convertView, mConversationContainer,
739 true /* measureOnly */);
Andy Huang7bdc3752012-03-25 17:18:19 -0700740 if (convertView == null) {
741 mConversationContainer.addScrapView(type, hostView);
742 }
743
Andy Huang9875bb42012-04-04 20:36:21 -0700744 final int heightPx = mConversationContainer.measureOverlay(hostView);
Andy Huang7bdc3752012-03-25 17:18:19 -0700745 convItem.setHeight(heightPx);
Andy Huang9875bb42012-04-04 20:36:21 -0700746 convItem.markMeasurementValid();
Andy Huang7bdc3752012-03-25 17:18:19 -0700747
Andy Huang23014702012-07-09 12:50:36 -0700748 return heightPx;
Andy Huang7bdc3752012-03-25 17:18:19 -0700749 }
750
Andy Huang632721e2012-04-11 16:57:26 -0700751 private void onConversationSeen() {
Andy Huang5895f7b2012-06-01 17:07:20 -0700752 // Ignore unsafe calls made after a fragment is detached from an activity
753 final ControllableActivity activity = (ControllableActivity) getActivity();
754 if (activity == null) {
755 LogUtils.w(LOG_TAG, "ignoring onConversationSeen for conv=%s", mConversation.id);
756 return;
757 }
758
Andy Huangcd5c5ee2012-08-12 19:03:51 -0700759 mViewState.setInfoForConversation(mConversation);
Andy Huangc11011d2012-07-24 18:50:34 -0700760
Andy Huang423bea22012-08-21 12:00:49 -0700761 // mark viewed/read if not previously marked viewed by this conversation view,
762 // or if unread messages still exist in the message list cursor
Andy Huangcd5c5ee2012-08-12 19:03:51 -0700763 // we don't want to keep marking viewed on rotation or restore
Andy Huang423bea22012-08-21 12:00:49 -0700764 // but we do want future re-renders to mark read (e.g. "New message from X" case)
765 if (!mConversation.isViewed() || (mCursor != null && !mCursor.isConversationRead())) {
Vikram Aggarwal66bc2aa2012-08-02 10:47:03 -0700766 final ConversationUpdater listController = activity.getConversationUpdater();
767 // The conversation cursor may not have finished loading by now (when launched via
768 // notification), so watch for when it finishes and mark it read then.
769 if (listController.getConversationListCursor() == null) {
770 LogUtils.i(LOG_TAG, "deferring conv mark read on open for id=%d",
771 mConversation.id);
772 mMarkReadObserver = new MarkReadObserver(listController);
773 listController.registerConversationListObserver(mMarkReadObserver);
774 } else {
Andy Huangcd5c5ee2012-08-12 19:03:51 -0700775 markReadOnSeen(listController);
Andy Huang7e854f52012-07-24 11:35:49 -0700776 }
Andy Huang632721e2012-04-11 16:57:26 -0700777 }
778
Andy Huang5895f7b2012-06-01 17:07:20 -0700779 activity.onConversationSeen(mConversation);
780
781 final SubjectDisplayChanger sdc = activity.getSubjectDisplayChanger();
782 if (sdc != null) {
783 sdc.setSubject(mConversation.subject);
Andy Huang632721e2012-04-11 16:57:26 -0700784 }
785 }
786
Andy Huangcd5c5ee2012-08-12 19:03:51 -0700787 private void markReadOnSeen(ConversationUpdater listController) {
788 // Mark the conversation viewed and read.
789 listController.markConversationsRead(Arrays.asList(mConversation), true /* read */,
790 true /* viewed */);
791
792 // and update the Message objects in the cursor so the next time a cursor update happens
793 // with these messages marked read, we know to ignore it
794 if (mCursor != null) {
795 mCursor.markMessagesRead();
796 }
797 }
798
Andy Huang3233bff2012-03-20 19:38:45 -0700799 // BEGIN conversation header callbacks
Andy Huang5ff63742012-03-16 20:30:23 -0700800 @Override
801 public void onFoldersClicked() {
802 if (mChangeFoldersMenuItem == null) {
803 LogUtils.e(LOG_TAG, "unable to open 'change folders' dialog for a conversation");
804 return;
805 }
806 mActivity.onOptionsItemSelected(mChangeFoldersMenuItem);
807 }
808
809 @Override
810 public void onConversationViewHeaderHeightChange(int newHeight) {
811 // TODO: propagate the new height to the header's HTML spacer. This can happen when labels
812 // are added/removed
813 }
814
815 @Override
816 public String getSubjectRemainder(String subject) {
Andy Huang5895f7b2012-06-01 17:07:20 -0700817 final SubjectDisplayChanger sdc = mActivity.getSubjectDisplayChanger();
818 if (sdc == null) {
819 return subject;
820 }
821 return sdc.getUnshownSubject(subject);
Andy Huang5ff63742012-03-16 20:30:23 -0700822 }
Andy Huang3233bff2012-03-20 19:38:45 -0700823 // END conversation header callbacks
824
825 // START message header callbacks
826 @Override
Andy Huangc7543572012-04-03 15:34:29 -0700827 public void setMessageSpacerHeight(MessageHeaderItem item, int newSpacerHeightPx) {
828 mConversationContainer.invalidateSpacerGeometry();
829
830 // update message HTML spacer height
Andy Huang23014702012-07-09 12:50:36 -0700831 final int h = mWebView.screenPxToWebPx(newSpacerHeightPx);
832 LogUtils.i(LAYOUT_TAG, "setting HTML spacer h=%dwebPx (%dscreenPx)", h,
833 newSpacerHeightPx);
Andy Huangc7543572012-04-03 15:34:29 -0700834 mWebView.loadUrl(String.format("javascript:setMessageHeaderSpacerHeight('%s', %d);",
Andy Huang23014702012-07-09 12:50:36 -0700835 mTemplates.getMessageDomId(item.message), h));
Andy Huang3233bff2012-03-20 19:38:45 -0700836 }
837
838 @Override
Andy Huangc7543572012-04-03 15:34:29 -0700839 public void setMessageExpanded(MessageHeaderItem item, int newSpacerHeightPx) {
840 mConversationContainer.invalidateSpacerGeometry();
841
842 // show/hide the HTML message body and update the spacer height
Andy Huang23014702012-07-09 12:50:36 -0700843 final int h = mWebView.screenPxToWebPx(newSpacerHeightPx);
844 LogUtils.i(LAYOUT_TAG, "setting HTML spacer expanded=%s h=%dwebPx (%dscreenPx)",
845 item.isExpanded(), h, newSpacerHeightPx);
Andy Huangc7543572012-04-03 15:34:29 -0700846 mWebView.loadUrl(String.format("javascript:setMessageBodyVisible('%s', %s, %d);",
Andy Huang23014702012-07-09 12:50:36 -0700847 mTemplates.getMessageDomId(item.message), item.isExpanded(), h));
Andy Huang839ada22012-07-20 15:48:40 -0700848
Paul Westbrook08098ec2012-08-12 15:30:28 -0700849 mViewState.setExpansionState(item.message,
850 item.isExpanded() ? ExpansionState.EXPANDED : ExpansionState.COLLAPSED);
Andy Huang3233bff2012-03-20 19:38:45 -0700851 }
852
853 @Override
854 public void showExternalResources(Message msg) {
855 mWebView.getSettings().setBlockNetworkImage(false);
856 mWebView.loadUrl("javascript:unblockImages('" + mTemplates.getMessageDomId(msg) + "');");
857 }
858 // END message header callbacks
Andy Huang5ff63742012-03-16 20:30:23 -0700859
Andy Huang46dfba62012-04-19 01:47:32 -0700860 @Override
861 public void onSuperCollapsedClick(SuperCollapsedBlockItem item) {
862 if (mCursor == null || !mViewsCreated) {
863 return;
864 }
865
866 mTempBodiesHtml = renderCollapsedHeaders(mCursor, item);
867 mWebView.loadUrl("javascript:replaceSuperCollapsedBlock(" + item.getStart() + ")");
868 }
869
Andy Huang47aa9c92012-07-31 15:37:21 -0700870 private void showNewMessageNotification(NewMessagesInfo info) {
871 final TextView descriptionView = (TextView) mNewMessageBar.findViewById(
872 R.id.new_message_description);
873 descriptionView.setText(info.getNotificationText());
874 mNewMessageBar.setVisibility(View.VISIBLE);
875 }
876
877 private void onNewMessageBarClick() {
878 mNewMessageBar.setVisibility(View.GONE);
879
Andy Huangcd5c5ee2012-08-12 19:03:51 -0700880 renderConversation(mCursor); // mCursor is already up-to-date per onLoadFinished()
Andy Huang47aa9c92012-07-31 15:37:21 -0700881 }
882
Andy Huang5fbda022012-02-28 18:22:03 -0800883 private static class MessageLoader extends CursorLoader {
Paul Westbrook2d82d612012-03-07 09:21:14 -0800884 private boolean mDeliveredFirstResults = false;
Andy Huang839ada22012-07-20 15:48:40 -0700885 private final Conversation mConversation;
Andy Huangcd5c5ee2012-08-12 19:03:51 -0700886 private final ConversationController mController;
Andy Huang5fbda022012-02-28 18:22:03 -0800887
Andy Huangcd5c5ee2012-08-12 19:03:51 -0700888 public MessageLoader(Context c, Conversation conv, ConversationController controller) {
Andy Huang839ada22012-07-20 15:48:40 -0700889 super(c, conv.messageListUri, UIProvider.MESSAGE_PROJECTION, null, null, null);
890 mConversation = conv;
Andy Huangcd5c5ee2012-08-12 19:03:51 -0700891 mController = controller;
Andy Huang5fbda022012-02-28 18:22:03 -0800892 }
893
894 @Override
895 public Cursor loadInBackground() {
Andy Huangcd5c5ee2012-08-12 19:03:51 -0700896 return new MessageCursor(super.loadInBackground(), mConversation, mController);
Andy Huang5fbda022012-02-28 18:22:03 -0800897 }
Paul Westbrook2d82d612012-03-07 09:21:14 -0800898
899 @Override
900 public void deliverResult(Cursor result) {
901 // We want to deliver these results, and then we want to make sure that any subsequent
902 // queries do not hit the network
903 super.deliverResult(result);
904
905 if (!mDeliveredFirstResults) {
906 mDeliveredFirstResults = true;
907 Uri uri = getUri();
908
909 // Create a ListParams that tells the provider to not hit the network
910 final ListParams listParams =
911 new ListParams(ListParams.NO_LIMIT, false /* useNetwork */);
912
913 // Build the new uri with this additional parameter
914 uri = uri.buildUpon().appendQueryParameter(
915 UIProvider.LIST_PARAMS_QUERY_PARAMETER, listParams.serialize()).build();
916 setUri(uri);
917 }
918 }
Andy Huang5fbda022012-02-28 18:22:03 -0800919 }
920
Andy Huangb5078b22012-03-05 19:52:29 -0800921 private static int[] parseInts(final String[] stringArray) {
922 final int len = stringArray.length;
923 final int[] ints = new int[len];
924 for (int i = 0; i < len; i++) {
925 ints[i] = Integer.parseInt(stringArray[i]);
926 }
927 return ints;
928 }
929
Andy Huang47aa9c92012-07-31 15:37:21 -0700930 @Override
931 public String toString() {
932 // log extra info at DEBUG level or finer
933 final String s = super.toString();
934 if (!LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG) || mConversation == null) {
935 return s;
936 }
937 return "(" + s + " subj=" + mConversation.subject + ")";
938 }
939
Andy Huang16174812012-08-16 16:40:35 -0700940 private Address getAddress(String rawFrom) {
941 Address addr = mAddressCache.get(rawFrom);
942 if (addr == null) {
943 addr = Address.getEmailAddress(rawFrom);
944 mAddressCache.put(rawFrom, addr);
945 }
946 return addr;
947 }
948
Andy Huang17a9cde2012-03-09 18:03:16 -0800949 private class ConversationWebViewClient extends WebViewClient {
950
951 @Override
952 public void onPageFinished(WebView view, String url) {
Andy Huangde56e972012-07-26 18:23:08 -0700953 // Ignore unsafe calls made after a fragment is detached from an activity
954 final ControllableActivity activity = (ControllableActivity) getActivity();
955 if (activity == null || !mViewsCreated) {
956 LogUtils.i(LOG_TAG, "ignoring CVF.onPageFinished, url=%s fragment=%s", url,
Andy Huangb95da852012-07-18 14:16:58 -0700957 ConversationViewFragment.this);
958 return;
959 }
960
Andy Huang47aa9c92012-07-31 15:37:21 -0700961 LogUtils.i(LOG_TAG, "IN CVF.onPageFinished, url=%s fragment=%s act=%s", url,
962 ConversationViewFragment.this, getActivity());
Andy Huang632721e2012-04-11 16:57:26 -0700963
Andy Huang17a9cde2012-03-09 18:03:16 -0800964 super.onPageFinished(view, url);
965
966 // TODO: save off individual message unread state (here, or in onLoadFinished?) so
967 // 'mark unread' restores the original unread state for each individual message
968
Andy Huang632721e2012-04-11 16:57:26 -0700969 if (mUserVisible) {
970 onConversationSeen();
Andy Huang51067132012-03-12 20:08:19 -0700971 }
Andy Huangb8331b42012-07-16 19:08:53 -0700972
973 final Set<String> emailAddresses = Sets.newHashSet();
974 for (Address addr : mAddressCache.values()) {
975 emailAddresses.add(addr.getAddress());
976 }
977 mContactLoaderCallbacks.setSenders(emailAddresses);
978 getLoaderManager().restartLoader(CONTACT_LOADER_ID, Bundle.EMPTY,
979 mContactLoaderCallbacks);
Andy Huang17a9cde2012-03-09 18:03:16 -0800980 }
981
Andy Huangaf5d4e02012-03-19 19:02:12 -0700982 @Override
983 public boolean shouldOverrideUrlLoading(WebView view, String url) {
Andy Huang46dfba62012-04-19 01:47:32 -0700984 final Activity activity = getActivity();
985 if (!mViewsCreated || activity == null) {
986 return false;
987 }
988
Andy Huangaf5d4e02012-03-19 19:02:12 -0700989 boolean result = false;
990 final Uri uri = Uri.parse(url);
991 Intent intent = new Intent(Intent.ACTION_VIEW, uri);
Andy Huang46dfba62012-04-19 01:47:32 -0700992 intent.putExtra(Browser.EXTRA_APPLICATION_ID, activity.getPackageName());
Andy Huangaf5d4e02012-03-19 19:02:12 -0700993 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
994
995 // FIXME: give provider a chance to customize url intents?
996 // Utils.addGoogleUriAccountIntentExtras(mContext, uri, mAccount, intent);
997
998 try {
Andy Huang46dfba62012-04-19 01:47:32 -0700999 activity.startActivity(intent);
Andy Huangaf5d4e02012-03-19 19:02:12 -07001000 result = true;
1001 } catch (ActivityNotFoundException ex) {
1002 // If no application can handle the URL, assume that the
1003 // caller can handle it.
1004 }
1005
1006 return result;
1007 }
1008
Andy Huang17a9cde2012-03-09 18:03:16 -08001009 }
1010
Andy Huangf70fc402012-02-17 15:37:42 -08001011 /**
1012 * NOTE: all public methods must be listed in the proguard flags so that they can be accessed
1013 * via reflection and not stripped.
1014 *
1015 */
1016 private class MailJsBridge {
1017
1018 @SuppressWarnings("unused")
Andy Huang7bdc3752012-03-25 17:18:19 -07001019 public void onWebContentGeometryChange(final String[] overlayBottomStrs) {
Andy Huang46dfba62012-04-19 01:47:32 -07001020 try {
1021 mHandler.post(new Runnable() {
1022 @Override
1023 public void run() {
1024 if (!mViewsCreated) {
1025 LogUtils.d(LOG_TAG, "ignoring webContentGeometryChange because views" +
1026 " are gone, %s", ConversationViewFragment.this);
1027 return;
1028 }
Andy Huang51067132012-03-12 20:08:19 -07001029
Andy Huang46dfba62012-04-19 01:47:32 -07001030 mConversationContainer.onGeometryChange(parseInts(overlayBottomStrs));
1031 }
1032 });
1033 } catch (Throwable t) {
1034 LogUtils.e(LOG_TAG, t, "Error in MailJsBridge.onWebContentGeometryChange");
1035 }
1036 }
1037
1038 @SuppressWarnings("unused")
1039 public String getTempMessageBodies() {
1040 try {
1041 if (!mViewsCreated) {
1042 return "";
Andy Huangf70fc402012-02-17 15:37:42 -08001043 }
Andy Huang46dfba62012-04-19 01:47:32 -07001044
1045 final String s = mTempBodiesHtml;
1046 mTempBodiesHtml = null;
1047 return s;
1048 } catch (Throwable t) {
1049 LogUtils.e(LOG_TAG, t, "Error in MailJsBridge.getTempMessageBodies");
1050 return "";
1051 }
Andy Huangf70fc402012-02-17 15:37:42 -08001052 }
1053
1054 }
1055
Andy Huang47aa9c92012-07-31 15:37:21 -07001056 private class NewMessagesInfo {
1057 int count;
1058 String senderAddress;
1059
1060 /**
1061 * Return the display text for the new message notification overlay. It will be formatted
1062 * appropriately for a single new message vs. multiple new messages.
1063 *
1064 * @return display text
1065 */
1066 public String getNotificationText() {
1067 final Object param;
1068 if (count > 1) {
1069 param = count;
1070 } else {
Andy Huang16174812012-08-16 16:40:35 -07001071 final Address addr = getAddress(senderAddress);
Andy Huang47aa9c92012-07-31 15:37:21 -07001072 param = TextUtils.isEmpty(addr.getName()) ? addr.getAddress() : addr.getName();
1073 }
1074 return getResources().getQuantityString(R.plurals.new_incoming_messages, count, param);
1075 }
1076 }
1077
Andy Huangb8331b42012-07-16 19:08:53 -07001078 private class MessageLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> {
1079
1080 @Override
1081 public Loader<Cursor> onCreateLoader(int id, Bundle args) {
Andy Huangcd5c5ee2012-08-12 19:03:51 -07001082 return new MessageLoader(mContext, mConversation, ConversationViewFragment.this);
Andy Huangb8331b42012-07-16 19:08:53 -07001083 }
1084
1085 @Override
1086 public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
1087 MessageCursor messageCursor = (MessageCursor) data;
1088
1089 // ignore truly duplicate results
1090 // this can happen when restoring after rotation
1091 if (mCursor == messageCursor) {
1092 return;
1093 }
1094
Andy Huang47aa9c92012-07-31 15:37:21 -07001095 if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) {
1096 LogUtils.d(LOG_TAG, "LOADED CONVERSATION= %s", messageCursor.getDebugDump());
1097 }
1098
1099 // ignore cursors that are still loading results
1100 if (!messageCursor.isLoaded()) {
Andy Huangb8331b42012-07-16 19:08:53 -07001101 return;
1102 }
1103
Andy Huang47aa9c92012-07-31 15:37:21 -07001104 // TODO: handle ERROR status
1105
1106 if (messageCursor.getCount() == 0 && mCursor != null) {
1107 // TODO: need to exit this view- conversation may have been deleted, or for
1108 // whatever reason is now invalid (e.g. discard single draft)
1109 return;
1110 }
1111
Andy Huangcd5c5ee2012-08-12 19:03:51 -07001112 /*
1113 * what kind of changes affect the MessageCursor?
1114 * 1. new message(s)
1115 * 2. read/unread state change
1116 * 3. deleted message, either regular or draft
1117 * 4. updated message, either from self or from others, updated in content or state
1118 * or sender
1119 * 5. star/unstar of message (technically similar to #1)
1120 * 6. other label change
1121 *
1122 * Use MessageCursor.hashCode() to sort out interesting vs. no-op cursor updates.
1123 */
Andy Huang47aa9c92012-07-31 15:37:21 -07001124
1125 if (mCursor == null) {
Andy Huangcd5c5ee2012-08-12 19:03:51 -07001126 LogUtils.i(LOG_TAG, "CONV RENDER: existing cursor is null, rendering from scratch");
Andy Huang47aa9c92012-07-31 15:37:21 -07001127 } else {
Andy Huangcd5c5ee2012-08-12 19:03:51 -07001128 final NewMessagesInfo info = getNewIncomingMessagesInfo(messageCursor);
1129
1130 if (info.count > 0 || messageCursor.hashCode() == mCursor.hashCode()) {
1131
1132 if (info.count > 0) {
1133 // don't immediately render new incoming messages from other senders
1134 // (to avoid a new message from losing the user's focus)
1135 LogUtils.i(LOG_TAG, "CONV RENDER: conversation updated"
1136 + ", holding cursor for new incoming message");
1137 showNewMessageNotification(info);
1138 } else {
1139 LogUtils.i(LOG_TAG, "CONV RENDER: uninteresting update"
1140 + ", ignoring this conversation update");
1141 }
1142
1143 // update mCursor reference because the old one is about to be closed by
1144 // CursorLoader
1145 mCursor = messageCursor;
1146 return;
1147 }
1148
1149 // cursors are different, and not due to an incoming message. fall through and
1150 // render.
1151 LogUtils.i(LOG_TAG, "CONV RENDER: conversation updated"
1152 + ", but not due to incoming message. rendering.");
Andy Huang47aa9c92012-07-31 15:37:21 -07001153 }
Andy Huangcd5c5ee2012-08-12 19:03:51 -07001154
Andy Huang47aa9c92012-07-31 15:37:21 -07001155 renderConversation(messageCursor);
1156
Andy Huangb8331b42012-07-16 19:08:53 -07001157 // TODO: if this is not user-visible, delay render until user-visible fragment is done.
1158 // This is needed in addition to the showConversation() delay to speed up rotation and
1159 // restoration.
Andy Huangb8331b42012-07-16 19:08:53 -07001160 }
1161
1162 @Override
1163 public void onLoaderReset(Loader<Cursor> loader) {
1164 mCursor = null;
Andy Huangb8331b42012-07-16 19:08:53 -07001165 }
1166
Andy Huang47aa9c92012-07-31 15:37:21 -07001167 private NewMessagesInfo getNewIncomingMessagesInfo(MessageCursor newCursor) {
1168 final NewMessagesInfo info = new NewMessagesInfo();
1169
1170 int pos = -1;
1171 while (newCursor.moveToPosition(++pos)) {
1172 final Message m = newCursor.getMessage();
1173 if (!mViewState.contains(m)) {
1174 LogUtils.i(LOG_TAG, "conversation diff: found new msg: %s", m.uri);
Andy Huang16174812012-08-16 16:40:35 -07001175
1176 final Address from = getAddress(m.from);
1177 // distinguish ours from theirs
1178 // new messages from the account owner should not trigger a notification
1179 if (mAccount.ownsFromAddress(from.getAddress())) {
1180 LogUtils.i(LOG_TAG, "found message from self: %s", m.uri);
1181 continue;
1182 }
1183
Andy Huang47aa9c92012-07-31 15:37:21 -07001184 info.count++;
1185 info.senderAddress = m.from;
1186 }
1187 }
1188 return info;
1189 }
1190
Andy Huangb8331b42012-07-16 19:08:53 -07001191 }
1192
1193 /**
1194 * Inner class to to asynchronously load contact data for all senders in the conversation,
1195 * and notify observers when the data is ready.
1196 *
1197 */
1198 private class ContactLoaderCallbacks implements ContactInfoSource,
1199 LoaderManager.LoaderCallbacks<ImmutableMap<String, ContactInfo>> {
1200
1201 private Set<String> mSenders;
1202 private ImmutableMap<String, ContactInfo> mContactInfoMap;
1203 private DataSetObservable mObservable = new DataSetObservable();
1204
1205 public void setSenders(Set<String> emailAddresses) {
1206 mSenders = emailAddresses;
1207 }
1208
1209 @Override
1210 public Loader<ImmutableMap<String, ContactInfo>> onCreateLoader(int id, Bundle args) {
1211 return new SenderInfoLoader(mContext, mSenders);
1212 }
1213
1214 @Override
1215 public void onLoadFinished(Loader<ImmutableMap<String, ContactInfo>> loader,
1216 ImmutableMap<String, ContactInfo> data) {
1217 mContactInfoMap = data;
1218 mObservable.notifyChanged();
1219 }
1220
1221 @Override
1222 public void onLoaderReset(Loader<ImmutableMap<String, ContactInfo>> loader) {
1223 }
1224
1225 @Override
1226 public ContactInfo getContactInfo(String email) {
1227 if (mContactInfoMap == null) {
1228 return null;
1229 }
1230 return mContactInfoMap.get(email);
1231 }
1232
1233 @Override
1234 public void registerObserver(DataSetObserver observer) {
1235 mObservable.registerObserver(observer);
1236 }
1237
1238 @Override
1239 public void unregisterObserver(DataSetObserver observer) {
1240 mObservable.unregisterObserver(observer);
1241 }
1242
1243 }
1244
Andy Huangc11011d2012-07-24 18:50:34 -07001245 private class MarkReadObserver extends DataSetObserver {
Andy Huangc11011d2012-07-24 18:50:34 -07001246 private final ConversationUpdater mListController;
1247
1248 private MarkReadObserver(ConversationUpdater listController) {
1249 mListController = listController;
1250 }
1251
1252 @Override
1253 public void onChanged() {
1254 if (mListController.getConversationListCursor() == null) {
1255 // nothing yet, keep watching
1256 return;
1257 }
1258 // done loading, safe to mark read now
1259 mListController.unregisterConversationListObserver(this);
1260 mMarkReadObserver = null;
1261 LogUtils.i(LOG_TAG, "running deferred conv mark read on open, id=%d", mConversation.id);
Andy Huangcd5c5ee2012-08-12 19:03:51 -07001262 markReadOnSeen(mListController);
Andy Huangc11011d2012-07-24 18:50:34 -07001263 }
1264 }
1265
Vikram Aggarwalf76bf4c2012-08-01 11:21:05 -07001266 @Override
1267 public Settings getSettings() {
1268 return mAccount.settings;
1269 }
1270
Paul Westbrookcebcc642012-08-08 10:06:04 -07001271 private class SetCookieTask extends AsyncTask<Void, Void, Void> {
1272 final String mUri;
1273 final String mCookie;
1274
1275 SetCookieTask(String uri, String cookie) {
1276 mUri = uri;
1277 mCookie = cookie;
1278 }
1279
1280 @Override
1281 public Void doInBackground(Void... args) {
1282 final CookieSyncManager csm =
1283 CookieSyncManager.createInstance(mContext);
1284 CookieManager.getInstance().setCookie(mUri, mCookie);
1285 csm.sync();
1286 return null;
1287 }
1288 }
Mindy Pereira9b875682012-02-15 18:10:54 -08001289}