blob: 2692da096cc15e8c30a78895c728f4f4b9efa3ce [file] [log] [blame]
Mindy Pereira8e9305e2011-12-13 14:25:04 -08001/**
2 * Copyright (c) 2011, Google Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Andy Huang30e2c242012-01-06 18:14:30 -080017package com.android.mail.compose;
Mindy Pereira8e9305e2011-12-13 14:25:04 -080018
Mindy Pereira326c6602012-01-04 15:32:42 -080019import android.app.ActionBar;
Andy Huang5c5fd572012-04-08 18:19:29 -070020import android.app.ActionBar.OnNavigationListener;
21import android.app.Activity;
Mindy Pereira82cc5662012-01-09 17:29:30 -080022import android.app.ActivityManager;
23import android.app.AlertDialog;
24import android.app.Dialog;
Mindy Pereirab199d172012-08-13 11:04:03 -070025import android.app.Fragment;
Mindy Pereirab199d172012-08-13 11:04:03 -070026import android.app.FragmentTransaction;
Mindy Pereira96a7f7a2012-07-09 16:51:06 -070027import android.app.LoaderManager;
Mindy Pereira6349a042012-01-04 11:25:01 -080028import android.content.ContentResolver;
Mindy Pereira82cc5662012-01-09 17:29:30 -080029import android.content.ContentValues;
Mindy Pereira6349a042012-01-04 11:25:01 -080030import android.content.Context;
Mindy Pereira96a7f7a2012-07-09 16:51:06 -070031import android.content.CursorLoader;
Mindy Pereira82cc5662012-01-09 17:29:30 -080032import android.content.DialogInterface;
Mindy Pereira6349a042012-01-04 11:25:01 -080033import android.content.Intent;
Mindy Pereira96a7f7a2012-07-09 16:51:06 -070034import android.content.Loader;
Mindy Pereira82cc5662012-01-09 17:29:30 -080035import android.content.pm.ActivityInfo;
Mindy Pereira7ed1c112012-01-18 10:59:25 -080036import android.database.Cursor;
Mindy Pereira6349a042012-01-04 11:25:01 -080037import android.net.Uri;
Mindy Pereira8e9305e2011-12-13 14:25:04 -080038import android.os.Bundle;
Mindy Pereira82cc5662012-01-09 17:29:30 -080039import android.os.Handler;
40import android.os.HandlerThread;
Paul Westbrookf97588b2012-03-20 11:11:37 -070041import android.os.Parcelable;
Mindy Pereira82cc5662012-01-09 17:29:30 -080042import android.provider.BaseColumns;
Mindy Pereira46ce0b12012-01-05 10:32:15 -080043import android.text.Editable;
Mindy Pereira82cc5662012-01-09 17:29:30 -080044import android.text.Html;
mindyped9c2f02012-10-12 10:02:08 -070045import android.text.SpannableString;
Mindy Pereira82cc5662012-01-09 17:29:30 -080046import android.text.Spanned;
Paul Westbrookc1827622012-01-06 11:27:12 -080047import android.text.TextUtils;
Mindy Pereira82cc5662012-01-09 17:29:30 -080048import android.text.TextWatcher;
Mindy Pereira46ce0b12012-01-05 10:32:15 -080049import android.text.util.Rfc822Token;
Mindy Pereirac17d0732011-12-29 10:46:19 -080050import android.text.util.Rfc822Tokenizer;
Mindy Pereira3cd4f402012-07-17 11:16:18 -070051import android.view.Gravity;
mindyp62d3ec72012-08-24 13:04:09 -070052import android.view.KeyEvent;
Mindy Pereira326c6602012-01-04 15:32:42 -080053import android.view.LayoutInflater;
Mindy Pereirab47f3e22011-12-13 14:25:04 -080054import android.view.Menu;
55import android.view.MenuInflater;
56import android.view.MenuItem;
Mindy Pereira8e9305e2011-12-13 14:25:04 -080057import android.view.View;
58import android.view.View.OnClickListener;
Andy Huang5c5fd572012-04-08 18:19:29 -070059import android.view.ViewGroup;
Mindy Pereiraeaea9f12012-01-10 15:05:27 -080060import android.view.inputmethod.BaseInputConnection;
mindyp62d3ec72012-08-24 13:04:09 -070061import android.view.inputmethod.EditorInfo;
Mindy Pereira326c6602012-01-04 15:32:42 -080062import android.widget.ArrayAdapter;
Mindy Pereira8e9305e2011-12-13 14:25:04 -080063import android.widget.Button;
Mindy Pereira433b1982012-04-03 11:53:07 -070064import android.widget.EditText;
Mindy Pereira6349a042012-01-04 11:25:01 -080065import android.widget.TextView;
Mindy Pereira013194c2012-01-06 15:09:33 -080066import android.widget.Toast;
Mindy Pereira7b56a612011-12-14 12:32:28 -080067
Mindy Pereirac17d0732011-12-29 10:46:19 -080068import com.android.common.Rfc822Validator;
Andy Huang5c5fd572012-04-08 18:19:29 -070069import com.android.ex.chips.RecipientEditTextView;
70import com.android.mail.R;
mindyp40882432012-09-06 11:07:40 -070071import com.android.mail.compose.AttachmentsView.AttachmentAddedOrDeletedListener;
Mindy Pereira9932dee2012-01-10 16:09:50 -080072import com.android.mail.compose.AttachmentsView.AttachmentFailureException;
Mindy Pereira5a85e2b2012-01-11 09:53:32 -080073import com.android.mail.compose.FromAddressSpinner.OnAccountChangedListener;
Andy Huang30e2c242012-01-06 18:14:30 -080074import com.android.mail.compose.QuotedTextView.RespondInlineListener;
Mindy Pereira33fe9082012-01-09 16:24:30 -080075import com.android.mail.providers.Account;
Andy Huang30e2c242012-01-06 18:14:30 -080076import com.android.mail.providers.Address;
77import com.android.mail.providers.Attachment;
Mindy Pereira47d0e652012-07-23 09:45:07 -070078import com.android.mail.providers.MailAppProvider;
Mindy Pereira3ce64e72012-01-13 14:29:45 -080079import com.android.mail.providers.Message;
Mindy Pereira82cc5662012-01-09 17:29:30 -080080import com.android.mail.providers.MessageModification;
Mindy Pereira92551d02012-04-05 11:31:12 -070081import com.android.mail.providers.ReplyFromAccount;
Mindy Pereira181df782012-03-01 13:32:44 -080082import com.android.mail.providers.Settings;
Andy Huang30e2c242012-01-06 18:14:30 -080083import com.android.mail.providers.UIProvider;
Mindy Pereira3ca5bad2012-04-16 11:02:42 -070084import com.android.mail.providers.UIProvider.AccountCapabilities;
Mindy Pereira12575862012-03-21 16:30:54 -070085import com.android.mail.providers.UIProvider.DraftType;
Mindy Pereirafa20c1a2012-07-23 13:00:02 -070086import com.android.mail.ui.MailActivity;
Mindy Pereirab199d172012-08-13 11:04:03 -070087import com.android.mail.ui.WaitFragment;
Mark Wei62066e42012-09-13 12:07:02 -070088import com.android.mail.ui.AttachmentTile.AttachmentPreview;
Paul Westbrook92227f62012-03-20 10:32:51 -070089import com.android.mail.utils.AccountUtils;
Mark Wei434f2942012-08-24 11:54:02 -070090import com.android.mail.utils.AttachmentUtils;
Paul Westbrookb334c902012-06-25 11:42:46 -070091import com.android.mail.utils.LogTag;
Andy Huang30e2c242012-01-06 18:14:30 -080092import com.android.mail.utils.LogUtils;
Andy Huang30e2c242012-01-06 18:14:30 -080093import com.android.mail.utils.Utils;
Mindy Pereira46ce0b12012-01-05 10:32:15 -080094import com.google.common.annotations.VisibleForTesting;
Mindy Pereira82cc5662012-01-09 17:29:30 -080095import com.google.common.collect.Lists;
Mindy Pereira4a27ea92012-01-05 15:55:25 -080096import com.google.common.collect.Sets;
Mindy Pereira8e9305e2011-12-13 14:25:04 -080097
Mindy Pereira8eca57a2012-03-20 16:42:34 -070098import java.io.UnsupportedEncodingException;
99import java.net.URLDecoder;
Mindy Pereira46ce0b12012-01-05 10:32:15 -0800100import java.util.ArrayList;
Paul Westbrookbb87b7f2012-03-20 16:20:07 -0700101import java.util.Arrays;
Mindy Pereira46ce0b12012-01-05 10:32:15 -0800102import java.util.Collection;
Mindy Pereira75f66632012-01-11 11:42:02 -0800103import java.util.HashMap;
Mindy Pereira46ce0b12012-01-05 10:32:15 -0800104import java.util.HashSet;
105import java.util.List;
Paul Westbrook1c078cf2012-03-20 16:18:51 -0700106import java.util.Map.Entry;
Paul Westbrookbb87b7f2012-03-20 16:20:07 -0700107import java.util.Set;
Mindy Pereira82cc5662012-01-09 17:29:30 -0800108import java.util.concurrent.ConcurrentHashMap;
Mindy Pereira46ce0b12012-01-05 10:32:15 -0800109
110public class ComposeActivity extends Activity implements OnClickListener, OnNavigationListener,
Mindy Pereira5a85e2b2012-01-11 09:53:32 -0800111 RespondInlineListener, DialogInterface.OnClickListener, TextWatcher,
mindyp40882432012-09-06 11:07:40 -0700112 AttachmentAddedOrDeletedListener, OnAccountChangedListener, LoaderManager.LoaderCallbacks<Cursor>,
mindyp62d3ec72012-08-24 13:04:09 -0700113 TextView.OnEditorActionListener {
Mindy Pereira6349a042012-01-04 11:25:01 -0800114 // Identifiers for which type of composition this is
Mindy Pereira36bbcae2012-04-25 09:27:04 -0700115 static final int COMPOSE = -1;
116 static final int REPLY = 0;
117 static final int REPLY_ALL = 1;
118 static final int FORWARD = 2;
119 static final int EDIT_DRAFT = 3;
Mindy Pereira6349a042012-01-04 11:25:01 -0800120
121 // Integer extra holding one of the above compose action
Mindy Pereira96a7f7a2012-07-09 16:51:06 -0700122 protected static final String EXTRA_ACTION = "action";
Mindy Pereira6349a042012-01-04 11:25:01 -0800123
Mindy Pereira326689d2012-05-17 10:14:14 -0700124 private static final String EXTRA_SHOW_CC = "showCc";
125 private static final String EXTRA_SHOW_BCC = "showBcc";
Mindy Pereiraa34c9a02012-04-17 14:10:53 -0700126
Mindy Pereira8eca57a2012-03-20 16:42:34 -0700127 private static final String UTF8_ENCODING_NAME = "UTF-8";
128
129 private static final String MAIL_TO = "mailto";
130
Mindy Pereira8eca57a2012-03-20 16:42:34 -0700131 private static final String EXTRA_SUBJECT = "subject";
132
133 private static final String EXTRA_BODY = "body";
134
mindypd27b6ea2012-10-05 09:43:49 -0700135 protected static final String EXTRA_FROM_ACCOUNT_STRING = "fromAccountString";
Mindy Pereira9a42bb42012-04-18 15:21:33 -0700136
Mark Wei62066e42012-09-13 12:07:02 -0700137 private static final String EXTRA_ATTACHMENT_PREVIEWS = "attachmentPreviews";
138
Mindy Pereira8eca57a2012-03-20 16:42:34 -0700139 // Extra that we can get passed from other activities
140 private static final String EXTRA_TO = "to";
141 private static final String EXTRA_CC = "cc";
142 private static final String EXTRA_BCC = "bcc";
143
144 // List of all the fields
145 static final String[] ALL_EXTRAS = { EXTRA_SUBJECT, EXTRA_BODY, EXTRA_TO, EXTRA_CC, EXTRA_BCC };
146
Mindy Pereira82cc5662012-01-09 17:29:30 -0800147 private static SendOrSaveCallback sTestSendOrSaveCallback = null;
148 // Map containing information about requests to create new messages, and the id of the
149 // messages that were the result of those requests.
150 //
151 // This map is used when the activity that initiated the save a of a new message, is killed
152 // before the save has completed (and when we know the id of the newly created message). When
153 // a save is completed, the service that is running in the background, will update the map
154 //
155 // When a new ComposeActivity instance is created, it will attempt to use the information in
156 // the previously instantiated map. If ComposeActivity.onCreate() is called, with a bundle
157 // (restoring data from a previous instance), and the map hasn't been created, we will attempt
158 // to populate the map with data stored in shared preferences.
159 private static ConcurrentHashMap<Integer, Long> sRequestMessageIdMap = null;
160 // Key used to store the above map
161 private static final String CACHED_MESSAGE_REQUEST_IDS_KEY = "cache-message-request-ids";
Mindy Pereira6349a042012-01-04 11:25:01 -0800162 /**
163 * Notifies the {@code Activity} that the caller is an Email
164 * {@code Activity}, so that the back behavior may be modified accordingly.
165 *
166 * @see #onAppUpPressed
167 */
Paul Westbrookdaecb4b2012-05-31 10:21:26 -0700168 public static final String EXTRA_FROM_EMAIL_TASK = "fromemail";
Mindy Pereira6349a042012-01-04 11:25:01 -0800169
Mindy Pereirae011b1d2012-06-18 13:45:26 -0700170 public static final String EXTRA_ATTACHMENTS = "attachments";
Paul Westbrookf97588b2012-03-20 11:11:37 -0700171
Mindy Pereira3ce64e72012-01-13 14:29:45 -0800172 // If this is a reply/forward then this extra will hold the original message
Mindy Pereira36bbcae2012-04-25 09:27:04 -0700173 private static final String EXTRA_IN_REFERENCE_TO_MESSAGE = "in-reference-to-message";
Mindy Pereirab18e5a92012-07-10 11:47:21 -0700174 // If this is a reply/forward then this extra will hold a uri we must query
175 // to get the original message.
176 protected static final String EXTRA_IN_REFERENCE_TO_MESSAGE_URI = "in-reference-to-message-uri";
Mark Wei434f2942012-08-24 11:54:02 -0700177 // If this is an action to edit an existing draft message, this extra will hold the
Paul Westbrookbb87b7f2012-03-20 16:20:07 -0700178 // draft message
179 private static final String ORIGINAL_DRAFT_MESSAGE = "original-draft-message";
Mindy Pereira4a27ea92012-01-05 15:55:25 -0800180 private static final String END_TOKEN = ", ";
Paul Westbrookb334c902012-06-25 11:42:46 -0700181 private static final String LOG_TAG = LogTag.getLogTag();
Mindy Pereira013194c2012-01-06 15:09:33 -0800182 // Request numbers for activities we start
183 private static final int RESULT_PICK_ATTACHMENT = 1;
184 private static final int RESULT_CREATE_ACCOUNT = 2;
Mindy Pereira8eca57a2012-03-20 16:42:34 -0700185 // TODO(mindyp) set mime-type for auto send?
Mindy Pereirae011b1d2012-06-18 13:45:26 -0700186 public static final String AUTO_SEND_ACTION = "com.android.mail.action.AUTO_SEND";
Mindy Pereira8eca57a2012-03-20 16:42:34 -0700187
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700188 private static final String EXTRA_SELECTED_REPLY_FROM_ACCOUNT = "replyFromAccount";
189 private static final String EXTRA_REQUEST_ID = "requestId";
190 private static final String EXTRA_FOCUS_SELECTION_START = "focusSelectionStart";
191 private static final String EXTRA_FOCUS_SELECTION_END = null;
192 private static final String EXTRA_MESSAGE = "extraMessage";
Mindy Pereira96a7f7a2012-07-09 16:51:06 -0700193 private static final int REFERENCE_MESSAGE_LOADER = 0;
Mindy Pereirab199d172012-08-13 11:04:03 -0700194 private static final int LOADER_ACCOUNT_CURSOR = 1;
Mindy Pereira47d0e652012-07-23 09:45:07 -0700195 private static final String EXTRA_SELECTED_ACCOUNT = "selectedAccount";
Mindy Pereirab199d172012-08-13 11:04:03 -0700196 private static final String TAG_WAIT = "wait-fragment";
Mindy Pereira2db7d4a2012-08-15 11:00:02 -0700197 private static final String MIME_TYPE_PHOTO = "image/*";
198 private static final String MIME_TYPE_VIDEO = "video/*";
Mindy Pereira8e9305e2011-12-13 14:25:04 -0800199
Mindy Pereira82cc5662012-01-09 17:29:30 -0800200 /**
201 * A single thread for running tasks in the background.
202 */
203 private Handler mSendSaveTaskHandler = null;
Mindy Pereirac17d0732011-12-29 10:46:19 -0800204 private RecipientEditTextView mTo;
205 private RecipientEditTextView mCc;
206 private RecipientEditTextView mBcc;
Mindy Pereira8e9305e2011-12-13 14:25:04 -0800207 private Button mCcBccButton;
208 private CcBccView mCcBccView;
Mindy Pereira7b56a612011-12-14 12:32:28 -0800209 private AttachmentsView mAttachmentsView;
Mindy Pereira33fe9082012-01-09 16:24:30 -0800210 private Account mAccount;
Mindy Pereira92551d02012-04-05 11:31:12 -0700211 private ReplyFromAccount mReplyFromAccount;
Mindy Pereira181df782012-03-01 13:32:44 -0800212 private Settings mCachedSettings;
Mindy Pereira82cc5662012-01-09 17:29:30 -0800213 private Rfc822Validator mValidator;
Mindy Pereira6349a042012-01-04 11:25:01 -0800214 private TextView mSubject;
215
Mindy Pereira326c6602012-01-04 15:32:42 -0800216 private ComposeModeAdapter mComposeModeAdapter;
217 private int mComposeMode = -1;
Mindy Pereira46ce0b12012-01-05 10:32:15 -0800218 private boolean mForward;
219 private String mRecipient;
Mindy Pereira46ce0b12012-01-05 10:32:15 -0800220 private QuotedTextView mQuotedTextView;
Mindy Pereira433b1982012-04-03 11:53:07 -0700221 private EditText mBodyView;
Mindy Pereira1a95a572012-01-05 12:21:29 -0800222 private View mFromStatic;
Mindy Pereira2eb17322012-03-07 10:07:34 -0800223 private TextView mFromStaticText;
Mindy Pereiraeaea9f12012-01-10 15:05:27 -0800224 private View mFromSpinnerWrapper;
Mindy Pereira1883b342012-06-20 08:34:56 -0700225 @VisibleForTesting
226 protected FromAddressSpinner mFromSpinner;
Mindy Pereira013194c2012-01-06 15:09:33 -0800227 private boolean mAddingAttachment;
Mindy Pereiraeaea9f12012-01-10 15:05:27 -0800228 private boolean mAttachmentsChanged;
Mindy Pereira82cc5662012-01-09 17:29:30 -0800229 private boolean mTextChanged;
230 private boolean mReplyFromChanged;
231 private MenuItem mSave;
232 private MenuItem mSend;
Mindy Pereira82cc5662012-01-09 17:29:30 -0800233 private AlertDialog mRecipientErrorDialog;
Mindy Pereiraeaea9f12012-01-10 15:05:27 -0800234 private AlertDialog mSendConfirmDialog;
Mindy Pereirab3112a22012-06-20 12:10:03 -0700235 @VisibleForTesting
236 protected Message mRefMessage;
Mindy Pereira7ed1c112012-01-18 10:59:25 -0800237 private long mDraftId = UIProvider.INVALID_MESSAGE_ID;
238 private Message mDraft;
239 private Object mDraftLock = new Object();
mindyp93b079b2012-08-29 16:32:15 -0700240 private View mPhotoAttachmentsButton;
241 private View mVideoAttachmentsButton;
Mindy Pereira3ce64e72012-01-13 14:29:45 -0800242
Mindy Pereira326c6602012-01-04 15:32:42 -0800243 /**
Paul Westbrookdaecb4b2012-05-31 10:21:26 -0700244 * Boolean indicating whether ComposeActivity was launched from a Gmail controlled view.
245 */
246 private boolean mLaunchedFromEmail = false;
Mindy Pereiracbfb75a2012-06-25 14:52:23 -0700247 private RecipientTextWatcher mToListener;
248 private RecipientTextWatcher mCcListener;
249 private RecipientTextWatcher mBccListener;
Mindy Pereirab18e5a92012-07-10 11:47:21 -0700250 private Uri mRefMessageUri;
Mindy Pereirab199d172012-08-13 11:04:03 -0700251 private Bundle mSavedInstanceState;
Paul Westbrookdaecb4b2012-05-31 10:21:26 -0700252
253
254 /**
Mindy Pereira326c6602012-01-04 15:32:42 -0800255 * Can be called from a non-UI thread.
256 */
Mindy Pereira3ce64e72012-01-13 14:29:45 -0800257 public static void editDraft(Context launcher, Account account, Message message) {
Paul Westbrookbb87b7f2012-03-20 16:20:07 -0700258 launch(launcher, account, message, EDIT_DRAFT);
Mindy Pereira326c6602012-01-04 15:32:42 -0800259 }
260
Mindy Pereira6349a042012-01-04 11:25:01 -0800261 /**
262 * Can be called from a non-UI thread.
263 */
Mindy Pereira33fe9082012-01-09 16:24:30 -0800264 public static void compose(Context launcher, Account account) {
Mindy Pereira6349a042012-01-04 11:25:01 -0800265 launch(launcher, account, null, COMPOSE);
266 }
267
268 /**
269 * Can be called from a non-UI thread.
270 */
Mindy Pereira3ce64e72012-01-13 14:29:45 -0800271 public static void reply(Context launcher, Account account, Message message) {
272 launch(launcher, account, message, REPLY);
Mindy Pereira6349a042012-01-04 11:25:01 -0800273 }
274
275 /**
276 * Can be called from a non-UI thread.
277 */
Mindy Pereira3ce64e72012-01-13 14:29:45 -0800278 public static void replyAll(Context launcher, Account account, Message message) {
279 launch(launcher, account, message, REPLY_ALL);
Mindy Pereira6349a042012-01-04 11:25:01 -0800280 }
281
282 /**
283 * Can be called from a non-UI thread.
284 */
Mindy Pereira3ce64e72012-01-13 14:29:45 -0800285 public static void forward(Context launcher, Account account, Message message) {
286 launch(launcher, account, message, FORWARD);
Mindy Pereira6349a042012-01-04 11:25:01 -0800287 }
288
Mindy Pereira3ce64e72012-01-13 14:29:45 -0800289 private static void launch(Context launcher, Account account, Message message, int action) {
Mindy Pereira6349a042012-01-04 11:25:01 -0800290 Intent intent = new Intent(launcher, ComposeActivity.class);
291 intent.putExtra(EXTRA_FROM_EMAIL_TASK, true);
292 intent.putExtra(EXTRA_ACTION, action);
293 intent.putExtra(Utils.EXTRA_ACCOUNT, account);
Paul Westbrookbb87b7f2012-03-20 16:20:07 -0700294 if (action == EDIT_DRAFT) {
295 intent.putExtra(ORIGINAL_DRAFT_MESSAGE, message);
296 } else {
297 intent.putExtra(EXTRA_IN_REFERENCE_TO_MESSAGE, message);
298 }
Mindy Pereira6349a042012-01-04 11:25:01 -0800299 launcher.startActivity(intent);
300 }
Mindy Pereira8e9305e2011-12-13 14:25:04 -0800301
302 @Override
303 public void onCreate(Bundle savedInstanceState) {
304 super.onCreate(savedInstanceState);
Mindy Pereira3528d362012-01-05 14:39:44 -0800305 setContentView(R.layout.compose);
Mindy Pereirab199d172012-08-13 11:04:03 -0700306 mSavedInstanceState = savedInstanceState;
307 checkValidAccounts();
308 }
309
310 private void finishCreate() {
311 Bundle savedInstanceState = mSavedInstanceState;
Mindy Pereira3528d362012-01-05 14:39:44 -0800312 findViews();
Mindy Pereira818143e2012-01-11 13:59:49 -0800313 Intent intent = getIntent();
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700314 Message message;
Mark Wei62066e42012-09-13 12:07:02 -0700315 ArrayList<AttachmentPreview> previews;
Mindy Pereira71c9e562012-05-17 11:01:02 -0700316 boolean showQuotedText = false;
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700317 int action;
Mindy Pereira47d0e652012-07-23 09:45:07 -0700318 // Check for any of the possibly supplied accounts.;
319 Account account = null;
Mindy Pereiraf7fc6c32012-06-19 15:18:33 -0700320 if (hadSavedInstanceStateMessage(savedInstanceState)) {
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700321 action = savedInstanceState.getInt(EXTRA_ACTION, COMPOSE);
322 account = savedInstanceState.getParcelable(Utils.EXTRA_ACCOUNT);
323 message = (Message) savedInstanceState.getParcelable(EXTRA_MESSAGE);
Mark Wei62066e42012-09-13 12:07:02 -0700324
325 previews = savedInstanceState.getParcelableArrayList(EXTRA_ATTACHMENT_PREVIEWS);
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700326 mRefMessage = (Message) savedInstanceState.getParcelable(EXTRA_IN_REFERENCE_TO_MESSAGE);
327 } else {
Mindy Pereira47d0e652012-07-23 09:45:07 -0700328 account = obtainAccount(intent);
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700329 action = intent.getIntExtra(EXTRA_ACTION, COMPOSE);
330 // Initialize the message from the message in the intent
331 message = (Message) intent.getParcelableExtra(ORIGINAL_DRAFT_MESSAGE);
Mark Wei62066e42012-09-13 12:07:02 -0700332 previews = intent.getParcelableArrayListExtra(EXTRA_ATTACHMENT_PREVIEWS);
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700333 mRefMessage = (Message) intent.getParcelableExtra(EXTRA_IN_REFERENCE_TO_MESSAGE);
Mindy Pereirab18e5a92012-07-10 11:47:21 -0700334 mRefMessageUri = (Uri) intent.getParcelableExtra(EXTRA_IN_REFERENCE_TO_MESSAGE_URI);
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700335 }
Mark Wei62066e42012-09-13 12:07:02 -0700336 mAttachmentsView.setAttachmentPreviews(previews);
Paul Westbrook92227f62012-03-20 10:32:51 -0700337
338 setAccount(account);
Mindy Pereira818143e2012-01-11 13:59:49 -0800339 if (mAccount == null) {
340 return;
341 }
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700342
Paul Westbrookdaecb4b2012-05-31 10:21:26 -0700343 if (intent.getBooleanExtra(EXTRA_FROM_EMAIL_TASK, false)) {
344 mLaunchedFromEmail = true;
345 } else if (Intent.ACTION_SEND.equals(intent.getAction())) {
346 final Uri dataUri = intent.getData();
347 if (dataUri != null) {
348 final String dataScheme = intent.getData().getScheme();
349 final String accountScheme = mAccount.composeIntentUri.getScheme();
350 mLaunchedFromEmail = TextUtils.equals(dataScheme, accountScheme);
351 }
352 }
353
Mindy Pereira96a7f7a2012-07-09 16:51:06 -0700354 if (mRefMessageUri != null) {
355 // We have a referenced message that we must look up.
356 getLoaderManager().initLoader(REFERENCE_MESSAGE_LOADER, null, this);
357 return;
358 } else if (message != null && action != EDIT_DRAFT) {
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700359 initFromDraftMessage(message);
360 initQuotedTextFromRefMessage(mRefMessage, action);
Mindy Pereiraa34c9a02012-04-17 14:10:53 -0700361 showCcBcc(savedInstanceState);
Mindy Pereira71c9e562012-05-17 11:01:02 -0700362 showQuotedText = message.appendRefMessageContent;
Paul Westbrookbb87b7f2012-03-20 16:20:07 -0700363 } else if (action == EDIT_DRAFT) {
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700364 initFromDraftMessage(message);
Mindy Pereiraef388302012-06-18 19:07:44 -0700365 boolean showBcc = !TextUtils.isEmpty(message.bcc);
366 boolean showCc = showBcc || !TextUtils.isEmpty(message.cc);
367 mCcBccView.show(false, showCc, showBcc);
Paul Westbrookbb87b7f2012-03-20 16:20:07 -0700368 // Update the action to the draft type of the previous draft
369 switch (message.draftType) {
370 case UIProvider.DraftType.REPLY:
371 action = REPLY;
372 break;
373 case UIProvider.DraftType.REPLY_ALL:
374 action = REPLY_ALL;
375 break;
376 case UIProvider.DraftType.FORWARD:
377 action = FORWARD;
378 break;
379 case UIProvider.DraftType.COMPOSE:
380 default:
381 action = COMPOSE;
382 break;
383 }
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700384 initQuotedTextFromRefMessage(mRefMessage, action);
Mindy Pereira71c9e562012-05-17 11:01:02 -0700385 showQuotedText = message.appendRefMessageContent;
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700386 } else if ((action == REPLY || action == REPLY_ALL || action == FORWARD)) {
387 if (mRefMessage != null) {
388 initFromRefMessage(action, mAccount.name);
Mindy Pereira71c9e562012-05-17 11:01:02 -0700389 showQuotedText = true;
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700390 }
Mindy Pereira8eca57a2012-03-20 16:42:34 -0700391 } else {
392 initFromExtras(intent);
Paul Westbrookbb87b7f2012-03-20 16:20:07 -0700393 }
Mindy Pereira96a7f7a2012-07-09 16:51:06 -0700394 finishSetup(action, intent, savedInstanceState, showQuotedText);
395 }
Paul Westbrookbb87b7f2012-03-20 16:20:07 -0700396
Mindy Pereirab199d172012-08-13 11:04:03 -0700397 private void checkValidAccounts() {
398 mAccounts = AccountUtils.getAccounts(this);
399 if (mAccounts == null || mAccounts.length == 0) {
400 final Intent noAccountIntent = MailAppProvider.getNoAccountIntent(this);
401 if (noAccountIntent != null) {
402 startActivityForResult(noAccountIntent, RESULT_CREATE_ACCOUNT);
403 }
404 } else {
mindyp26d4d2d2012-09-18 17:30:32 -0700405 // If none of the accounts are syncing, setup a watcher.
Mindy Pereirab199d172012-08-13 11:04:03 -0700406 boolean anySyncing = false;
407 for (Account a : mAccounts) {
Paul Westbrookdfa1dec2012-09-26 16:27:28 -0700408 if (a.isAccountReady()) {
Mindy Pereirab199d172012-08-13 11:04:03 -0700409 anySyncing = true;
410 break;
411 }
412 }
413 if (!anySyncing) {
414 // There are accounts, but none are sync'd, which is just like having no accounts.
415 mAccounts = null;
416 getLoaderManager().initLoader(LOADER_ACCOUNT_CURSOR, null, this);
417 return;
418 }
419 finishCreate();
420 }
421 }
422
Mindy Pereira47d0e652012-07-23 09:45:07 -0700423 private Account obtainAccount(Intent intent) {
424 Account account = null;
425 Object accountExtra = null;
426 if (intent != null && intent.getExtras() != null) {
427 accountExtra = intent.getExtras().get(Utils.EXTRA_ACCOUNT);
428 if (accountExtra instanceof Account) {
429 return (Account) accountExtra;
mindyp7ae042e2012-08-27 13:27:37 -0700430 } else if (accountExtra instanceof String) {
431 // This is the Account attached to the widget compose intent.
432 account = Account.newinstance((String)accountExtra);
433 if (account != null) {
434 return account;
435 }
Mindy Pereira47d0e652012-07-23 09:45:07 -0700436 }
437 accountExtra = intent.getStringExtra(EXTRA_SELECTED_ACCOUNT);
438 }
439 if (account == null) {
mindyp06174462012-10-12 09:11:27 -0700440 MailAppProvider provider = MailAppProvider.getInstance();
441 String lastAccountUri = provider.getLastSentFromAccount();
442 if (TextUtils.isEmpty(lastAccountUri)) {
443 lastAccountUri = provider.getLastViewedAccount();
444 }
Mindy Pereira47d0e652012-07-23 09:45:07 -0700445 if (!TextUtils.isEmpty(lastAccountUri)) {
446 accountExtra = Uri.parse(lastAccountUri);
447 }
448 }
Mindy Pereirab199d172012-08-13 11:04:03 -0700449 if (mAccounts != null && mAccounts.length > 0) {
Mindy Pereira47d0e652012-07-23 09:45:07 -0700450 if (accountExtra instanceof String && !TextUtils.isEmpty((String) accountExtra)) {
451 // For backwards compatibility, we need to check account
452 // names.
Mindy Pereirab199d172012-08-13 11:04:03 -0700453 for (Account a : mAccounts) {
Mindy Pereira47d0e652012-07-23 09:45:07 -0700454 if (a.name.equals(accountExtra)) {
455 account = a;
456 }
457 }
458 } else if (accountExtra instanceof Uri) {
459 // The uri of the last viewed account is what is stored in
460 // the current code base.
Mindy Pereirab199d172012-08-13 11:04:03 -0700461 for (Account a : mAccounts) {
Mindy Pereira47d0e652012-07-23 09:45:07 -0700462 if (a.uri.equals(accountExtra)) {
463 account = a;
464 }
465 }
Mindy Pereirab199d172012-08-13 11:04:03 -0700466 }
467 if (account == null) {
468 account = mAccounts[0];
Mindy Pereira47d0e652012-07-23 09:45:07 -0700469 }
470 }
471 return account;
472 }
473
Mindy Pereira96a7f7a2012-07-09 16:51:06 -0700474 private void finishSetup(int action, Intent intent, Bundle savedInstanceState,
475 boolean showQuotedText) {
Paul Westbrookbb87b7f2012-03-20 16:20:07 -0700476 if (action == COMPOSE) {
Mindy Pereiraeaea9f12012-01-10 15:05:27 -0800477 mQuotedTextView.setVisibility(View.GONE);
Mindy Pereira46ce0b12012-01-05 10:32:15 -0800478 }
Mindy Pereira818143e2012-01-11 13:59:49 -0800479 initRecipients();
Mindy Pereiraf7fc6c32012-06-19 15:18:33 -0700480 // Don't bother with the intent if we have procured a message from the
481 // intent already.
482 if (!hadSavedInstanceStateMessage(savedInstanceState)) {
483 initAttachmentsFromIntent(intent);
484 }
Mindy Pereira1a95a572012-01-05 12:21:29 -0800485 initActionBar(action);
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700486 initFromSpinner(savedInstanceState != null ? savedInstanceState : intent.getExtras(),
487 action);
Mindy Pereira75f66632012-01-11 11:42:02 -0800488 initChangeListeners();
Mindy Pereira433b1982012-04-03 11:53:07 -0700489 setFocus(action);
Mindy Pereira326689d2012-05-17 10:14:14 -0700490 updateHideOrShowCcBcc();
Mindy Pereira71c9e562012-05-17 11:01:02 -0700491 updateHideOrShowQuotedText(showQuotedText);
492 }
493
Mindy Pereiraf7fc6c32012-06-19 15:18:33 -0700494 private boolean hadSavedInstanceStateMessage(Bundle savedInstanceState) {
495 return savedInstanceState != null && savedInstanceState.containsKey(EXTRA_MESSAGE);
496 }
497
Mindy Pereira71c9e562012-05-17 11:01:02 -0700498 private void updateHideOrShowQuotedText(boolean showQuotedText) {
499 mQuotedTextView.updateCheckedState(showQuotedText);
mindyp40882432012-09-06 11:07:40 -0700500 mQuotedTextView.setUpperDividerVisible(mAttachmentsView.getAttachments().size() > 0);
Mindy Pereira433b1982012-04-03 11:53:07 -0700501 }
502
503 private void setFocus(int action) {
504 if (action == EDIT_DRAFT) {
505 int type = mDraft.draftType;
506 switch (type) {
507 case UIProvider.DraftType.COMPOSE:
508 case UIProvider.DraftType.FORWARD:
509 action = COMPOSE;
510 break;
511 case UIProvider.DraftType.REPLY:
512 case UIProvider.DraftType.REPLY_ALL:
513 default:
514 action = REPLY;
515 break;
516 }
517 }
518 switch (action) {
519 case FORWARD:
520 case COMPOSE:
521 mTo.requestFocus();
522 break;
523 case REPLY:
524 case REPLY_ALL:
525 default:
526 focusBody();
527 break;
528 }
529 }
530
531 /**
532 * Focus the body of the message.
533 */
534 public void focusBody() {
535 mBodyView.requestFocus();
536 int length = mBodyView.getText().length();
537
538 int signatureStartPos = getSignatureStartPosition(
539 mSignature, mBodyView.getText().toString());
540 if (signatureStartPos > -1) {
541 // In case the user deleted the newlines...
542 mBodyView.setSelection(signatureStartPos);
mindyp8743cfc2012-09-18 13:29:08 -0700543 } else if (length >= 0) {
Mindy Pereira433b1982012-04-03 11:53:07 -0700544 // Move cursor to the end.
545 mBodyView.setSelection(length);
546 }
Mindy Pereira1a95a572012-01-05 12:21:29 -0800547 }
548
549 @Override
550 protected void onResume() {
551 super.onResume();
552 // Update the from spinner as other accounts
553 // may now be available.
Mindy Pereira818143e2012-01-11 13:59:49 -0800554 if (mFromSpinner != null && mAccount != null) {
Mindy Pereirab199d172012-08-13 11:04:03 -0700555 mFromSpinner.asyncInitFromSpinner(mComposeMode, mAccount, mAccounts);
Mindy Pereira818143e2012-01-11 13:59:49 -0800556 }
Mindy Pereira1a95a572012-01-05 12:21:29 -0800557 }
558
Mindy Pereiraeaea9f12012-01-10 15:05:27 -0800559 @Override
560 protected void onPause() {
561 super.onPause();
562
563 if (mSendConfirmDialog != null) {
564 mSendConfirmDialog.dismiss();
565 }
566 if (mRecipientErrorDialog != null) {
567 mRecipientErrorDialog.dismiss();
568 }
Mindy Pereiraa2148332012-07-02 13:54:14 -0700569 // When the user exits the compose view, see if this draft needs saving.
Yorke Lee3d7048e2012-09-19 14:19:25 -0700570 // Don't save unnecessary drafts if we are only changing the orientation.
571 if (!isChangingConfigurations()) {
Mindy Pereiraa2148332012-07-02 13:54:14 -0700572 saveIfNeeded();
573 }
Mindy Pereiraeaea9f12012-01-10 15:05:27 -0800574 }
575
576 @Override
577 protected final void onActivityResult(int request, int result, Intent data) {
Mindy Pereirab199d172012-08-13 11:04:03 -0700578 if (request == RESULT_PICK_ATTACHMENT && result == RESULT_OK) {
Mindy Pereiraeaea9f12012-01-10 15:05:27 -0800579 addAttachmentAndUpdateView(data);
Mindy Pereirab199d172012-08-13 11:04:03 -0700580 mAddingAttachment = false;
581 } else if (request == RESULT_CREATE_ACCOUNT) {
582 // We were waiting for the user to create an account
583 if (result != RESULT_OK) {
584 finish();
585 } else {
586 // Watch for accounts to show up!
587 // restart the loader to get the updated list of accounts
588 getLoaderManager().initLoader(LOADER_ACCOUNT_CURSOR, null, this);
589 showWaitFragment(null);
590 }
Mindy Pereiraeaea9f12012-01-10 15:05:27 -0800591 }
592 }
593
594 @Override
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700595 public final void onRestoreInstanceState(Bundle savedInstanceState) {
596 super.onRestoreInstanceState(savedInstanceState);
597 if (savedInstanceState != null) {
598 if (savedInstanceState.containsKey(EXTRA_FOCUS_SELECTION_START)) {
599 int selectionStart = savedInstanceState.getInt(EXTRA_FOCUS_SELECTION_START);
600 int selectionEnd = savedInstanceState.getInt(EXTRA_FOCUS_SELECTION_END);
601 // There should be a focus and it should be an EditText since we
602 // only save these extras if these conditions are true.
603 EditText focusEditText = (EditText) getCurrentFocus();
604 final int length = focusEditText.getText().length();
605 if (selectionStart < length && selectionEnd < length) {
606 focusEditText.setSelection(selectionStart, selectionEnd);
607 }
608 }
609 }
610 }
611
612 @Override
Mindy Pereiraeaea9f12012-01-10 15:05:27 -0800613 public final void onSaveInstanceState(Bundle state) {
614 super.onSaveInstanceState(state);
Mindy Pereirab199d172012-08-13 11:04:03 -0700615 // We have no accounts so there is nothing to compose, and therefore, nothing to save.
616 if (mAccounts == null || mAccounts.length == 0) {
617 return;
618 }
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700619 // The framework is happy to save and restore the selection but only if it also saves and
620 // restores the contents of the edit text. That's a lot of text to put in a bundle so we do
621 // this manually.
622 View focus = getCurrentFocus();
623 if (focus != null && focus instanceof EditText) {
624 EditText focusEditText = (EditText) focus;
625 state.putInt(EXTRA_FOCUS_SELECTION_START, focusEditText.getSelectionStart());
626 state.putInt(EXTRA_FOCUS_SELECTION_END, focusEditText.getSelectionEnd());
627 }
Paul Westbrook6273e962012-04-23 10:44:15 -0700628
629 final List<ReplyFromAccount> replyFromAccounts = mFromSpinner.getReplyFromAccounts();
Paul Westbrook151f1ad2012-04-24 09:13:00 -0700630 final int selectedPos = mFromSpinner.getSelectedItemPosition();
Mindy Pereirad90f7ac2012-06-27 10:31:06 -0700631 final ReplyFromAccount selectedReplyFromAccount = (replyFromAccounts != null
632 && replyFromAccounts.size() > 0 && replyFromAccounts.size() > selectedPos) ?
633 replyFromAccounts.get(selectedPos) : null;
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700634 if (selectedReplyFromAccount != null) {
635 state.putString(EXTRA_SELECTED_REPLY_FROM_ACCOUNT, selectedReplyFromAccount.serialize()
636 .toString());
637 state.putParcelable(Utils.EXTRA_ACCOUNT, selectedReplyFromAccount.account);
638 } else {
639 state.putParcelable(Utils.EXTRA_ACCOUNT, mAccount);
640 }
Mindy Pereiraeaea9f12012-01-10 15:05:27 -0800641
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700642 if (mDraftId == UIProvider.INVALID_MESSAGE_ID && mRequestId !=0) {
643 // We don't have a draft id, and we have a request id,
644 // save the request id.
645 state.putInt(EXTRA_REQUEST_ID, mRequestId);
646 }
647
648 // We want to restore the current mode after a pause
649 // or rotation.
650 int mode = getMode();
651 state.putInt(EXTRA_ACTION, mode);
652
653 Message message = createMessage(selectedReplyFromAccount, mode);
654 state.putParcelable(EXTRA_MESSAGE, message);
655
656 if (mRefMessage != null) {
657 state.putParcelable(EXTRA_IN_REFERENCE_TO_MESSAGE, mRefMessage);
658 }
Mindy Pereira326689d2012-05-17 10:14:14 -0700659 state.putBoolean(EXTRA_SHOW_CC, mCcBccView.isCcVisible());
660 state.putBoolean(EXTRA_SHOW_BCC, mCcBccView.isBccVisible());
Mark Wei62066e42012-09-13 12:07:02 -0700661
662 state.putParcelableArrayList(
663 EXTRA_ATTACHMENT_PREVIEWS, mAttachmentsView.getAttachmentPreviews());
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700664 }
665
666 private int getMode() {
667 int mode = ComposeActivity.COMPOSE;
668 ActionBar actionBar = getActionBar();
Mindy Pereirae011b1d2012-06-18 13:45:26 -0700669 if (actionBar != null
670 && actionBar.getNavigationMode() == ActionBar.NAVIGATION_MODE_LIST) {
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700671 mode = actionBar.getSelectedNavigationIndex();
672 }
673 return mode;
674 }
675
676 private Message createMessage(ReplyFromAccount selectedReplyFromAccount, int mode) {
677 Message message = new Message();
678 message.id = UIProvider.INVALID_MESSAGE_ID;
Andy Huangd47877e2012-08-09 19:31:24 -0700679 message.serverId = null;
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700680 message.uri = null;
681 message.conversationUri = null;
682 message.subject = mSubject.getText().toString();
683 message.snippet = null;
Mindy Pereira1eedc752012-07-16 16:34:41 -0700684 message.from = selectedReplyFromAccount != null ? selectedReplyFromAccount.address
Mindy Pereirab67aa8f2012-07-11 15:09:06 -0700685 : mAccount != null ? mAccount.name : null;
Mindy Pereira3c911582012-08-09 16:59:09 -0700686 message.to = formatSenders(mTo.getText().toString());
687 message.cc = formatSenders(mCc.getText().toString());
688 message.bcc = formatSenders(mBcc.getText().toString());
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700689 message.replyTo = null;
690 message.dateReceivedMs = 0;
mindyped9c2f02012-10-12 10:02:08 -0700691 String htmlBody = Html.toHtml(new SpannableString(mBodyView.getText().toString()));
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700692 StringBuilder fullBody = new StringBuilder(htmlBody);
693 message.bodyHtml = fullBody.toString();
694 message.bodyText = mBodyView.getText().toString();
695 message.embedsExternalResources = false;
696 message.refMessageId = mRefMessage != null ? mRefMessage.uri.toString() : null;
Mindy Pereirad2bef8b2012-05-30 12:14:52 -0700697 message.draftType = getDraftType(mode);
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700698 message.appendRefMessageContent = mQuotedTextView.getQuotedTextIfIncluded() != null;
699 ArrayList<Attachment> attachments = mAttachmentsView.getAttachments();
700 message.hasAttachments = attachments != null && attachments.size() > 0;
701 message.attachmentListUri = null;
702 message.messageFlags = 0;
703 message.saveUri = null;
704 message.sendUri = null;
705 message.alwaysShowImages = false;
706 message.attachmentsJson = Attachment.toJSONArray(attachments);
707 CharSequence quotedText = mQuotedTextView.getQuotedText();
708 message.quotedTextOffset = !TextUtils.isEmpty(quotedText) ? QuotedTextView
709 .getQuotedTextOffset(quotedText.toString()) : -1;
710 message.accountUri = null;
711 return message;
Mindy Pereiraeaea9f12012-01-10 15:05:27 -0800712 }
713
Mindy Pereira3c911582012-08-09 16:59:09 -0700714 private String formatSenders(String string) {
715 if (!TextUtils.isEmpty(string) && string.charAt(string.length() - 1) == ',') {
716 return string.substring(0, string.length() - 1);
717 }
718 return string;
719 }
720
Mindy Pereira818143e2012-01-11 13:59:49 -0800721 @VisibleForTesting
722 void setAccount(Account account) {
Mindy Pereirabb5217e2012-04-17 11:08:29 -0700723 if (account == null) {
724 return;
725 }
Mindy Pereira23e9fde2012-03-20 15:08:24 -0700726 if (!account.equals(mAccount)) {
727 mAccount = account;
Paul Westbrookb1f573c2012-04-06 11:38:28 -0700728 mCachedSettings = mAccount.settings;
729 appendSignature();
Mindy Pereira23e9fde2012-03-20 15:08:24 -0700730 }
Mindy Pereirafa20c1a2012-07-23 13:00:02 -0700731 if (mAccount != null) {
732 MailActivity.setForegroundNdef(MailActivity.getMailtoNdef(mAccount.name));
733 }
Mindy Pereira818143e2012-01-11 13:59:49 -0800734 }
735
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700736 private void initFromSpinner(Bundle bundle, int action) {
Mindy Pereira9a42bb42012-04-18 15:21:33 -0700737 String accountString = null;
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700738 if (action == EDIT_DRAFT && mDraft.draftType == UIProvider.DraftType.COMPOSE) {
Mindy Pereira62de1b12012-04-06 12:17:56 -0700739 action = COMPOSE;
740 }
Mindy Pereirab199d172012-08-13 11:04:03 -0700741 mFromSpinner.asyncInitFromSpinner(action, mAccount, mAccounts);
Mindy Pereira9a42bb42012-04-18 15:21:33 -0700742 if (bundle != null) {
743 if (bundle.containsKey(EXTRA_SELECTED_REPLY_FROM_ACCOUNT)) {
744 mReplyFromAccount = ReplyFromAccount.deserialize(mAccount,
745 bundle.getString(EXTRA_SELECTED_REPLY_FROM_ACCOUNT));
746 } else if (bundle.containsKey(EXTRA_FROM_ACCOUNT_STRING)) {
747 accountString = bundle.getString(EXTRA_FROM_ACCOUNT_STRING);
748 mReplyFromAccount = mFromSpinner.getMatchingReplyFromAccount(accountString);
749 }
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700750 }
751 if (mReplyFromAccount == null) {
752 if (mDraft != null) {
753 mReplyFromAccount = getReplyFromAccountFromDraft(mAccount, mDraft);
754 } else if (mRefMessage != null) {
755 mReplyFromAccount = getReplyFromAccountForReply(mAccount, mRefMessage);
756 }
Mindy Pereira62de1b12012-04-06 12:17:56 -0700757 }
758 if (mReplyFromAccount == null) {
759 mReplyFromAccount = new ReplyFromAccount(mAccount, mAccount.uri, mAccount.name,
Mindy Pereiracd970dd2012-05-31 10:07:47 -0700760 mAccount.name, mAccount.name, true, false);
Mindy Pereira62de1b12012-04-06 12:17:56 -0700761 }
Mindy Pereira9a42bb42012-04-18 15:21:33 -0700762
Mindy Pereira62de1b12012-04-06 12:17:56 -0700763 mFromSpinner.setCurrentAccount(mReplyFromAccount);
Mindy Pereira9a42bb42012-04-18 15:21:33 -0700764
Mindy Pereira62de1b12012-04-06 12:17:56 -0700765 if (mFromSpinner.getCount() > 1) {
Mindy Pereiraa83e7082012-03-30 08:53:11 -0700766 // If there is only 1 account, just show that account.
767 // Otherwise, give the user the ability to choose which account to
Mindy Pereira62de1b12012-04-06 12:17:56 -0700768 // send mail from / save drafts to.
769 mFromStatic.setVisibility(View.GONE);
Mindy Pereiraa83e7082012-03-30 08:53:11 -0700770 mFromStaticText.setText(mAccount.name);
Mindy Pereira62de1b12012-04-06 12:17:56 -0700771 mFromSpinnerWrapper.setVisibility(View.VISIBLE);
Mindy Pereiraa83e7082012-03-30 08:53:11 -0700772 } else {
773 mFromStatic.setVisibility(View.VISIBLE);
774 mFromStaticText.setText(mAccount.name);
775 mFromSpinnerWrapper.setVisibility(View.GONE);
Mindy Pereiraa83e7082012-03-30 08:53:11 -0700776 }
Mindy Pereira46ce0b12012-01-05 10:32:15 -0800777 }
778
Mindy Pereira62de1b12012-04-06 12:17:56 -0700779 private ReplyFromAccount getReplyFromAccountForReply(Account account, Message refMessage) {
780 if (refMessage.accountUri != null) {
781 // This must be from combined inbox.
782 List<ReplyFromAccount> replyFromAccounts = mFromSpinner.getReplyFromAccounts();
783 for (ReplyFromAccount from : replyFromAccounts) {
784 if (from.account.uri.equals(refMessage.accountUri)) {
785 return from;
786 }
787 }
788 return null;
789 } else {
790 return getReplyFromAccount(account, refMessage);
791 }
792 }
793
794 /**
795 * Given an account and which email address the message was sent to,
796 * return who the message should be sent from.
797 * @param account Account in which the message arrived.
798 * @param sentTo Email address to which the message was sent.
799 * @return the address from which to reply.
800 */
801 public ReplyFromAccount getReplyFromAccount(Account account, Message refMessage) {
802 // First see if we are supposed to use the default address or
803 // the address it was sentTo.
Mindy Pereira326689d2012-05-17 10:14:14 -0700804 if (mCachedSettings.forceReplyFromDefault) {
Mindy Pereira62de1b12012-04-06 12:17:56 -0700805 return getDefaultReplyFromAccount(account);
806 } else {
Mindy Pereira89bae572012-06-18 11:34:36 -0700807 // If we aren't explicitly told which account to look for, look at
Mindy Pereira62de1b12012-04-06 12:17:56 -0700808 // all the message recipients and find one that matches
809 // a custom from or account.
810 List<String> allRecipients = new ArrayList<String>();
mindyp5ed63112012-09-17 17:31:45 -0700811 allRecipients.addAll(Arrays.asList(refMessage.getToAddresses()));
812 allRecipients.addAll(Arrays.asList(refMessage.getCcAddresses()));
Mindy Pereira62de1b12012-04-06 12:17:56 -0700813 return getMatchingRecipient(account, allRecipients);
814 }
815 }
816
817 /**
818 * Compare all the recipients of an email to the current account and all
819 * custom addresses associated with that account. Return the match if there
820 * is one, or the default account if there isn't.
821 */
822 protected ReplyFromAccount getMatchingRecipient(Account account, List<String> sentTo) {
823 // Tokenize the list and place in a hashmap.
824 ReplyFromAccount matchingReplyFrom = null;
825 Rfc822Token[] tokens;
826 HashSet<String> recipientsMap = new HashSet<String>();
827 for (String address : sentTo) {
828 tokens = Rfc822Tokenizer.tokenize(address);
829 for (int i = 0; i < tokens.length; i++) {
830 recipientsMap.add(tokens[i].getAddress());
831 }
832 }
833
834 int matchingAddressCount = 0;
835 List<ReplyFromAccount> customFroms;
Andy Huang16174812012-08-16 16:40:35 -0700836 customFroms = account.getReplyFroms();
837 if (customFroms != null) {
838 for (ReplyFromAccount entry : customFroms) {
839 if (recipientsMap.contains(entry.address)) {
840 matchingReplyFrom = entry;
841 matchingAddressCount++;
Mindy Pereira62de1b12012-04-06 12:17:56 -0700842 }
843 }
Mindy Pereira62de1b12012-04-06 12:17:56 -0700844 }
845 if (matchingAddressCount > 1) {
846 matchingReplyFrom = getDefaultReplyFromAccount(account);
847 }
848 return matchingReplyFrom;
849 }
850
851 private ReplyFromAccount getDefaultReplyFromAccount(Account account) {
852 List<ReplyFromAccount> replyFromAccounts = mFromSpinner.getReplyFromAccounts();
853 for (ReplyFromAccount from : replyFromAccounts) {
854 if (from.isDefault) {
855 return from;
856 }
857 }
Mindy Pereiracd970dd2012-05-31 10:07:47 -0700858 return new ReplyFromAccount(account, account.uri, account.name, account.name, account.name,
859 true, false);
Mindy Pereira62de1b12012-04-06 12:17:56 -0700860 }
861
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700862 private ReplyFromAccount getReplyFromAccountFromDraft(Account account, Message msg) {
863 String sender = msg.from;
Mindy Pereira62de1b12012-04-06 12:17:56 -0700864 ReplyFromAccount replyFromAccount = null;
865 List<ReplyFromAccount> replyFromAccounts = mFromSpinner.getReplyFromAccounts();
866 if (TextUtils.equals(account.name, sender)) {
867 replyFromAccount = new ReplyFromAccount(mAccount, mAccount.uri, mAccount.name,
Mindy Pereiracd970dd2012-05-31 10:07:47 -0700868 mAccount.name, mAccount.name, true, false);
Mindy Pereira62de1b12012-04-06 12:17:56 -0700869 } else {
870 for (ReplyFromAccount fromAccount : replyFromAccounts) {
871 if (TextUtils.equals(fromAccount.name, sender)) {
872 replyFromAccount = fromAccount;
873 break;
874 }
875 }
876 }
877 return replyFromAccount;
878 }
879
Mindy Pereira46ce0b12012-01-05 10:32:15 -0800880 private void findViews() {
Mindy Pereirab199d172012-08-13 11:04:03 -0700881 findViewById(R.id.compose).setVisibility(View.VISIBLE);
Mindy Pereiraa26b54e2012-01-06 12:54:33 -0800882 mCcBccButton = (Button) findViewById(R.id.add_cc_bcc);
Mindy Pereira8e9305e2011-12-13 14:25:04 -0800883 if (mCcBccButton != null) {
884 mCcBccButton.setOnClickListener(this);
885 }
886 mCcBccView = (CcBccView) findViewById(R.id.cc_bcc_wrapper);
Mindy Pereira7b56a612011-12-14 12:32:28 -0800887 mAttachmentsView = (AttachmentsView)findViewById(R.id.attachments);
mindyp93b079b2012-08-29 16:32:15 -0700888 mPhotoAttachmentsButton = findViewById(R.id.add_photo_attachment);
mindypcd0b0b92012-08-23 14:33:17 -0700889 if (mPhotoAttachmentsButton != null) {
890 mPhotoAttachmentsButton.setOnClickListener(this);
891 }
mindyp93b079b2012-08-29 16:32:15 -0700892 mVideoAttachmentsButton = findViewById(R.id.add_video_attachment);
mindypcd0b0b92012-08-23 14:33:17 -0700893 if (mVideoAttachmentsButton != null) {
894 mVideoAttachmentsButton.setOnClickListener(this);
895 }
Mindy Pereira818143e2012-01-11 13:59:49 -0800896 mTo = (RecipientEditTextView) findViewById(R.id.to);
897 mCc = (RecipientEditTextView) findViewById(R.id.cc);
898 mBcc = (RecipientEditTextView) findViewById(R.id.bcc);
Mindy Pereira82cc5662012-01-09 17:29:30 -0800899 // TODO: add special chips text change watchers before adding
900 // this as a text changed watcher to the to, cc, bcc fields.
Mindy Pereira6349a042012-01-04 11:25:01 -0800901 mSubject = (TextView) findViewById(R.id.subject);
mindyp62d3ec72012-08-24 13:04:09 -0700902 mSubject.setOnEditorActionListener(this);
Mindy Pereira46ce0b12012-01-05 10:32:15 -0800903 mQuotedTextView = (QuotedTextView) findViewById(R.id.quoted_text_view);
904 mQuotedTextView.setRespondInlineListener(this);
Mindy Pereira433b1982012-04-03 11:53:07 -0700905 mBodyView = (EditText) findViewById(R.id.body);
Mindy Pereira1a95a572012-01-05 12:21:29 -0800906 mFromStatic = findViewById(R.id.static_from_content);
Mindy Pereira2eb17322012-03-07 10:07:34 -0800907 mFromStaticText = (TextView) findViewById(R.id.from_account_name);
Mindy Pereiraeaea9f12012-01-10 15:05:27 -0800908 mFromSpinnerWrapper = findViewById(R.id.spinner_from_content);
Mindy Pereira5a85e2b2012-01-11 09:53:32 -0800909 mFromSpinner = (FromAddressSpinner) findViewById(R.id.from_picker);
Mindy Pereira6349a042012-01-04 11:25:01 -0800910 }
911
mindyp62d3ec72012-08-24 13:04:09 -0700912 @Override
913 public boolean onEditorAction(TextView view, int action, KeyEvent keyEvent) {
914 if (action == EditorInfo.IME_ACTION_DONE) {
915 focusBody();
916 return true;
917 }
918 return false;
919 }
920
Mindy Pereirae011b1d2012-06-18 13:45:26 -0700921 protected TextView getBody() {
922 return mBodyView;
923 }
924
925 @VisibleForTesting
926 public Account getFromAccount() {
927 return mReplyFromAccount != null && mReplyFromAccount.account != null ?
928 mReplyFromAccount.account : mAccount;
929 }
930
Mindy Pereiracbfb75a2012-06-25 14:52:23 -0700931 private void clearChangeListeners() {
932 mSubject.removeTextChangedListener(this);
933 mBodyView.removeTextChangedListener(this);
934 mTo.removeTextChangedListener(mToListener);
935 mCc.removeTextChangedListener(mCcListener);
936 mBcc.removeTextChangedListener(mBccListener);
937 mFromSpinner.setOnAccountChangedListener(null);
938 mAttachmentsView.setAttachmentChangesListener(null);
939 }
940
Mindy Pereira75f66632012-01-11 11:42:02 -0800941 // Now that the message has been initialized from any existing draft or
942 // ref message data, set up listeners for any changes that occur to the
943 // message.
944 private void initChangeListeners() {
945 mSubject.addTextChangedListener(this);
946 mBodyView.addTextChangedListener(this);
Mindy Pereiracbfb75a2012-06-25 14:52:23 -0700947 if (mToListener == null) {
948 mToListener = new RecipientTextWatcher(mTo, this);
949 }
950 mTo.addTextChangedListener(mToListener);
951 if (mCcListener == null) {
952 mCcListener = new RecipientTextWatcher(mCc, this);
953 }
954 mCc.addTextChangedListener(mCcListener);
955 if (mBccListener == null) {
956 mBccListener = new RecipientTextWatcher(mBcc, this);
957 }
958 mBcc.addTextChangedListener(mBccListener);
Mindy Pereira75f66632012-01-11 11:42:02 -0800959 mFromSpinner.setOnAccountChangedListener(this);
Mindy Pereira818143e2012-01-11 13:59:49 -0800960 mAttachmentsView.setAttachmentChangesListener(this);
Mindy Pereira75f66632012-01-11 11:42:02 -0800961 }
962
Mindy Pereira326c6602012-01-04 15:32:42 -0800963 private void initActionBar(int action) {
964 mComposeMode = action;
Mindy Pereiraeaea9f12012-01-10 15:05:27 -0800965 ActionBar actionBar = getActionBar();
Mindy Pereirae011b1d2012-06-18 13:45:26 -0700966 if (actionBar == null) {
967 return;
968 }
Mindy Pereira326c6602012-01-04 15:32:42 -0800969 if (action == ComposeActivity.COMPOSE) {
Mindy Pereiraeaea9f12012-01-10 15:05:27 -0800970 actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
971 actionBar.setTitle(R.string.compose);
Mindy Pereira326c6602012-01-04 15:32:42 -0800972 } else {
Mindy Pereiraeaea9f12012-01-10 15:05:27 -0800973 actionBar.setTitle(null);
Mindy Pereira326c6602012-01-04 15:32:42 -0800974 if (mComposeModeAdapter == null) {
975 mComposeModeAdapter = new ComposeModeAdapter(this);
976 }
Mindy Pereiraeaea9f12012-01-10 15:05:27 -0800977 actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
978 actionBar.setListNavigationCallbacks(mComposeModeAdapter, this);
Mindy Pereira326c6602012-01-04 15:32:42 -0800979 switch (action) {
980 case ComposeActivity.REPLY:
Mindy Pereiraeaea9f12012-01-10 15:05:27 -0800981 actionBar.setSelectedNavigationItem(0);
Mindy Pereira326c6602012-01-04 15:32:42 -0800982 break;
983 case ComposeActivity.REPLY_ALL:
Mindy Pereiraeaea9f12012-01-10 15:05:27 -0800984 actionBar.setSelectedNavigationItem(1);
Mindy Pereira326c6602012-01-04 15:32:42 -0800985 break;
986 case ComposeActivity.FORWARD:
Mindy Pereiraeaea9f12012-01-10 15:05:27 -0800987 actionBar.setSelectedNavigationItem(2);
Mindy Pereira326c6602012-01-04 15:32:42 -0800988 break;
989 }
990 }
Mindy Pereirafbe40192012-03-20 10:40:45 -0700991 actionBar.setDisplayOptions(ActionBar.DISPLAY_HOME_AS_UP | ActionBar.DISPLAY_SHOW_HOME,
992 ActionBar.DISPLAY_HOME_AS_UP | ActionBar.DISPLAY_SHOW_HOME);
993 actionBar.setHomeButtonEnabled(true);
Mindy Pereira326c6602012-01-04 15:32:42 -0800994 }
995
Mindy Pereira46ce0b12012-01-05 10:32:15 -0800996 private void initFromRefMessage(int action, String recipientAddress) {
Mindy Pereira96a7f7a2012-07-09 16:51:06 -0700997 setFieldsFromRefMessage(action, recipientAddress);
998 if (mRefMessage != null) {
999 // CC field only gets populated when doing REPLY_ALL.
1000 // BCC never gets auto-populated, unless the user is editing
1001 // a draft with one.
Mindy Pereira29a717e2012-07-25 18:05:48 -07001002 if (!TextUtils.isEmpty(mCc.getText()) && action == REPLY_ALL) {
Mindy Pereira96a7f7a2012-07-09 16:51:06 -07001003 mCcBccView.show(false, true, false);
1004 }
1005 }
1006 updateHideOrShowCcBcc();
1007 }
1008
1009 private void setFieldsFromRefMessage(int action, String recipientAddress) {
Mindy Pereira8eca57a2012-03-20 16:42:34 -07001010 setSubject(mRefMessage, action);
1011 // Setup recipients
1012 if (action == FORWARD) {
1013 mForward = true;
Mindy Pereira6349a042012-01-04 11:25:01 -08001014 }
Mindy Pereira8eca57a2012-03-20 16:42:34 -07001015 initRecipientsFromRefMessage(recipientAddress, mRefMessage, action);
Mindy Pereirae8f94dc2012-04-16 11:56:21 -07001016 initQuotedTextFromRefMessage(mRefMessage, action);
Mindy Pereira8eca57a2012-03-20 16:42:34 -07001017 if (action == ComposeActivity.FORWARD || mAttachmentsChanged) {
1018 initAttachments(mRefMessage);
1019 }
Mindy Pereirac17d0732011-12-29 10:46:19 -08001020 }
1021
Mindy Pereirae8f94dc2012-04-16 11:56:21 -07001022 private void initFromDraftMessage(Message message) {
Paul Westbrookbb87b7f2012-03-20 16:20:07 -07001023 LogUtils.d(LOG_TAG, "Intializing draft from previous draft message");
1024
1025 mDraft = message;
1026 mDraftId = message.id;
1027 mSubject.setText(message.subject);
1028 mForward = message.draftType == UIProvider.DraftType.FORWARD;
1029 final List<String> toAddresses = Arrays.asList(message.getToAddresses());
1030 addToAddresses(toAddresses);
1031 addCcAddresses(Arrays.asList(message.getCcAddresses()), toAddresses);
1032 addBccAddresses(Arrays.asList(message.getBccAddresses()));
Mindy Pereira2421dc82012-03-27 13:32:31 -07001033 if (message.hasAttachments) {
1034 List<Attachment> attachments = message.getAttachments();
1035 for (Attachment a : attachments) {
Andy Huang5c5fd572012-04-08 18:19:29 -07001036 addAttachmentAndUpdateView(a);
Mindy Pereira2421dc82012-03-27 13:32:31 -07001037 }
1038 }
Mindy Pereiracc8e7db2012-05-30 12:57:42 -07001039 int quotedTextIndex = message.appendRefMessageContent ?
Mindy Pereira002ff522012-05-30 10:31:26 -07001040 message.quotedTextOffset : -1;
Paul Westbrookbb87b7f2012-03-20 16:20:07 -07001041 // Set the body
Mindy Pereira002ff522012-05-30 10:31:26 -07001042 CharSequence quotedText = null;
Paul Westbrookbb87b7f2012-03-20 16:20:07 -07001043 if (!TextUtils.isEmpty(message.bodyHtml)) {
Mindy Pereira752222d2012-07-19 09:58:53 -07001044 CharSequence htmlText = "";
Mindy Pereira002ff522012-05-30 10:31:26 -07001045 if (quotedTextIndex > -1) {
Mindy Pereira752222d2012-07-19 09:58:53 -07001046 // Find the offset in the htmltext of the actual quoted text and strip it out.
1047 quotedTextIndex = QuotedTextView.findQuotedTextIndex(message.bodyHtml);
1048 if (quotedTextIndex > -1) {
1049 htmlText = Html.fromHtml(message.bodyHtml.substring(0, quotedTextIndex));
1050 quotedText = message.bodyHtml.subSequence(quotedTextIndex,
1051 message.bodyHtml.length());
1052 }
Mindy Pereira1a6e9382012-08-14 15:51:22 -07001053 } else {
1054 htmlText = Html.fromHtml(message.bodyHtml);
Mindy Pereira002ff522012-05-30 10:31:26 -07001055 }
1056 mBodyView.setText(htmlText);
Paul Westbrookbb87b7f2012-03-20 16:20:07 -07001057 } else {
Mindy Pereira752222d2012-07-19 09:58:53 -07001058 final String body = message.bodyText;
1059 final CharSequence bodyText = !TextUtils.isEmpty(body) ?
1060 (quotedTextIndex > -1 ?
1061 message.bodyText.substring(0, quotedTextIndex) : message.bodyText)
1062 : "";
Mindy Pereira002ff522012-05-30 10:31:26 -07001063 if (quotedTextIndex > -1) {
Mindy Pereira752222d2012-07-19 09:58:53 -07001064 quotedText = !TextUtils.isEmpty(body) ? message.bodyText.substring(quotedTextIndex)
1065 : null;
Mindy Pereira002ff522012-05-30 10:31:26 -07001066 }
1067 mBodyView.setText(bodyText);
1068 }
1069 if (quotedTextIndex > -1 && quotedText != null) {
Mindy Pereira39713232012-05-30 11:48:41 -07001070 mQuotedTextView.setQuotedTextFromDraft(quotedText, mForward);
Paul Westbrookbb87b7f2012-03-20 16:20:07 -07001071 }
Paul Westbrookbb87b7f2012-03-20 16:20:07 -07001072 }
1073
Mindy Pereira8eca57a2012-03-20 16:42:34 -07001074 /**
1075 * Fill all the widgets with the content found in the Intent Extra, if any.
1076 * Also apply the same style to all widgets. Note: if initFromExtras is
1077 * called as a result of switching between reply, reply all, and forward per
1078 * the latest revision of Gmail, and the user has already made changes to
1079 * attachments on a previous incarnation of the message (as a reply, reply
1080 * all, or forward), the original attachments from the message will not be
1081 * re-instantiated. The user's changes will be respected. This follows the
1082 * web gmail interaction.
1083 */
1084 public void initFromExtras(Intent intent) {
Mindy Pereira8eca57a2012-03-20 16:42:34 -07001085 // If we were invoked with a SENDTO intent, the value
1086 // should take precedence
1087 final Uri dataUri = intent.getData();
1088 if (dataUri != null) {
1089 if (MAIL_TO.equals(dataUri.getScheme())) {
1090 initFromMailTo(dataUri.toString());
1091 } else {
Mindy Pereira0b4f28e2012-03-28 14:12:21 -07001092 if (!mAccount.composeIntentUri.equals(dataUri)) {
Mindy Pereira8eca57a2012-03-20 16:42:34 -07001093 String toText = dataUri.getSchemeSpecificPart();
1094 if (toText != null) {
1095 mTo.setText("");
Mindy Pereiradbe89962012-04-13 09:42:38 -07001096 addToAddresses(Arrays.asList(TextUtils.split(toText, ",")));
Mindy Pereira8eca57a2012-03-20 16:42:34 -07001097 }
1098 }
1099 }
1100 }
1101
1102 String[] extraStrings = intent.getStringArrayExtra(Intent.EXTRA_EMAIL);
1103 if (extraStrings != null) {
1104 addToAddresses(Arrays.asList(extraStrings));
1105 }
1106 extraStrings = intent.getStringArrayExtra(Intent.EXTRA_CC);
1107 if (extraStrings != null) {
1108 addCcAddresses(Arrays.asList(extraStrings), null);
1109 }
1110 extraStrings = intent.getStringArrayExtra(Intent.EXTRA_BCC);
1111 if (extraStrings != null) {
1112 addBccAddresses(Arrays.asList(extraStrings));
1113 }
1114
1115 String extraString = intent.getStringExtra(Intent.EXTRA_SUBJECT);
1116 if (extraString != null) {
1117 mSubject.setText(extraString);
1118 }
1119
1120 for (String extra : ALL_EXTRAS) {
1121 if (intent.hasExtra(extra)) {
1122 String value = intent.getStringExtra(extra);
1123 if (EXTRA_TO.equals(extra)) {
Mindy Pereiradbe89962012-04-13 09:42:38 -07001124 addToAddresses(Arrays.asList(TextUtils.split(value, ",")));
Mindy Pereira8eca57a2012-03-20 16:42:34 -07001125 } else if (EXTRA_CC.equals(extra)) {
Mindy Pereiradbe89962012-04-13 09:42:38 -07001126 addCcAddresses(Arrays.asList(TextUtils.split(value, ",")), null);
Mindy Pereira8eca57a2012-03-20 16:42:34 -07001127 } else if (EXTRA_BCC.equals(extra)) {
Mindy Pereiradbe89962012-04-13 09:42:38 -07001128 addBccAddresses(Arrays.asList(TextUtils.split(value, ",")));
Mindy Pereira8eca57a2012-03-20 16:42:34 -07001129 } else if (EXTRA_SUBJECT.equals(extra)) {
1130 mSubject.setText(value);
1131 } else if (EXTRA_BODY.equals(extra)) {
1132 setBody(value, true /* with signature */);
1133 }
1134 }
1135 }
1136
1137 Bundle extras = intent.getExtras();
1138 if (extras != null) {
Mindy Pereira8eca57a2012-03-20 16:42:34 -07001139 CharSequence text = extras.getCharSequence(Intent.EXTRA_TEXT);
1140 if (text != null) {
1141 setBody(text, true /* with signature */);
1142 }
Mindy Pereira8eca57a2012-03-20 16:42:34 -07001143 }
Mindy Pereirae8f94dc2012-04-16 11:56:21 -07001144 }
1145
Mindy Pereira8eca57a2012-03-20 16:42:34 -07001146 @VisibleForTesting
1147 protected String decodeEmailInUri(String s) throws UnsupportedEncodingException {
Mindy Pereiraa4069f22012-05-30 15:31:45 -07001148 // TODO: handle the case where there are spaces in the display name as
1149 // well as the email such as "Guy with spaces <guy+with+spaces@gmail.com>"
1150 // as they could be encoded ambiguously.
Mindy Pereira8eca57a2012-03-20 16:42:34 -07001151 // Since URLDecode.decode changes + into ' ', and + is a valid
1152 // email character, we need to find/ replace these ourselves before
1153 // decoding.
1154 String replacePlus = s.replace("+", "%2B");
Mindy Pereiraa4069f22012-05-30 15:31:45 -07001155 try {
1156 return URLDecoder.decode(replacePlus, UTF8_ENCODING_NAME);
1157 } catch (IllegalArgumentException e) {
1158 if (LogUtils.isLoggable(LOG_TAG, LogUtils.VERBOSE)) {
1159 LogUtils.e(LOG_TAG, "%s while decoding '%s'", e.getMessage(), s);
1160 } else {
1161 LogUtils.e(LOG_TAG, e, "Exception while decoding mailto address");
1162 }
1163 return null;
1164 }
Mindy Pereira8eca57a2012-03-20 16:42:34 -07001165 }
1166
1167 /**
1168 * Initialize the compose view from a String representing a mailTo uri.
1169 * @param mailToString The uri as a string.
1170 */
1171 public void initFromMailTo(String mailToString) {
1172 // We need to disguise this string as a URI in order to parse it
1173 // TODO: Remove this hack when http://b/issue?id=1445295 gets fixed
1174 Uri uri = Uri.parse("foo://" + mailToString);
1175 int index = mailToString.indexOf("?");
1176 int length = "mailto".length() + 1;
1177 String to;
1178 try {
1179 // Extract the recipient after mailto:
1180 if (index == -1) {
1181 to = decodeEmailInUri(mailToString.substring(length));
1182 } else {
1183 to = decodeEmailInUri(mailToString.substring(length, index));
1184 }
Mindy Pereiraa4069f22012-05-30 15:31:45 -07001185 if (!TextUtils.isEmpty(to)) {
1186 addToAddresses(Arrays.asList(TextUtils.split(to, ",")));
1187 }
Mindy Pereira8eca57a2012-03-20 16:42:34 -07001188 } catch (UnsupportedEncodingException e) {
1189 if (LogUtils.isLoggable(LOG_TAG, LogUtils.VERBOSE)) {
1190 LogUtils.e(LOG_TAG, "%s while decoding '%s'", e.getMessage(), mailToString);
1191 } else {
1192 LogUtils.e(LOG_TAG, e, "Exception while decoding mailto address");
1193 }
1194 }
1195
1196 List<String> cc = uri.getQueryParameters("cc");
1197 addCcAddresses(Arrays.asList(cc.toArray(new String[cc.size()])), null);
1198
1199 List<String> otherTo = uri.getQueryParameters("to");
1200 addToAddresses(Arrays.asList(otherTo.toArray(new String[otherTo.size()])));
1201
1202 List<String> bcc = uri.getQueryParameters("bcc");
1203 addBccAddresses(Arrays.asList(bcc.toArray(new String[bcc.size()])));
1204
1205 List<String> subject = uri.getQueryParameters("subject");
1206 if (subject.size() > 0) {
1207 try {
1208 mSubject.setText(URLDecoder.decode(subject.get(0), UTF8_ENCODING_NAME));
1209 } catch (UnsupportedEncodingException e) {
1210 LogUtils.e(LOG_TAG, "%s while decoding subject '%s'",
1211 e.getMessage(), subject);
1212 }
1213 }
1214
1215 List<String> body = uri.getQueryParameters("body");
1216 if (body.size() > 0) {
1217 try {
1218 setBody(URLDecoder.decode(body.get(0), UTF8_ENCODING_NAME),
1219 true /* with signature */);
1220 } catch (UnsupportedEncodingException e) {
1221 LogUtils.e(LOG_TAG, "%s while decoding body '%s'", e.getMessage(), body);
1222 }
1223 }
Mindy Pereira8eca57a2012-03-20 16:42:34 -07001224 }
1225
Mindy Pereirabddd6f32012-06-20 12:10:03 -07001226 @VisibleForTesting
1227 protected void initAttachments(Message refMessage) {
Mark Wei434f2942012-08-24 11:54:02 -07001228 addAttachments(refMessage.getAttachments());
1229 }
1230
1231 public long addAttachments(List<Attachment> attachments) {
1232 long size = 0;
1233 AttachmentFailureException error = null;
1234 for (Attachment a : attachments) {
1235 try {
1236 size += mAttachmentsView.addAttachment(mAccount, a);
1237 } catch (AttachmentFailureException e) {
1238 error = e;
1239 }
Mindy Pereira3cd4f402012-07-17 11:16:18 -07001240 }
Mark Wei434f2942012-08-24 11:54:02 -07001241 if (error != null) {
1242 LogUtils.e(LOG_TAG, error, "Error adding attachment");
1243 if (attachments.size() > 1) {
1244 showAttachmentTooBigToast(R.string.too_large_to_attach_multiple);
1245 } else {
1246 showAttachmentTooBigToast(error.getErrorRes());
1247 }
1248 }
1249 return size;
Mindy Pereira3cd4f402012-07-17 11:16:18 -07001250 }
1251
1252 /**
1253 * When an attachment is too large to be added to a message, show a toast.
1254 * This method also updates the position of the toast so that it is shown
1255 * clearly above they keyboard if it happens to be open.
1256 */
Mark Wei434f2942012-08-24 11:54:02 -07001257 private void showAttachmentTooBigToast(int errorRes) {
1258 String maxSize = AttachmentUtils.convertToHumanReadableSize(
1259 getApplicationContext(), mAccount.settings.getMaxAttachmentSize());
1260 showErrorToast(getString(errorRes, maxSize));
Mindy Pereira3cd4f402012-07-17 11:16:18 -07001261 }
1262
Mark Wei434f2942012-08-24 11:54:02 -07001263 private void showErrorToast(String message) {
1264 Toast t = Toast.makeText(this, message, Toast.LENGTH_LONG);
1265 t.setText(message);
Mindy Pereira3cd4f402012-07-17 11:16:18 -07001266 t.setGravity(Gravity.CENTER_HORIZONTAL, 0,
1267 getResources().getDimensionPixelSize(R.dimen.attachment_toast_yoffset));
1268 t.show();
Mindy Pereiraeaea9f12012-01-10 15:05:27 -08001269 }
1270
Paul Westbrookf97588b2012-03-20 11:11:37 -07001271 private void initAttachmentsFromIntent(Intent intent) {
Paul Westbrook03ee9712012-04-02 09:51:51 -07001272 Bundle extras = intent.getExtras();
1273 if (extras == null) {
1274 extras = Bundle.EMPTY;
1275 }
Paul Westbrookf97588b2012-03-20 11:11:37 -07001276 final String action = intent.getAction();
1277 if (!mAttachmentsChanged) {
1278 long totalSize = 0;
1279 if (extras.containsKey(EXTRA_ATTACHMENTS)) {
1280 String[] uris = (String[]) extras.getSerializable(EXTRA_ATTACHMENTS);
1281 for (String uriString : uris) {
1282 final Uri uri = Uri.parse(uriString);
1283 long size = 0;
1284 try {
Andy Huang5c5fd572012-04-08 18:19:29 -07001285 size = mAttachmentsView.addAttachment(mAccount, uri);
Paul Westbrookf97588b2012-03-20 11:11:37 -07001286 } catch (AttachmentFailureException e) {
Paul Westbrookf97588b2012-03-20 11:11:37 -07001287 LogUtils.e(LOG_TAG, e, "Error adding attachment");
Mark Wei434f2942012-08-24 11:54:02 -07001288 showAttachmentTooBigToast(e.getErrorRes());
Paul Westbrookf97588b2012-03-20 11:11:37 -07001289 }
1290 totalSize += size;
1291 }
1292 }
mindyp9a9e8d62012-10-03 12:24:07 -07001293 if (extras.containsKey(Intent.EXTRA_STREAM)) {
1294 if (Intent.ACTION_SEND_MULTIPLE.equals(action)) {
1295 ArrayList<Parcelable> uris = extras
1296 .getParcelableArrayList(Intent.EXTRA_STREAM);
1297 ArrayList<Attachment> attachments = new ArrayList<Attachment>();
1298 for (Parcelable uri : uris) {
1299 try {
1300 attachments.add(mAttachmentsView.generateLocalAttachment((Uri) uri));
1301 } catch (AttachmentFailureException e) {
1302 LogUtils.e(LOG_TAG, e, "Error adding attachment");
1303 String maxSize = AttachmentUtils.convertToHumanReadableSize(
1304 getApplicationContext(),
1305 mAccount.settings.getMaxAttachmentSize());
1306 showErrorToast(getString
1307 (R.string.generic_attachment_problem, maxSize));
1308 }
1309 }
1310 totalSize += addAttachments(attachments);
1311 } else {
1312 final Uri uri = (Uri) extras.getParcelable(Intent.EXTRA_STREAM);
1313 long size = 0;
Paul Westbrookf97588b2012-03-20 11:11:37 -07001314 try {
mindyp9a9e8d62012-10-03 12:24:07 -07001315 size = mAttachmentsView.addAttachment(mAccount, uri);
Paul Westbrookf97588b2012-03-20 11:11:37 -07001316 } catch (AttachmentFailureException e) {
Paul Westbrookf97588b2012-03-20 11:11:37 -07001317 LogUtils.e(LOG_TAG, e, "Error adding attachment");
mindyp9a9e8d62012-10-03 12:24:07 -07001318 showAttachmentTooBigToast(e.getErrorRes());
Paul Westbrookf97588b2012-03-20 11:11:37 -07001319 }
mindyp9a9e8d62012-10-03 12:24:07 -07001320 totalSize += size;
Paul Westbrookf97588b2012-03-20 11:11:37 -07001321 }
1322 }
1323
1324 if (totalSize > 0) {
1325 mAttachmentsChanged = true;
1326 updateSaveUi();
1327 }
1328 }
1329 }
1330
1331
Mindy Pereirae8f94dc2012-04-16 11:56:21 -07001332 private void initQuotedTextFromRefMessage(Message refMessage, int action) {
1333 if (mRefMessage != null && (action == REPLY || action == REPLY_ALL || action == FORWARD)) {
Mindy Pereira9932dee2012-01-10 16:09:50 -08001334 mQuotedTextView.setQuotedText(action, refMessage, action != FORWARD);
1335 }
Mindy Pereira46ce0b12012-01-05 10:32:15 -08001336 }
1337
1338 private void updateHideOrShowCcBcc() {
Mindy Pereiraec8b0ed2012-01-06 10:35:59 -08001339 // Its possible there is a menu item OR a button.
Mindy Pereira326689d2012-05-17 10:14:14 -07001340 boolean ccVisible = mCcBccView.isCcVisible();
1341 boolean bccVisible = mCcBccView.isBccVisible();
Mindy Pereiraec8b0ed2012-01-06 10:35:59 -08001342 if (mCcBccButton != null) {
Mindy Pereira326689d2012-05-17 10:14:14 -07001343 if (!ccVisible || !bccVisible) {
Mindy Pereiraec8b0ed2012-01-06 10:35:59 -08001344 mCcBccButton.setVisibility(View.VISIBLE);
Mindy Pereira326689d2012-05-17 10:14:14 -07001345 mCcBccButton.setText(getString(!ccVisible ? R.string.add_cc_label
Mindy Pereiraec8b0ed2012-01-06 10:35:59 -08001346 : R.string.add_bcc_label));
1347 } else {
mindypcd0b0b92012-08-23 14:33:17 -07001348 mCcBccButton.setVisibility(View.INVISIBLE);
Mindy Pereiraec8b0ed2012-01-06 10:35:59 -08001349 }
1350 }
Mindy Pereira46ce0b12012-01-05 10:32:15 -08001351 }
1352
Mindy Pereiraa34c9a02012-04-17 14:10:53 -07001353 private void showCcBcc(Bundle state) {
Mindy Pereira326689d2012-05-17 10:14:14 -07001354 if (state != null && state.containsKey(EXTRA_SHOW_CC)) {
1355 boolean showCc = state.getBoolean(EXTRA_SHOW_CC);
1356 boolean showBcc = state.getBoolean(EXTRA_SHOW_BCC);
1357 if (showCc || showBcc) {
1358 mCcBccView.show(false, showCc, showBcc);
Mindy Pereira6faeedf2012-04-18 16:11:39 -07001359 }
Mindy Pereiraa34c9a02012-04-17 14:10:53 -07001360 }
1361 }
1362
Mindy Pereira013194c2012-01-06 15:09:33 -08001363 /**
1364 * Add attachment and update the compose area appropriately.
1365 * @param data
1366 */
1367 public void addAttachmentAndUpdateView(Intent data) {
Mindy Pereira2421dc82012-03-27 13:32:31 -07001368 addAttachmentAndUpdateView(data != null ? data.getData() : (Uri) null);
1369 }
1370
Andy Huang5c5fd572012-04-08 18:19:29 -07001371 public void addAttachmentAndUpdateView(Uri contentUri) {
1372 if (contentUri == null) {
Mindy Pereira2421dc82012-03-27 13:32:31 -07001373 return;
1374 }
Mindy Pereira013194c2012-01-06 15:09:33 -08001375 try {
Andy Huang5c5fd572012-04-08 18:19:29 -07001376 addAttachmentAndUpdateView(mAttachmentsView.generateLocalAttachment(contentUri));
1377 } catch (AttachmentFailureException e) {
Andy Huang5c5fd572012-04-08 18:19:29 -07001378 LogUtils.e(LOG_TAG, e, "Error adding attachment");
Mark Wei434f2942012-08-24 11:54:02 -07001379 showErrorToast(getResources().getString(
1380 e.getErrorRes(),
1381 AttachmentUtils.convertToHumanReadableSize(
1382 getApplicationContext(), mAccount.settings.getMaxAttachmentSize())));
Andy Huang5c5fd572012-04-08 18:19:29 -07001383 }
1384 }
1385
1386 public void addAttachmentAndUpdateView(Attachment attachment) {
1387 try {
Mark Wei434f2942012-08-24 11:54:02 -07001388 long size = mAttachmentsView.addAttachment(mAccount, attachment);
Mindy Pereira9932dee2012-01-10 16:09:50 -08001389 if (size > 0) {
1390 mAttachmentsChanged = true;
1391 updateSaveUi();
Mindy Pereira013194c2012-01-06 15:09:33 -08001392 }
Mindy Pereira9932dee2012-01-10 16:09:50 -08001393 } catch (AttachmentFailureException e) {
Mindy Pereira9932dee2012-01-10 16:09:50 -08001394 LogUtils.e(LOG_TAG, e, "Error adding attachment");
Mark Wei434f2942012-08-24 11:54:02 -07001395 showAttachmentTooBigToast(e.getErrorRes());
Mindy Pereira013194c2012-01-06 15:09:33 -08001396 }
1397 }
1398
Mindy Pereira3ce64e72012-01-13 14:29:45 -08001399 void initRecipientsFromRefMessage(String recipientAddress, Message refMessage,
Mindy Pereira46ce0b12012-01-05 10:32:15 -08001400 int action) {
Mindy Pereira4a27ea92012-01-05 15:55:25 -08001401 // Don't populate the address if this is a forward.
1402 if (action == ComposeActivity.FORWARD) {
1403 return;
1404 }
Mindy Pereira33fe9082012-01-09 16:24:30 -08001405 initReplyRecipients(mAccount.name, refMessage, action);
Mindy Pereira4a27ea92012-01-05 15:55:25 -08001406 }
Mindy Pereira46ce0b12012-01-05 10:32:15 -08001407
Mindy Pereira818143e2012-01-11 13:59:49 -08001408 @VisibleForTesting
Mindy Pereira3ce64e72012-01-13 14:29:45 -08001409 void initReplyRecipients(String account, Message refMessage, int action) {
Mindy Pereira4a27ea92012-01-05 15:55:25 -08001410 // This is the email address of the current user, i.e. the one composing
1411 // the reply.
Mindy Pereira4a20b702012-01-05 16:24:24 -08001412 final String accountEmail = Address.getEmailAddress(account).getAddress();
Mindy Pereira1469b4e2012-06-19 19:18:54 -07001413 String fromAddress = getAddress(refMessage.from);
mindyp5ed63112012-09-17 17:31:45 -07001414 String[] sentToAddresses = refMessage.getToAddresses();
Mindy Pereira3ce64e72012-01-13 14:29:45 -08001415 String replytoAddress = refMessage.replyTo;
Mindy Pereiraa26b54e2012-01-06 12:54:33 -08001416 final Collection<String> toAddresses;
Mindy Pereira4a27ea92012-01-05 15:55:25 -08001417
1418 // If this is a reply, the Cc list is empty. If this is a reply-all, the
1419 // Cc list is the union of the To and Cc recipients of the original
1420 // message, excluding the current user's email address and any addresses
Mindy Pereiraa26b54e2012-01-06 12:54:33 -08001421 // already on the To list.
1422 if (action == ComposeActivity.REPLY) {
Mindy Pereira3ce64e72012-01-13 14:29:45 -08001423 toAddresses = initToRecipients(account, accountEmail, fromAddress, replytoAddress,
Mindy Pereira1469b4e2012-06-19 19:18:54 -07001424 sentToAddresses);
Mindy Pereiraa26b54e2012-01-06 12:54:33 -08001425 addToAddresses(toAddresses);
1426 } else if (action == ComposeActivity.REPLY_ALL) {
Mindy Pereira4a27ea92012-01-05 15:55:25 -08001427 final Set<String> ccAddresses = Sets.newHashSet();
Mindy Pereira3ce64e72012-01-13 14:29:45 -08001428 toAddresses = initToRecipients(account, accountEmail, fromAddress, replytoAddress,
Mindy Pereira9b18a9a2012-07-25 18:13:57 -07001429 sentToAddresses);
Mindy Pereira154386a2012-01-11 13:02:33 -08001430 addToAddresses(toAddresses);
Mindy Pereira4a27ea92012-01-05 15:55:25 -08001431 addRecipients(accountEmail, ccAddresses, sentToAddresses);
mindyp5ed63112012-09-17 17:31:45 -07001432 addRecipients(accountEmail, ccAddresses, refMessage.getCcAddresses());
Mindy Pereira4a27ea92012-01-05 15:55:25 -08001433 addCcAddresses(ccAddresses, toAddresses);
1434 }
1435 }
1436
Mindy Pereira1469b4e2012-06-19 19:18:54 -07001437 private String getAddress(String toParse) {
1438 if (!TextUtils.isEmpty(toParse)) {
1439 Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(toParse);
1440 if (tokens.length > 0) {
1441 return tokens[0].getAddress();
1442 }
1443 }
1444 return "";
1445 }
1446
Mindy Pereira4a27ea92012-01-05 15:55:25 -08001447 private void addToAddresses(Collection<String> addresses) {
1448 addAddressesToList(addresses, mTo);
1449 }
1450
1451 private void addCcAddresses(Collection<String> addresses, Collection<String> toAddresses) {
Mindy Pereira8eca57a2012-03-20 16:42:34 -07001452 addCcAddressesToList(tokenizeAddressList(addresses),
1453 toAddresses != null ? tokenizeAddressList(toAddresses) : null, mCc);
Mindy Pereira4a27ea92012-01-05 15:55:25 -08001454 }
1455
Paul Westbrookbb87b7f2012-03-20 16:20:07 -07001456 private void addBccAddresses(Collection<String> addresses) {
1457 addAddressesToList(addresses, mBcc);
1458 }
1459
Mindy Pereira4a27ea92012-01-05 15:55:25 -08001460 @VisibleForTesting
1461 protected void addCcAddressesToList(List<Rfc822Token[]> addresses,
1462 List<Rfc822Token[]> compareToList, RecipientEditTextView list) {
1463 String address;
1464
Mindy Pereira8eca57a2012-03-20 16:42:34 -07001465 if (compareToList == null) {
1466 for (Rfc822Token[] tokens : addresses) {
1467 for (int i = 0; i < tokens.length; i++) {
1468 address = tokens[i].toString();
Mindy Pereira4a27ea92012-01-05 15:55:25 -08001469 list.append(address + END_TOKEN);
1470 }
1471 }
Mindy Pereira8eca57a2012-03-20 16:42:34 -07001472 } else {
1473 HashSet<String> compareTo = convertToHashSet(compareToList);
1474 for (Rfc822Token[] tokens : addresses) {
1475 for (int i = 0; i < tokens.length; i++) {
1476 address = tokens[i].toString();
1477 // Check if this is a duplicate:
1478 if (!compareTo.contains(tokens[i].getAddress())) {
1479 // Get the address here
1480 list.append(address + END_TOKEN);
1481 }
1482 }
1483 }
Mindy Pereira4a27ea92012-01-05 15:55:25 -08001484 }
1485 }
1486
Mindy Pereira4a27ea92012-01-05 15:55:25 -08001487 private HashSet<String> convertToHashSet(List<Rfc822Token[]> list) {
1488 HashSet<String> hash = new HashSet<String>();
1489 for (Rfc822Token[] tokens : list) {
1490 for (int i = 0; i < tokens.length; i++) {
1491 hash.add(tokens[i].getAddress());
1492 }
1493 }
1494 return hash;
1495 }
1496
Mindy Pereira4a27ea92012-01-05 15:55:25 -08001497 protected List<Rfc822Token[]> tokenizeAddressList(Collection<String> addresses) {
1498 @VisibleForTesting
1499 List<Rfc822Token[]> tokenized = new ArrayList<Rfc822Token[]>();
1500
1501 for (String address: addresses) {
1502 tokenized.add(Rfc822Tokenizer.tokenize(address));
1503 }
1504 return tokenized;
1505 }
1506
1507 @VisibleForTesting
1508 void addAddressesToList(Collection<String> addresses, RecipientEditTextView list) {
1509 for (String address : addresses) {
1510 addAddressToList(address, list);
1511 }
1512 }
1513
1514 private void addAddressToList(String address, RecipientEditTextView list) {
1515 if (address == null || list == null)
1516 return;
1517
1518 Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(address);
1519
1520 for (int i = 0; i < tokens.length; i++) {
1521 list.append(tokens[i] + END_TOKEN);
1522 }
1523 }
1524
1525 @VisibleForTesting
1526 protected Collection<String> initToRecipients(String account, String accountEmail,
Mindy Pereira3ce64e72012-01-13 14:29:45 -08001527 String senderAddress, String replyToAddress, String[] inToAddresses) {
Mindy Pereira4a27ea92012-01-05 15:55:25 -08001528 // The To recipient is the reply-to address specified in the original
1529 // message, unless it is:
1530 // the current user OR a custom from of the current user, in which case
1531 // it's the To recipient list of the original message.
1532 // OR missing, in which case use the sender of the original message
1533 Set<String> toAddresses = Sets.newHashSet();
Mindy Pereira3ce64e72012-01-13 14:29:45 -08001534 if (!TextUtils.isEmpty(replyToAddress)) {
1535 toAddresses.add(replyToAddress);
Mindy Pereira4a27ea92012-01-05 15:55:25 -08001536 } else {
Mindy Pereiracecc54a2012-07-31 09:38:11 -07001537 if (!recipientMatchesThisAccount(account, senderAddress)) {
Mindy Pereira1469b4e2012-06-19 19:18:54 -07001538 toAddresses.add(senderAddress);
1539 } else {
1540 // This happens if the user replies to a message they originally
Mindy Pereira1883b342012-06-20 08:34:56 -07001541 // wrote. In this case, "reply" really means "re-send," so we
1542 // target the original recipients. This works as expected even
1543 // if the user sent the original message to themselves.
Mindy Pereira1469b4e2012-06-19 19:18:54 -07001544 toAddresses.addAll(Arrays.asList(inToAddresses));
1545 }
Mindy Pereira4a27ea92012-01-05 15:55:25 -08001546 }
1547 return toAddresses;
1548 }
1549
Mindy Pereiracecc54a2012-07-31 09:38:11 -07001550 private void addRecipients(String accountAddress, Set<String> recipients, String[] addresses) {
Mindy Pereira4a27ea92012-01-05 15:55:25 -08001551 for (String email : addresses) {
Mindy Pereiracecc54a2012-07-31 09:38:11 -07001552 // Do not add this account, or any of its custom from addresses, to
1553 // the list of recipients.
Mindy Pereira4a20b702012-01-05 16:24:24 -08001554 final String recipientAddress = Address.getEmailAddress(email).getAddress();
Mindy Pereiracecc54a2012-07-31 09:38:11 -07001555 if (!recipientMatchesThisAccount(accountAddress, recipientAddress)) {
Mindy Pereira4a27ea92012-01-05 15:55:25 -08001556 recipients.add(email.replace("\"\"", ""));
1557 }
1558 }
Mindy Pereira46ce0b12012-01-05 10:32:15 -08001559 }
1560
Mindy Pereiracecc54a2012-07-31 09:38:11 -07001561 /**
1562 * A recipient matches this account if it has the same address as the
1563 * currently selected account OR one of the custom from addresses associated
1564 * with the currently selected account.
1565 * @param accountAddress currently selected account
1566 * @param recipientAddress address we are comparing with the currently selected account
1567 * @return
1568 */
1569 protected boolean recipientMatchesThisAccount(String accountAddress, String recipientAddress) {
1570 return accountAddress.equalsIgnoreCase(recipientAddress)
1571 || ReplyFromAccount.isCustomFrom(recipientAddress,
1572 mFromSpinner.getReplyFromAccounts());
1573 }
1574
Mindy Pereira3ce64e72012-01-13 14:29:45 -08001575 private void setSubject(Message refMessage, int action) {
1576 String subject = refMessage.subject;
Mindy Pereira46ce0b12012-01-05 10:32:15 -08001577 String prefix;
1578 String correctedSubject = null;
1579 if (action == ComposeActivity.COMPOSE) {
1580 prefix = "";
1581 } else if (action == ComposeActivity.FORWARD) {
1582 prefix = getString(R.string.forward_subject_label);
1583 } else {
1584 prefix = getString(R.string.reply_subject_label);
1585 }
1586
1587 // Don't duplicate the prefix
Mindy Pereirac7a36992012-07-30 14:00:37 -07001588 if (!TextUtils.isEmpty(subject)
1589 && subject.toLowerCase().startsWith(prefix.toLowerCase())) {
Mindy Pereira46ce0b12012-01-05 10:32:15 -08001590 correctedSubject = subject;
1591 } else {
1592 correctedSubject = String
1593 .format(getString(R.string.formatted_subject), prefix, subject);
1594 }
1595 mSubject.setText(correctedSubject);
1596 }
1597
Mindy Pereira818143e2012-01-11 13:59:49 -08001598 private void initRecipients() {
1599 setupRecipients(mTo);
1600 setupRecipients(mCc);
1601 setupRecipients(mBcc);
Mindy Pereiraeaea9f12012-01-10 15:05:27 -08001602 }
1603
Mindy Pereira818143e2012-01-11 13:59:49 -08001604 private void setupRecipients(RecipientEditTextView view) {
Paul Westbrook679a8cc2012-02-21 16:37:58 -08001605 view.setAdapter(new RecipientAdapter(this, mAccount));
Mindy Pereirac17d0732011-12-29 10:46:19 -08001606 view.setTokenizer(new Rfc822Tokenizer());
Mindy Pereira82cc5662012-01-09 17:29:30 -08001607 if (mValidator == null) {
Paul Westbrook679a8cc2012-02-21 16:37:58 -08001608 final String accountName = mAccount.name;
Mindy Pereira33fe9082012-01-09 16:24:30 -08001609 int offset = accountName.indexOf("@") + 1;
1610 String account = accountName;
Mindy Pereirac17d0732011-12-29 10:46:19 -08001611 if (offset > -1) {
Mindy Pereira33fe9082012-01-09 16:24:30 -08001612 account = account.substring(accountName.indexOf("@") + 1);
Mindy Pereirac17d0732011-12-29 10:46:19 -08001613 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08001614 mValidator = new Rfc822Validator(account);
Mindy Pereirac17d0732011-12-29 10:46:19 -08001615 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08001616 view.setValidator(mValidator);
Mindy Pereira8e9305e2011-12-13 14:25:04 -08001617 }
1618
1619 @Override
1620 public void onClick(View v) {
1621 int id = v.getId();
1622 switch (id) {
Mindy Pereiraa26b54e2012-01-06 12:54:33 -08001623 case R.id.add_cc_bcc:
Mindy Pereira8e9305e2011-12-13 14:25:04 -08001624 // Verify that cc/ bcc aren't showing.
1625 // Animate in cc/bcc.
Mindy Pereiraa26b54e2012-01-06 12:54:33 -08001626 showCcBccViews();
Mindy Pereira8e9305e2011-12-13 14:25:04 -08001627 break;
mindypcd0b0b92012-08-23 14:33:17 -07001628 case R.id.add_photo_attachment:
1629 doAttach(MIME_TYPE_PHOTO);
1630 break;
1631 case R.id.add_video_attachment:
1632 doAttach(MIME_TYPE_VIDEO);
1633 break;
Mindy Pereira8e9305e2011-12-13 14:25:04 -08001634 }
1635 }
Mindy Pereirab47f3e22011-12-13 14:25:04 -08001636
1637 @Override
1638 public boolean onCreateOptionsMenu(Menu menu) {
1639 super.onCreateOptionsMenu(menu);
Mindy Pereirab199d172012-08-13 11:04:03 -07001640 // Don't render any menu items when there are no accounts.
1641 if (mAccounts == null || mAccounts.length == 0) {
1642 return true;
1643 }
Mindy Pereirab47f3e22011-12-13 14:25:04 -08001644 MenuInflater inflater = getMenuInflater();
1645 inflater.inflate(R.menu.compose_menu, menu);
Mindy Pereira82cc5662012-01-09 17:29:30 -08001646 mSave = menu.findItem(R.id.save);
1647 mSend = menu.findItem(R.id.send);
Mindy Pereira3ca5bad2012-04-16 11:02:42 -07001648 MenuItem helpItem = menu.findItem(R.id.help_info_menu_item);
1649 MenuItem sendFeedbackItem = menu.findItem(R.id.feedback_menu_item);
1650 if (helpItem != null) {
1651 helpItem.setVisible(mAccount != null
1652 && mAccount.supportsCapability(AccountCapabilities.HELP_CONTENT));
1653 }
1654 if (sendFeedbackItem != null) {
1655 sendFeedbackItem.setVisible(mAccount != null
1656 && mAccount.supportsCapability(AccountCapabilities.SEND_FEEDBACK));
1657 }
Mindy Pereirab47f3e22011-12-13 14:25:04 -08001658 return true;
1659 }
1660
1661 @Override
Mindy Pereiraec8b0ed2012-01-06 10:35:59 -08001662 public boolean onPrepareOptionsMenu(Menu menu) {
1663 MenuItem ccBcc = menu.findItem(R.id.add_cc_bcc);
Mindy Pereira818143e2012-01-11 13:59:49 -08001664 if (ccBcc != null && mCc != null) {
Mindy Pereiraec8b0ed2012-01-06 10:35:59 -08001665 // Its possible there is a menu item OR a button.
1666 boolean ccFieldVisible = mCc.isShown();
1667 boolean bccFieldVisible = mBcc.isShown();
1668 if (!ccFieldVisible || !bccFieldVisible) {
1669 ccBcc.setVisible(true);
1670 ccBcc.setTitle(getString(!ccFieldVisible ? R.string.add_cc_label
1671 : R.string.add_bcc_label));
1672 } else {
1673 ccBcc.setVisible(false);
1674 }
1675 }
Mindy Pereira75f66632012-01-11 11:42:02 -08001676 if (mSave != null) {
1677 mSave.setEnabled(shouldSave());
1678 }
Mindy Pereiraec8b0ed2012-01-06 10:35:59 -08001679 return true;
1680 }
1681
1682 @Override
Mindy Pereirab47f3e22011-12-13 14:25:04 -08001683 public boolean onOptionsItemSelected(MenuItem item) {
1684 int id = item.getItemId();
Mindy Pereira75f66632012-01-11 11:42:02 -08001685 boolean handled = true;
Mindy Pereirab47f3e22011-12-13 14:25:04 -08001686 switch (id) {
Mindy Pereira2db7d4a2012-08-15 11:00:02 -07001687 case R.id.add_photo_attachment:
1688 doAttach(MIME_TYPE_PHOTO);
1689 break;
1690 case R.id.add_video_attachment:
1691 doAttach(MIME_TYPE_VIDEO);
Mindy Pereira7b56a612011-12-14 12:32:28 -08001692 break;
Mindy Pereiraec8b0ed2012-01-06 10:35:59 -08001693 case R.id.add_cc_bcc:
1694 showCcBccViews();
Mindy Pereirab47f3e22011-12-13 14:25:04 -08001695 break;
Mindy Pereira33fe9082012-01-09 16:24:30 -08001696 case R.id.save:
Mindy Pereira48e31b02012-05-30 13:12:24 -07001697 doSave(true);
Mindy Pereira33fe9082012-01-09 16:24:30 -08001698 break;
1699 case R.id.send:
1700 doSend();
Mindy Pereira75f66632012-01-11 11:42:02 -08001701 break;
Mindy Pereiraefe3d252012-03-01 14:20:44 -08001702 case R.id.discard:
1703 doDiscard();
1704 break;
Mindy Pereira1f936682012-03-02 11:30:33 -08001705 case R.id.settings:
1706 Utils.showSettings(this, mAccount);
1707 break;
Mindy Pereirafbe40192012-03-20 10:40:45 -07001708 case android.R.id.home:
Paul Westbrookdaecb4b2012-05-31 10:21:26 -07001709 onAppUpPressed();
Mindy Pereirafbe40192012-03-20 10:40:45 -07001710 break;
1711 case R.id.help_info_menu_item:
Paul Westbrook30745b62012-08-19 14:10:32 -07001712 Utils.showHelp(this, mAccount, getString(R.string.compose_help_context));
Mindy Pereirafbe40192012-03-20 10:40:45 -07001713 break;
1714 case R.id.feedback_menu_item:
Paul Westbrook17beb0b2012-08-20 13:34:37 -07001715 Utils.sendFeedback(this, mAccount, false);
Mindy Pereirafbe40192012-03-20 10:40:45 -07001716 break;
Mindy Pereira75f66632012-01-11 11:42:02 -08001717 default:
1718 handled = false;
Mindy Pereira33fe9082012-01-09 16:24:30 -08001719 break;
Mindy Pereirab47f3e22011-12-13 14:25:04 -08001720 }
1721 return !handled ? super.onOptionsItemSelected(item) : handled;
1722 }
Mindy Pereira326c6602012-01-04 15:32:42 -08001723
Mindy Pereirab199d172012-08-13 11:04:03 -07001724 @Override
1725 public void onBackPressed() {
1726 // If we are showing the wait fragment, just exit.
1727 if (getWaitFragment() != null) {
1728 finish();
1729 } else {
1730 super.onBackPressed();
1731 }
1732 }
1733
Vikram Aggarwal1672ff82012-09-21 10:15:22 -07001734 /**
1735 * Carries out the "up" action in the action bar.
1736 */
Paul Westbrookdaecb4b2012-05-31 10:21:26 -07001737 private void onAppUpPressed() {
1738 if (mLaunchedFromEmail) {
1739 // If this was started from Gmail, simply treat app up as the system back button, so
1740 // that the last view is restored.
1741 onBackPressed();
1742 return;
1743 }
1744
1745 // Fire the main activity to ensure it launches the "top" screen of mail.
1746 // Since the main Activity is singleTask, it should revive that task if it was already
1747 // started.
Vikram Aggarwal0c3c2052012-09-21 11:06:28 -07001748 final Intent mailIntent = Utils.createViewInboxIntent(mAccount);
Paul Westbrookdaecb4b2012-05-31 10:21:26 -07001749 mailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK |
1750 Intent.FLAG_ACTIVITY_TASK_ON_HOME);
1751 startActivity(mailIntent);
1752 finish();
1753 }
1754
Mindy Pereira33fe9082012-01-09 16:24:30 -08001755 private void doSend() {
Mark Weidd19b632012-10-19 13:59:28 -07001756 sendOrSaveWithSanityChecks(false, true, false, false);
Mindy Pereira33fe9082012-01-09 16:24:30 -08001757 }
1758
Mindy Pereira48e31b02012-05-30 13:12:24 -07001759 private void doSave(boolean showToast) {
Mark Weidd19b632012-10-19 13:59:28 -07001760 sendOrSaveWithSanityChecks(true, showToast, false, false);
Mindy Pereira48e31b02012-05-30 13:12:24 -07001761 }
1762
Mindy Pereirae011b1d2012-06-18 13:45:26 -07001763 @VisibleForTesting
1764 public interface SendOrSaveCallback {
Mindy Pereira82cc5662012-01-09 17:29:30 -08001765 public void initializeSendOrSave(SendOrSaveTask sendOrSaveTask);
Mindy Pereira7ed1c112012-01-18 10:59:25 -08001766 public void notifyMessageIdAllocated(SendOrSaveMessage sendOrSaveMessage, Message message);
1767 public Message getMessage();
Mindy Pereira82cc5662012-01-09 17:29:30 -08001768 public void sendOrSaveFinished(SendOrSaveTask sendOrSaveTask, boolean success);
1769 }
1770
Mindy Pereirae011b1d2012-06-18 13:45:26 -07001771 @VisibleForTesting
1772 public static class SendOrSaveTask implements Runnable {
Mindy Pereira82cc5662012-01-09 17:29:30 -08001773 private final Context mContext;
Mindy Pereira82cc5662012-01-09 17:29:30 -08001774 @VisibleForTesting
Mindy Pereirae011b1d2012-06-18 13:45:26 -07001775 public final SendOrSaveCallback mSendOrSaveCallback;
1776 @VisibleForTesting
1777 public final SendOrSaveMessage mSendOrSaveMessage;
Mindy Pereira82cc5662012-01-09 17:29:30 -08001778
1779 public SendOrSaveTask(Context context, SendOrSaveMessage message,
1780 SendOrSaveCallback callback) {
1781 mContext = context;
1782 mSendOrSaveCallback = callback;
1783 mSendOrSaveMessage = message;
1784 }
1785
1786 @Override
1787 public void run() {
Mindy Pereira7ed1c112012-01-18 10:59:25 -08001788 final SendOrSaveMessage sendOrSaveMessage = mSendOrSaveMessage;
Mindy Pereira82cc5662012-01-09 17:29:30 -08001789
Mindy Pereira92551d02012-04-05 11:31:12 -07001790 final ReplyFromAccount selectedAccount = sendOrSaveMessage.mAccount;
Mindy Pereira7ed1c112012-01-18 10:59:25 -08001791 Message message = mSendOrSaveCallback.getMessage();
1792 long messageId = message != null ? message.id : UIProvider.INVALID_MESSAGE_ID;
Mindy Pereira82cc5662012-01-09 17:29:30 -08001793 // If a previous draft has been saved, in an account that is different
1794 // than what the user wants to send from, remove the old draft, and treat this
1795 // as a new message
Mindy Pereira7ed1c112012-01-18 10:59:25 -08001796 if (!selectedAccount.equals(sendOrSaveMessage.mAccount)) {
Mindy Pereira82cc5662012-01-09 17:29:30 -08001797 if (messageId != UIProvider.INVALID_MESSAGE_ID) {
1798 ContentResolver resolver = mContext.getContentResolver();
1799 ContentValues values = new ContentValues();
1800 values.put(BaseColumns._ID, messageId);
Mindy Pereira92551d02012-04-05 11:31:12 -07001801 if (selectedAccount.account.expungeMessageUri != null) {
1802 resolver.update(selectedAccount.account.expungeMessageUri, values, null,
Mindy Pereiraeaea9f12012-01-10 15:05:27 -08001803 null);
Mindy Pereiracfb7f332012-02-28 10:23:43 -08001804 } else {
1805 // TODO(mindyp) delete the conversation.
Mindy Pereiraeaea9f12012-01-10 15:05:27 -08001806 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08001807 // reset messageId to 0, so a new message will be created
1808 messageId = UIProvider.INVALID_MESSAGE_ID;
1809 }
1810 }
1811
1812 final long messageIdToSave = messageId;
Paul Westbrook72e2ea82012-10-22 16:25:22 -07001813 sendOrSaveMessage(messageIdToSave, sendOrSaveMessage, selectedAccount, message);
1814
1815 if (!sendOrSaveMessage.mSave) {
1816 UIProvider.incrementRecipientsTimesContacted(mContext,
1817 (String) sendOrSaveMessage.mValues.get(UIProvider.MessageColumns.TO));
1818 UIProvider.incrementRecipientsTimesContacted(mContext,
1819 (String) sendOrSaveMessage.mValues.get(UIProvider.MessageColumns.CC));
1820 UIProvider.incrementRecipientsTimesContacted(mContext,
1821 (String) sendOrSaveMessage.mValues.get(UIProvider.MessageColumns.BCC));
1822 }
1823 mSendOrSaveCallback.sendOrSaveFinished(SendOrSaveTask.this, true);
1824 }
1825
1826 /**
1827 * Send or Save a message.
1828 */
1829 private void sendOrSaveMessage(long messageIdToSave, SendOrSaveMessage sendOrSaveMessage,
1830 ReplyFromAccount selectedAccount, Message message) {
1831 final ContentResolver resolver = mContext.getContentResolver();
1832 final boolean updateExistingMessage = messageIdToSave != UIProvider.INVALID_MESSAGE_ID;
1833
1834 final String accountMethod = sendOrSaveMessage.mSave ?
1835 UIProvider.AccountCallMethods.SAVE_MESSAGE :
1836 UIProvider.AccountCallMethods.SEND_MESSAGE;
1837
1838 if (updateExistingMessage) {
Mindy Pereira7ed1c112012-01-18 10:59:25 -08001839 sendOrSaveMessage.mValues.put(BaseColumns._ID, messageIdToSave);
Paul Westbrook72e2ea82012-10-22 16:25:22 -07001840
1841 final Bundle result = callAccountSendSaveMethod(resolver, selectedAccount.account,
1842 accountMethod, sendOrSaveMessage);
1843 if (result == null) {
1844 // TODO(pwestbro): Once Email supports the call apu, remove this block
1845 // If null was returned, then the provider didn't handle the call method
1846 resolver.update(
1847 Uri.parse(sendOrSaveMessage.mSave ? message.saveUri : message.sendUri),
1848 sendOrSaveMessage.mValues, null, null);
1849 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08001850 } else {
Paul Westbrook72e2ea82012-10-22 16:25:22 -07001851 final Uri messageUri;
1852 final Bundle result = callAccountSendSaveMethod(resolver, selectedAccount.account,
1853 accountMethod, sendOrSaveMessage);
1854 if (result != null) {
1855 // If a non-null value was returned, then the provider handled the call method
1856 messageUri = result.getParcelable(UIProvider.MessageColumns.URI);
1857 } else {
1858 // TODO(pwestbro): Once Email supports the call apu, remove this block
1859 messageUri = resolver.insert(
1860 sendOrSaveMessage.mSave ? selectedAccount.account.saveDraftUri
1861 : selectedAccount.account.sendMessageUri,
1862 sendOrSaveMessage.mValues);
1863 }
Mindy Pereira7ed1c112012-01-18 10:59:25 -08001864 if (sendOrSaveMessage.mSave && messageUri != null) {
Paul Westbrook72e2ea82012-10-22 16:25:22 -07001865 final Cursor messageCursor = resolver.query(messageUri,
Mindy Pereira7ed1c112012-01-18 10:59:25 -08001866 UIProvider.MESSAGE_PROJECTION, null, null, null);
Paul Westbrookba558482012-03-19 11:00:24 -07001867 if (messageCursor != null) {
1868 try {
1869 if (messageCursor.moveToFirst()) {
1870 // Broadcast notification that a new message has
1871 // been allocated
1872 mSendOrSaveCallback.notifyMessageIdAllocated(sendOrSaveMessage,
1873 new Message(messageCursor));
1874 }
1875 } finally {
1876 messageCursor.close();
1877 }
Mindy Pereira7ed1c112012-01-18 10:59:25 -08001878 }
1879 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08001880 }
Paul Westbrook72e2ea82012-10-22 16:25:22 -07001881 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08001882
Paul Westbrook72e2ea82012-10-22 16:25:22 -07001883 /**
1884 * Use the {@link ContentResolver#call()} method to send or save the message.
1885 *
1886 * If this was successful, this method will return an non-null Bundle instance
1887 */
1888 private Bundle callAccountSendSaveMethod(ContentResolver resolver, Account account,
1889 String method, SendOrSaveMessage sendOrSaveMessage) {
1890 // Copy all of the values from the content values to the bundle
1891 final Bundle methodExtras = new Bundle(sendOrSaveMessage.mValues.size());
1892 final Set<Entry<String, Object>> valueSet = sendOrSaveMessage.mValues.valueSet();
1893
1894 for (Entry<String, Object> entry : valueSet) {
1895 final Object entryValue = entry.getValue();
1896 final String key = entry.getKey();
1897 if (entryValue instanceof String) {
1898 methodExtras.putString(key, (String)entryValue);
1899 } else if (entryValue instanceof Boolean) {
1900 methodExtras.putBoolean(key, (Boolean)entryValue);
1901 } else if (entryValue instanceof Integer) {
1902 methodExtras.putInt(key, (Integer)entryValue);
1903 } else if (entryValue instanceof Long) {
1904 methodExtras.putLong(key, (Long)entryValue);
1905 } else {
1906 LogUtils.wtf(LOG_TAG, "Unexpected object type: %s",
1907 entryValue.getClass().getName());
1908 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08001909 }
Paul Westbrook72e2ea82012-10-22 16:25:22 -07001910
1911 return resolver.call(account.uri, method, account.uri.toString(), methodExtras);
Mindy Pereira82cc5662012-01-09 17:29:30 -08001912 }
1913 }
1914
1915 // Array of the outstanding send or save tasks. Access is synchronized
1916 // with the object itself
1917 /* package for testing */
Mindy Pereirae011b1d2012-06-18 13:45:26 -07001918 @VisibleForTesting
1919 public ArrayList<SendOrSaveTask> mActiveTasks = Lists.newArrayList();
Mindy Pereira82cc5662012-01-09 17:29:30 -08001920 private int mRequestId;
Mindy Pereirabdf7a402012-03-01 15:23:26 -08001921 private String mSignature;
Mindy Pereirab199d172012-08-13 11:04:03 -07001922 private Account[] mAccounts;
Mindy Pereira82cc5662012-01-09 17:29:30 -08001923
Mindy Pereirae011b1d2012-06-18 13:45:26 -07001924 @VisibleForTesting
1925 public static class SendOrSaveMessage {
Mindy Pereira92551d02012-04-05 11:31:12 -07001926 final ReplyFromAccount mAccount;
Mindy Pereira82cc5662012-01-09 17:29:30 -08001927 final ContentValues mValues;
Mindy Pereira3ce64e72012-01-13 14:29:45 -08001928 final String mRefMessageId;
Mindy Pereirae011b1d2012-06-18 13:45:26 -07001929 @VisibleForTesting
1930 public final boolean mSave;
Mindy Pereira82cc5662012-01-09 17:29:30 -08001931 final int mRequestId;
1932
Mindy Pereira92551d02012-04-05 11:31:12 -07001933 public SendOrSaveMessage(ReplyFromAccount account, ContentValues values,
Mindy Pereira3ce64e72012-01-13 14:29:45 -08001934 String refMessageId, boolean save) {
Mindy Pereira82cc5662012-01-09 17:29:30 -08001935 mAccount = account;
Mindy Pereira82cc5662012-01-09 17:29:30 -08001936 mValues = values;
1937 mRefMessageId = refMessageId;
1938 mSave = save;
1939 mRequestId = mValues.hashCode() ^ hashCode();
1940 }
1941
1942 int requestId() {
1943 return mRequestId;
1944 }
1945 }
1946
1947 /**
1948 * Get the to recipients.
1949 */
1950 public String[] getToAddresses() {
1951 return getAddressesFromList(mTo);
1952 }
1953
1954 /**
1955 * Get the cc recipients.
1956 */
1957 public String[] getCcAddresses() {
1958 return getAddressesFromList(mCc);
1959 }
1960
1961 /**
1962 * Get the bcc recipients.
1963 */
1964 public String[] getBccAddresses() {
1965 return getAddressesFromList(mBcc);
1966 }
1967
1968 public String[] getAddressesFromList(RecipientEditTextView list) {
1969 if (list == null) {
1970 return new String[0];
1971 }
1972 Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(list.getText());
1973 int count = tokens.length;
1974 String[] result = new String[count];
1975 for (int i = 0; i < count; i++) {
1976 result[i] = tokens[i].toString();
1977 }
1978 return result;
1979 }
1980
1981 /**
1982 * Check for invalid email addresses.
1983 * @param to String array of email addresses to check.
1984 * @param wrongEmailsOut Emails addresses that were invalid.
1985 */
1986 public void checkInvalidEmails(String[] to, List<String> wrongEmailsOut) {
Mindy Pereirae5f20bf2012-06-25 14:20:40 -07001987 if (mValidator == null) {
1988 return;
1989 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08001990 for (String email : to) {
1991 if (!mValidator.isValid(email)) {
1992 wrongEmailsOut.add(email);
1993 }
1994 }
1995 }
1996
1997 /**
1998 * Show an error because the user has entered an invalid recipient.
1999 * @param message
2000 */
2001 public void showRecipientErrorDialog(String message) {
2002 // Only 1 invalid recipients error dialog should be allowed up at a
2003 // time.
2004 if (mRecipientErrorDialog != null) {
2005 mRecipientErrorDialog.dismiss();
2006 }
2007 mRecipientErrorDialog = new AlertDialog.Builder(this).setMessage(message).setTitle(
2008 R.string.recipient_error_dialog_title)
2009 .setIconAttribute(android.R.attr.alertDialogIcon)
Mindy Pereira82cc5662012-01-09 17:29:30 -08002010 .setPositiveButton(
2011 R.string.ok, new Dialog.OnClickListener() {
Marc Blank0bbc8582012-04-23 15:07:57 -07002012 @Override
Mindy Pereira82cc5662012-01-09 17:29:30 -08002013 public void onClick(DialogInterface dialog, int which) {
2014 // after the user dismisses the recipient error
2015 // dialog we want to make sure to refocus the
2016 // recipient to field so they can fix the issue
2017 // easily
2018 if (mTo != null) {
2019 mTo.requestFocus();
2020 }
2021 mRecipientErrorDialog = null;
2022 }
2023 }).show();
2024 }
2025
2026 /**
2027 * Update the state of the UI based on whether or not the current draft
2028 * needs to be saved and the message is not empty.
2029 */
Mindy Pereiraeaea9f12012-01-10 15:05:27 -08002030 public void updateSaveUi() {
Mindy Pereira82cc5662012-01-09 17:29:30 -08002031 if (mSave != null) {
2032 mSave.setEnabled((shouldSave() && !isBlank()));
2033 }
2034 }
2035
2036 /**
2037 * Returns true if we need to save the current draft.
2038 */
2039 private boolean shouldSave() {
Mindy Pereira7ed1c112012-01-18 10:59:25 -08002040 synchronized (mDraftLock) {
Mindy Pereira82cc5662012-01-09 17:29:30 -08002041 // The message should only be saved if:
2042 // It hasn't been sent AND
2043 // Some text has been added to the message OR
2044 // an attachment has been added or removed
Mindy Pereiraa2148332012-07-02 13:54:14 -07002045 // AND there is actually something in the draft to save.
Andy Huangd47877e2012-08-09 19:31:24 -07002046 return (mTextChanged || mAttachmentsChanged || mReplyFromChanged)
Mindy Pereiraa2148332012-07-02 13:54:14 -07002047 && !isBlank();
Mindy Pereira82cc5662012-01-09 17:29:30 -08002048 }
2049 }
2050
2051 /**
Mindy Pereirabdf7a402012-03-01 15:23:26 -08002052 * Check if all fields are blank.
Mindy Pereira82cc5662012-01-09 17:29:30 -08002053 * @return boolean
2054 */
2055 public boolean isBlank() {
2056 return mSubject.getText().length() == 0
Mindy Pereirabdf7a402012-03-01 15:23:26 -08002057 && (mBodyView.getText().length() == 0 || getSignatureStartPosition(mSignature,
2058 mBodyView.getText().toString()) == 0)
2059 && mTo.length() == 0
2060 && mCc.length() == 0 && mBcc.length() == 0
2061 && mAttachmentsView.getAttachments().size() == 0;
2062 }
2063
2064 @VisibleForTesting
2065 protected int getSignatureStartPosition(String signature, String bodyText) {
2066 int startPos = -1;
2067
2068 if (TextUtils.isEmpty(signature) || TextUtils.isEmpty(bodyText)) {
2069 return startPos;
2070 }
2071
2072 int bodyLength = bodyText.length();
2073 int signatureLength = signature.length();
2074 String printableVersion = convertToPrintableSignature(signature);
2075 int printableLength = printableVersion.length();
2076
2077 if (bodyLength >= printableLength
2078 && bodyText.substring(bodyLength - printableLength)
2079 .equals(printableVersion)) {
2080 startPos = bodyLength - printableLength;
2081 } else if (bodyLength >= signatureLength
2082 && bodyText.substring(bodyLength - signatureLength)
2083 .equals(signature)) {
2084 startPos = bodyLength - signatureLength;
2085 }
2086 return startPos;
Mindy Pereira82cc5662012-01-09 17:29:30 -08002087 }
2088
2089 /**
2090 * Allows any changes made by the user to be ignored. Called when the user
2091 * decides to discard a draft.
2092 */
2093 private void discardChanges() {
2094 mTextChanged = false;
Mindy Pereiraeaea9f12012-01-10 15:05:27 -08002095 mAttachmentsChanged = false;
Mindy Pereira82cc5662012-01-09 17:29:30 -08002096 mReplyFromChanged = false;
2097 }
2098
2099 /**
Mindy Pereira181df782012-03-01 13:32:44 -08002100 * @param body
2101 * @param save
2102 * @param showToast
2103 * @return Whether the send or save succeeded.
2104 */
2105 protected boolean sendOrSaveWithSanityChecks(final boolean save, final boolean showToast,
Mark Weidd19b632012-10-19 13:59:28 -07002106 final boolean orientationChanged, final boolean autoSend) {
Mark Wei009b3712012-10-18 18:07:50 -07002107 if (mAccounts == null || mAccount == null) {
2108 Toast.makeText(this, R.string.send_failed, Toast.LENGTH_SHORT).show();
Mark Weidd19b632012-10-19 13:59:28 -07002109 if (autoSend) {
2110 finish();
2111 }
Mark Wei009b3712012-10-18 18:07:50 -07002112 return false;
2113 }
2114
Mindy Pereira181df782012-03-01 13:32:44 -08002115 String[] to, cc, bcc;
2116 Editable body = mBodyView.getEditableText();
Mindy Pereira181df782012-03-01 13:32:44 -08002117 if (orientationChanged) {
2118 to = cc = bcc = new String[0];
2119 } else {
2120 to = getToAddresses();
2121 cc = getCcAddresses();
2122 bcc = getBccAddresses();
2123 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002124
Mindy Pereira181df782012-03-01 13:32:44 -08002125 // Don't let the user send to nobody (but it's okay to save a message
2126 // with no recipients)
2127 if (!save && (to.length == 0 && cc.length == 0 && bcc.length == 0)) {
2128 showRecipientErrorDialog(getString(R.string.recipient_needed));
2129 return false;
2130 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002131
Mindy Pereira181df782012-03-01 13:32:44 -08002132 List<String> wrongEmails = new ArrayList<String>();
2133 if (!save) {
2134 checkInvalidEmails(to, wrongEmails);
2135 checkInvalidEmails(cc, wrongEmails);
2136 checkInvalidEmails(bcc, wrongEmails);
2137 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002138
Mindy Pereira181df782012-03-01 13:32:44 -08002139 // Don't let the user send an email with invalid recipients
2140 if (wrongEmails.size() > 0) {
2141 String errorText = String.format(getString(R.string.invalid_recipient),
2142 wrongEmails.get(0));
2143 showRecipientErrorDialog(errorText);
2144 return false;
2145 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002146
Mindy Pereira181df782012-03-01 13:32:44 -08002147 DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
Marc Blank0bbc8582012-04-23 15:07:57 -07002148 @Override
Mindy Pereira181df782012-03-01 13:32:44 -08002149 public void onClick(DialogInterface dialog, int which) {
2150 sendOrSave(mBodyView.getEditableText(), save, showToast, orientationChanged);
2151 }
2152 };
Mindy Pereira82cc5662012-01-09 17:29:30 -08002153
Mindy Pereira181df782012-03-01 13:32:44 -08002154 // Show a warning before sending only if there are no attachments.
2155 if (!save) {
2156 if (mAttachmentsView.getAttachments().isEmpty() && showEmptyTextWarnings()) {
2157 boolean warnAboutEmptySubject = isSubjectEmpty();
2158 boolean emptyBody = TextUtils.getTrimmedLength(body) == 0;
Mindy Pereira82cc5662012-01-09 17:29:30 -08002159
Mindy Pereira181df782012-03-01 13:32:44 -08002160 // A warning about an empty body may not be warranted when
2161 // forwarding mails, since a common use case is to forward
2162 // quoted text and not append any more text.
2163 boolean warnAboutEmptyBody = emptyBody && (!mForward || isBodyEmpty());
Mindy Pereira82cc5662012-01-09 17:29:30 -08002164
Mindy Pereira181df782012-03-01 13:32:44 -08002165 // When we bring up a dialog warning the user about a send,
2166 // assume that they accept sending the message. If they do not,
2167 // the dialog listener is required to enable sending again.
2168 if (warnAboutEmptySubject) {
2169 showSendConfirmDialog(R.string.confirm_send_message_with_no_subject, listener);
2170 return true;
2171 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002172
Mindy Pereira181df782012-03-01 13:32:44 -08002173 if (warnAboutEmptyBody) {
2174 showSendConfirmDialog(R.string.confirm_send_message_with_no_body, listener);
2175 return true;
2176 }
2177 }
2178 // Ask for confirmation to send (if always required)
2179 if (showSendConfirmation()) {
2180 showSendConfirmDialog(R.string.confirm_send_message, listener);
2181 return true;
2182 }
2183 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002184
Mindy Pereira181df782012-03-01 13:32:44 -08002185 sendOrSave(body, save, showToast, false);
2186 return true;
2187 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002188
Mindy Pereira181df782012-03-01 13:32:44 -08002189 /**
2190 * Returns a boolean indicating whether warnings should be shown for empty
2191 * subject and body fields
Andy Huang5c5fd572012-04-08 18:19:29 -07002192 *
Mindy Pereira181df782012-03-01 13:32:44 -08002193 * @return True if a warning should be shown for empty text fields
2194 */
2195 protected boolean showEmptyTextWarnings() {
2196 return mAttachmentsView.getAttachments().size() == 0;
2197 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002198
Mindy Pereira181df782012-03-01 13:32:44 -08002199 /**
2200 * Returns a boolean indicating whether the user should confirm each send
2201 *
2202 * @return True if a warning should be on each send
2203 */
2204 protected boolean showSendConfirmation() {
2205 return mCachedSettings != null ? mCachedSettings.confirmSend : false;
2206 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002207
Mindy Pereira181df782012-03-01 13:32:44 -08002208 private void showSendConfirmDialog(int messageId, DialogInterface.OnClickListener listener) {
2209 if (mSendConfirmDialog != null) {
2210 mSendConfirmDialog.dismiss();
2211 mSendConfirmDialog = null;
2212 }
2213 mSendConfirmDialog = new AlertDialog.Builder(this).setMessage(messageId)
2214 .setTitle(R.string.confirm_send_title)
2215 .setIconAttribute(android.R.attr.alertDialogIcon)
2216 .setPositiveButton(R.string.send, listener)
Mindy Pereira6edd5972012-06-19 10:22:36 -07002217 .setNegativeButton(R.string.cancel, this)
2218 .show();
Mindy Pereira181df782012-03-01 13:32:44 -08002219 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002220
Mindy Pereira181df782012-03-01 13:32:44 -08002221 /**
2222 * Returns whether the ComposeArea believes there is any text in the body of
2223 * the composition. TODO: When ComposeArea controls the Body as well, add
2224 * that here.
2225 */
2226 public boolean isBodyEmpty() {
2227 return !mQuotedTextView.isTextIncluded();
2228 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002229
Mindy Pereira181df782012-03-01 13:32:44 -08002230 /**
2231 * Test to see if the subject is empty.
2232 *
2233 * @return boolean.
2234 */
2235 // TODO: this will likely go away when composeArea.focus() is implemented
2236 // after all the widget control is moved over.
2237 public boolean isSubjectEmpty() {
2238 return TextUtils.getTrimmedLength(mSubject.getText()) == 0;
2239 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002240
Mindy Pereira181df782012-03-01 13:32:44 -08002241 /* package */
Mindy Pereirae8f94dc2012-04-16 11:56:21 -07002242 static int sendOrSaveInternal(Context context, ReplyFromAccount replyFromAccount,
Paul Westbrook05b92b82012-04-20 13:29:37 -07002243 Message message, final Message refMessage, Spanned body, final CharSequence quotedText,
2244 SendOrSaveCallback callback, Handler handler, boolean save, int composeMode) {
Mindy Pereira29ef1b82012-01-13 11:26:21 -08002245 ContentValues values = new ContentValues();
Mindy Pereira82cc5662012-01-09 17:29:30 -08002246
Mindy Pereirac2031972012-04-03 09:38:35 -07002247 String refMessageId = refMessage != null ? refMessage.uri.toString() : "";
2248
Mindy Pereirae8f94dc2012-04-16 11:56:21 -07002249 MessageModification.putToAddresses(values, message.getToAddresses());
2250 MessageModification.putCcAddresses(values, message.getCcAddresses());
2251 MessageModification.putBccAddresses(values, message.getBccAddresses());
Mindy Pereira82cc5662012-01-09 17:29:30 -08002252
Mindy Pereirae8f94dc2012-04-16 11:56:21 -07002253 MessageModification.putCustomFromAddress(values, message.from);
Mindy Pereira92551d02012-04-05 11:31:12 -07002254
Mindy Pereirae8f94dc2012-04-16 11:56:21 -07002255 MessageModification.putSubject(values, message.subject);
mindyped9c2f02012-10-12 10:02:08 -07002256 String htmlBody = Html.toHtml(new SpannableString(body.toString()));
Paul Westbrook05b92b82012-04-20 13:29:37 -07002257
Mindy Pereira29ef1b82012-01-13 11:26:21 -08002258 boolean includeQuotedText = !TextUtils.isEmpty(quotedText);
2259 StringBuilder fullBody = new StringBuilder(htmlBody);
2260 if (includeQuotedText) {
Mindy Pereirae8caf122012-03-20 15:23:31 -07002261 // HTML gets converted to text for now
2262 final String text = quotedText.toString();
2263 if (QuotedTextView.containsQuotedText(text)) {
2264 int pos = QuotedTextView.getQuotedTextOffset(text);
Paul Westbrook55271cf2012-04-20 16:25:02 -07002265 final int quoteStartPos = fullBody.length() + pos;
2266 fullBody.append(text);
2267 MessageModification.putQuoteStartPos(values, quoteStartPos);
Mindy Pereira12575862012-03-21 16:30:54 -07002268 MessageModification.putForward(values, composeMode == ComposeActivity.FORWARD);
Mindy Pereirae8caf122012-03-20 15:23:31 -07002269 MessageModification.putAppendRefMessageContent(values, includeQuotedText);
Mindy Pereira29ef1b82012-01-13 11:26:21 -08002270 } else {
Mindy Pereirae8caf122012-03-20 15:23:31 -07002271 LogUtils.w(LOG_TAG, "Couldn't find quoted text");
2272 // This shouldn't happen, but just use what we have,
2273 // and don't do server-side expansion
2274 fullBody.append(text);
Mindy Pereira29ef1b82012-01-13 11:26:21 -08002275 }
2276 }
Mindy Pereira002ff522012-05-30 10:31:26 -07002277 int draftType = getDraftType(composeMode);
Mindy Pereira12575862012-03-21 16:30:54 -07002278 MessageModification.putDraftType(values, draftType);
Mindy Pereirac6f1e2a2012-04-04 10:33:45 -07002279 if (refMessage != null) {
2280 if (!TextUtils.isEmpty(refMessage.bodyHtml)) {
2281 MessageModification.putBodyHtml(values, fullBody.toString());
2282 }
2283 if (!TextUtils.isEmpty(refMessage.bodyText)) {
2284 MessageModification.putBody(values, Html.fromHtml(fullBody.toString()).toString());
2285 }
2286 } else {
Mindy Pereirac2031972012-04-03 09:38:35 -07002287 MessageModification.putBodyHtml(values, fullBody.toString());
Mindy Pereirac2031972012-04-03 09:38:35 -07002288 MessageModification.putBody(values, Html.fromHtml(fullBody.toString()).toString());
2289 }
Mindy Pereirae8f94dc2012-04-16 11:56:21 -07002290 MessageModification.putAttachments(values, message.getAttachments());
Mindy Pereira12575862012-03-21 16:30:54 -07002291 if (!TextUtils.isEmpty(refMessageId)) {
2292 MessageModification.putRefMessageId(values, refMessageId);
2293 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002294
Mindy Pereira92551d02012-04-05 11:31:12 -07002295 SendOrSaveMessage sendOrSaveMessage = new SendOrSaveMessage(replyFromAccount,
Mindy Pereira181df782012-03-01 13:32:44 -08002296 values, refMessageId, save);
2297 SendOrSaveTask sendOrSaveTask = new SendOrSaveTask(context, sendOrSaveMessage, callback);
Mindy Pereira82cc5662012-01-09 17:29:30 -08002298
Mindy Pereira181df782012-03-01 13:32:44 -08002299 callback.initializeSendOrSave(sendOrSaveTask);
Mindy Pereira82cc5662012-01-09 17:29:30 -08002300
Mindy Pereira181df782012-03-01 13:32:44 -08002301 // Do the send/save action on the specified handler to avoid possible
2302 // ANRs
2303 handler.post(sendOrSaveTask);
Mindy Pereira82cc5662012-01-09 17:29:30 -08002304
Mindy Pereira181df782012-03-01 13:32:44 -08002305 return sendOrSaveMessage.requestId();
2306 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002307
Mindy Pereira002ff522012-05-30 10:31:26 -07002308 private static int getDraftType(int mode) {
2309 int draftType = -1;
2310 switch (mode) {
2311 case ComposeActivity.COMPOSE:
2312 draftType = DraftType.COMPOSE;
2313 break;
2314 case ComposeActivity.REPLY:
2315 draftType = DraftType.REPLY;
2316 break;
2317 case ComposeActivity.REPLY_ALL:
2318 draftType = DraftType.REPLY_ALL;
2319 break;
2320 case ComposeActivity.FORWARD:
2321 draftType = DraftType.FORWARD;
2322 break;
2323 }
2324 return draftType;
2325 }
2326
Mindy Pereira181df782012-03-01 13:32:44 -08002327 private void sendOrSave(Spanned body, boolean save, boolean showToast,
2328 boolean orientationChanged) {
2329 // Check if user is a monkey. Monkeys can compose and hit send
2330 // button but are not allowed to send anything off the device.
Paul Westbrook3ae824c2012-04-06 13:29:39 -07002331 if (ActivityManager.isUserAMonkey()) {
Mindy Pereira181df782012-03-01 13:32:44 -08002332 return;
2333 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002334
Mindy Pereira181df782012-03-01 13:32:44 -08002335 String[] to, cc, bcc;
2336 if (orientationChanged) {
2337 to = cc = bcc = new String[0];
2338 } else {
2339 to = getToAddresses();
2340 cc = getCcAddresses();
2341 bcc = getBccAddresses();
2342 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002343
Mindy Pereira181df782012-03-01 13:32:44 -08002344 SendOrSaveCallback callback = new SendOrSaveCallback() {
Mindy Pereira82cc5662012-01-09 17:29:30 -08002345 private int mRestoredRequestId;
2346
Marc Blank0bbc8582012-04-23 15:07:57 -07002347 @Override
Mindy Pereira82cc5662012-01-09 17:29:30 -08002348 public void initializeSendOrSave(SendOrSaveTask sendOrSaveTask) {
Mindy Pereira181df782012-03-01 13:32:44 -08002349 synchronized (mActiveTasks) {
2350 int numTasks = mActiveTasks.size();
2351 if (numTasks == 0) {
2352 // Start service so we won't be killed if this app is
2353 // put in the background.
2354 startService(new Intent(ComposeActivity.this, EmptyService.class));
2355 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002356
Mindy Pereira181df782012-03-01 13:32:44 -08002357 mActiveTasks.add(sendOrSaveTask);
2358 }
2359 if (sTestSendOrSaveCallback != null) {
2360 sTestSendOrSaveCallback.initializeSendOrSave(sendOrSaveTask);
2361 }
2362 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002363
Marc Blank0bbc8582012-04-23 15:07:57 -07002364 @Override
Mindy Pereira7ed1c112012-01-18 10:59:25 -08002365 public void notifyMessageIdAllocated(SendOrSaveMessage sendOrSaveMessage,
2366 Message message) {
Mindy Pereira181df782012-03-01 13:32:44 -08002367 synchronized (mDraftLock) {
2368 mDraftId = message.id;
2369 mDraft = message;
Mindy Pereira7ed1c112012-01-18 10:59:25 -08002370 if (sRequestMessageIdMap != null) {
2371 sRequestMessageIdMap.put(sendOrSaveMessage.requestId(), mDraftId);
2372 }
Mindy Pereira181df782012-03-01 13:32:44 -08002373 // Cache request message map, in case the process is killed
2374 saveRequestMap();
2375 }
2376 if (sTestSendOrSaveCallback != null) {
Mindy Pereira7ed1c112012-01-18 10:59:25 -08002377 sTestSendOrSaveCallback.notifyMessageIdAllocated(sendOrSaveMessage, message);
Mindy Pereira181df782012-03-01 13:32:44 -08002378 }
2379 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002380
Marc Blank0bbc8582012-04-23 15:07:57 -07002381 @Override
Mindy Pereira7ed1c112012-01-18 10:59:25 -08002382 public Message getMessage() {
2383 synchronized (mDraftLock) {
2384 return mDraft;
2385 }
2386 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002387
Marc Blank0bbc8582012-04-23 15:07:57 -07002388 @Override
Mindy Pereira7ed1c112012-01-18 10:59:25 -08002389 public void sendOrSaveFinished(SendOrSaveTask task, boolean success) {
Mindy Pereira47d0e652012-07-23 09:45:07 -07002390 // Update the last sent from account.
2391 if (mAccount != null) {
2392 MailAppProvider.getInstance().setLastSentFromAccount(mAccount.uri.toString());
2393 }
Mindy Pereira7ed1c112012-01-18 10:59:25 -08002394 if (success) {
2395 // Successfully sent or saved so reset change markers
2396 discardChanges();
2397 } else {
2398 // A failure happened with saving/sending the draft
2399 // TODO(pwestbro): add a better string that should be used
2400 // when failing to send or save
2401 Toast.makeText(ComposeActivity.this, R.string.send_failed, Toast.LENGTH_SHORT)
2402 .show();
2403 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002404
Mindy Pereira7ed1c112012-01-18 10:59:25 -08002405 int numTasks;
2406 synchronized (mActiveTasks) {
2407 // Remove the task from the list of active tasks
2408 mActiveTasks.remove(task);
2409 numTasks = mActiveTasks.size();
2410 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002411
Mindy Pereira7ed1c112012-01-18 10:59:25 -08002412 if (numTasks == 0) {
2413 // Stop service so we can be killed.
2414 stopService(new Intent(ComposeActivity.this, EmptyService.class));
2415 }
2416 if (sTestSendOrSaveCallback != null) {
2417 sTestSendOrSaveCallback.sendOrSaveFinished(task, success);
2418 }
2419 }
Mindy Pereira181df782012-03-01 13:32:44 -08002420 };
Mindy Pereira82cc5662012-01-09 17:29:30 -08002421
Mindy Pereira181df782012-03-01 13:32:44 -08002422 // Get the selected account if the from spinner has been setup.
Mindy Pereira92551d02012-04-05 11:31:12 -07002423 ReplyFromAccount selectedAccount = mReplyFromAccount;
Mindy Pereira181df782012-03-01 13:32:44 -08002424 String fromAddress = selectedAccount.name;
2425 if (selectedAccount == null || fromAddress == null) {
2426 // We don't have either the selected account or from address,
2427 // use mAccount.
Mindy Pereira92551d02012-04-05 11:31:12 -07002428 selectedAccount = mReplyFromAccount;
Mindy Pereira181df782012-03-01 13:32:44 -08002429 fromAddress = mAccount.name;
2430 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002431
Mindy Pereira181df782012-03-01 13:32:44 -08002432 if (mSendSaveTaskHandler == null) {
2433 HandlerThread handlerThread = new HandlerThread("Send Message Task Thread");
2434 handlerThread.start();
Mindy Pereira82cc5662012-01-09 17:29:30 -08002435
Mindy Pereira181df782012-03-01 13:32:44 -08002436 mSendSaveTaskHandler = new Handler(handlerThread.getLooper());
2437 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002438
Mindy Pereirae8f94dc2012-04-16 11:56:21 -07002439 Message msg = createMessage(mReplyFromAccount, getMode());
Paul Westbrook05b92b82012-04-20 13:29:37 -07002440 mRequestId = sendOrSaveInternal(this, mReplyFromAccount, msg, mRefMessage, body,
2441 mQuotedTextView.getQuotedTextIfIncluded(), callback,
Mindy Pereira12575862012-03-21 16:30:54 -07002442 mSendSaveTaskHandler, save, mComposeMode);
Mindy Pereira82cc5662012-01-09 17:29:30 -08002443
Mindy Pereira181df782012-03-01 13:32:44 -08002444 if (mRecipient != null && mRecipient.equals(mAccount.name)) {
2445 mRecipient = selectedAccount.name;
2446 }
Paul Westbrookb1f573c2012-04-06 11:38:28 -07002447 setAccount(selectedAccount.account);
Mindy Pereira82cc5662012-01-09 17:29:30 -08002448
Mindy Pereira181df782012-03-01 13:32:44 -08002449 // Don't display the toast if the user is just changing the orientation,
2450 // but we still need to save the draft to the cursor because this is how we restore
2451 // the attachments when the configuration change completes.
2452 if (showToast && (getChangingConfigurations() & ActivityInfo.CONFIG_ORIENTATION) == 0) {
2453 Toast.makeText(this, save ? R.string.message_saved : R.string.sending_message,
2454 Toast.LENGTH_LONG).show();
2455 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002456
Mindy Pereira181df782012-03-01 13:32:44 -08002457 // Need to update variables here because the send or save completes
2458 // asynchronously even though the toast shows right away.
2459 discardChanges();
2460 updateSaveUi();
Mindy Pereira82cc5662012-01-09 17:29:30 -08002461
Mindy Pereira181df782012-03-01 13:32:44 -08002462 // If we are sending, finish the activity
2463 if (!save) {
2464 finish();
2465 }
2466 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002467
Mindy Pereira181df782012-03-01 13:32:44 -08002468 /**
2469 * Save the state of the request messageid map. This allows for the Gmail
2470 * process to be killed, but and still allow for ComposeActivity instances
2471 * to be recreated correctly.
2472 */
2473 private void saveRequestMap() {
2474 // TODO: store the request map in user preferences.
2475 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002476
Mindy Pereira2db7d4a2012-08-15 11:00:02 -07002477 private void doAttach(String type) {
Mindy Pereira013194c2012-01-06 15:09:33 -08002478 Intent i = new Intent(Intent.ACTION_GET_CONTENT);
2479 i.addCategory(Intent.CATEGORY_OPENABLE);
Paul Westbrookd6a9a3f2012-04-26 18:47:23 -07002480 i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
Mindy Pereira2db7d4a2012-08-15 11:00:02 -07002481 i.setType(type);
Mindy Pereira013194c2012-01-06 15:09:33 -08002482 mAddingAttachment = true;
Mindy Pereira181df782012-03-01 13:32:44 -08002483 startActivityForResult(Intent.createChooser(i, getText(R.string.select_attachment_type)),
2484 RESULT_PICK_ATTACHMENT);
Mindy Pereira013194c2012-01-06 15:09:33 -08002485 }
2486
Mindy Pereiraec8b0ed2012-01-06 10:35:59 -08002487 private void showCcBccViews() {
Mindy Pereiraa26b54e2012-01-06 12:54:33 -08002488 mCcBccView.show(true, true, true);
Mindy Pereiraec8b0ed2012-01-06 10:35:59 -08002489 if (mCcBccButton != null) {
mindypcd0b0b92012-08-23 14:33:17 -07002490 mCcBccButton.setVisibility(View.INVISIBLE);
Mindy Pereiraec8b0ed2012-01-06 10:35:59 -08002491 }
2492 }
2493
Mindy Pereira326c6602012-01-04 15:32:42 -08002494 @Override
2495 public boolean onNavigationItemSelected(int position, long itemId) {
Mindy Pereiraa26b54e2012-01-06 12:54:33 -08002496 int initialComposeMode = mComposeMode;
Mindy Pereira326c6602012-01-04 15:32:42 -08002497 if (position == ComposeActivity.REPLY) {
2498 mComposeMode = ComposeActivity.REPLY;
2499 } else if (position == ComposeActivity.REPLY_ALL) {
2500 mComposeMode = ComposeActivity.REPLY_ALL;
2501 } else if (position == ComposeActivity.FORWARD) {
2502 mComposeMode = ComposeActivity.FORWARD;
2503 }
Mindy Pereiracbfb75a2012-06-25 14:52:23 -07002504 clearChangeListeners();
Mindy Pereiraa26b54e2012-01-06 12:54:33 -08002505 if (initialComposeMode != mComposeMode) {
Mindy Pereira154386a2012-01-11 13:02:33 -08002506 resetMessageForModeChange();
Mindy Pereiraef388302012-06-18 19:07:44 -07002507 if (mDraft == null && mRefMessage != null) {
Mindy Pereira96a7f7a2012-07-09 16:51:06 -07002508 setFieldsFromRefMessage(mComposeMode, mAccount.name);
Mindy Pereira8eca57a2012-03-20 16:42:34 -07002509 }
Mindy Pereiraef388302012-06-18 19:07:44 -07002510 boolean showCc = false;
2511 boolean showBcc = false;
2512 if (mDraft != null) {
2513 // Following desktop behavior, if the user has added a BCC
2514 // field to a draft, we show it regardless of compose mode.
2515 showBcc = !TextUtils.isEmpty(mDraft.bcc);
2516 // Use the draft to determine what to populate.
2517 // If the Bcc field is showing, show the Cc field whether it is populated or not.
2518 showCc = showBcc || (!TextUtils.isEmpty(mDraft.cc) && mComposeMode == REPLY_ALL);
2519 } else if (mRefMessage != null) {
mindyp9b1ac572012-09-27 14:12:00 -07002520 showCc = !TextUtils.isEmpty(mCc.getText());
Mindy Pereiraef388302012-06-18 19:07:44 -07002521 }
2522 mCcBccView.show(false, showCc, showBcc);
Mindy Pereiraa26b54e2012-01-06 12:54:33 -08002523 }
Mindy Pereiraef388302012-06-18 19:07:44 -07002524 updateHideOrShowCcBcc();
Mindy Pereiracbfb75a2012-06-25 14:52:23 -07002525 initChangeListeners();
Mindy Pereira326c6602012-01-04 15:32:42 -08002526 return true;
2527 }
2528
Mindy Pereirab3112a22012-06-20 12:10:03 -07002529 @VisibleForTesting
2530 protected void resetMessageForModeChange() {
Mindy Pereira154386a2012-01-11 13:02:33 -08002531 // When switching between reply, reply all, forward,
2532 // follow the behavior of webview.
2533 // The contents of the following fields are cleared
2534 // so that they can be populated directly from the
2535 // ref message:
2536 // 1) Any recipient fields
2537 // 2) The subject
2538 mTo.setText("");
2539 mCc.setText("");
2540 mBcc.setText("");
2541 // Any edits to the subject are replaced with the original subject.
2542 mSubject.setText("");
2543
2544 // Any changes to the contents of the following fields are kept:
2545 // 1) Body
2546 // 2) Attachments
2547 // If the user made changes to attachments, keep their changes.
2548 if (!mAttachmentsChanged) {
2549 mAttachmentsView.deleteAllAttachments();
2550 }
2551 }
2552
Mindy Pereira326c6602012-01-04 15:32:42 -08002553 private class ComposeModeAdapter extends ArrayAdapter<String> {
2554
2555 private LayoutInflater mInflater;
2556
2557 public ComposeModeAdapter(Context context) {
2558 super(context, R.layout.compose_mode_item, R.id.mode, getResources()
2559 .getStringArray(R.array.compose_modes));
2560 }
2561
2562 private LayoutInflater getInflater() {
2563 if (mInflater == null) {
2564 mInflater = LayoutInflater.from(getContext());
2565 }
2566 return mInflater;
2567 }
2568
2569 @Override
2570 public View getView(int position, View convertView, ViewGroup parent) {
2571 if (convertView == null) {
2572 convertView = getInflater().inflate(R.layout.compose_mode_display_item, null);
2573 }
2574 ((TextView) convertView.findViewById(R.id.mode)).setText(getItem(position));
2575 return super.getView(position, convertView, parent);
2576 }
2577 }
Mindy Pereira46ce0b12012-01-05 10:32:15 -08002578
2579 @Override
2580 public void onRespondInline(String text) {
2581 appendToBody(text, false);
mindyp40882432012-09-06 11:07:40 -07002582 mQuotedTextView.setUpperDividerVisible(false);
2583 mTo.requestFocus();
Mindy Pereira46ce0b12012-01-05 10:32:15 -08002584 }
2585
2586 /**
2587 * Append text to the body of the message. If there is no existing body
2588 * text, just sets the body to text.
2589 *
2590 * @param text
2591 * @param withSignature True to append a signature.
2592 */
2593 public void appendToBody(CharSequence text, boolean withSignature) {
Mindy Pereiraeaea9f12012-01-10 15:05:27 -08002594 Editable bodyText = mBodyView.getEditableText();
Mindy Pereira46ce0b12012-01-05 10:32:15 -08002595 if (bodyText != null && bodyText.length() > 0) {
2596 bodyText.append(text);
2597 } else {
2598 setBody(text, withSignature);
2599 }
2600 }
2601
2602 /**
2603 * Set the body of the message.
Mindy Pereirabdf7a402012-03-01 15:23:26 -08002604 *
Mindy Pereira46ce0b12012-01-05 10:32:15 -08002605 * @param text
2606 * @param withSignature True to append a signature.
2607 */
2608 public void setBody(CharSequence text, boolean withSignature) {
Mindy Pereiraeaea9f12012-01-10 15:05:27 -08002609 mBodyView.setText(text);
Mindy Pereirabdf7a402012-03-01 15:23:26 -08002610 if (withSignature) {
2611 appendSignature();
2612 }
2613 }
2614
2615 private void appendSignature() {
Mindy Pereirab13917c2012-03-29 08:08:19 -07002616 String newSignature = mCachedSettings != null ? mCachedSettings.signature : null;
Mindy Pereira433b1982012-04-03 11:53:07 -07002617 boolean hasFocus = mBodyView.hasFocus();
Mindy Pereirab13917c2012-03-29 08:08:19 -07002618 if (!TextUtils.equals(newSignature, mSignature)) {
2619 mSignature = newSignature;
2620 if (!TextUtils.isEmpty(mSignature)
2621 && getSignatureStartPosition(mSignature,
2622 mBodyView.getText().toString()) < 0) {
2623 // Appending a signature does not count as changing text.
2624 mBodyView.removeTextChangedListener(this);
2625 mBodyView.append(convertToPrintableSignature(mSignature));
2626 mBodyView.addTextChangedListener(this);
2627 }
Mindy Pereira433b1982012-04-03 11:53:07 -07002628 if (hasFocus) {
2629 focusBody();
2630 }
Mindy Pereirabdf7a402012-03-01 15:23:26 -08002631 }
2632 }
2633
2634 private String convertToPrintableSignature(String signature) {
2635 String signatureResource = getResources().getString(R.string.signature);
2636 if (signature == null) {
2637 signature = "";
2638 }
2639 return String.format(signatureResource, signature);
Mindy Pereira46ce0b12012-01-05 10:32:15 -08002640 }
Mindy Pereira1a95a572012-01-05 12:21:29 -08002641
Mindy Pereira5a85e2b2012-01-11 09:53:32 -08002642 @Override
2643 public void onAccountChanged() {
Mindy Pereira92551d02012-04-05 11:31:12 -07002644 mReplyFromAccount = mFromSpinner.getCurrentAccount();
2645 if (!mAccount.equals(mReplyFromAccount.account)) {
Paul Westbrookb1f573c2012-04-06 11:38:28 -07002646 setAccount(mReplyFromAccount.account);
2647
Mindy Pereira181df782012-03-01 13:32:44 -08002648 // TODO: handle discarding attachments when switching accounts.
2649 // Only enable save for this draft if there is any other content
2650 // in the message.
2651 if (!isBlank()) {
2652 enableSave(true);
2653 }
2654 mReplyFromChanged = true;
2655 initRecipients();
Mindy Pereira82cc5662012-01-09 17:29:30 -08002656 }
Mindy Pereira1a95a572012-01-05 12:21:29 -08002657 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002658
2659 public void enableSave(boolean enabled) {
2660 if (mSave != null) {
2661 mSave.setEnabled(enabled);
2662 }
2663 }
2664
2665 public void enableSend(boolean enabled) {
2666 if (mSend != null) {
2667 mSend.setEnabled(enabled);
2668 }
2669 }
2670
2671 /**
2672 * Handles button clicks from any error dialogs dealing with sending
2673 * a message.
2674 */
2675 @Override
2676 public void onClick(DialogInterface dialog, int which) {
2677 switch (which) {
2678 case DialogInterface.BUTTON_POSITIVE: {
2679 doDiscardWithoutConfirmation(true /* show toast */ );
2680 break;
2681 }
2682 case DialogInterface.BUTTON_NEGATIVE: {
2683 // If the user cancels the send, re-enable the send button.
2684 enableSend(true);
2685 break;
2686 }
2687 }
2688
2689 }
2690
Mindy Pereiraefe3d252012-03-01 14:20:44 -08002691 private void doDiscard() {
2692 new AlertDialog.Builder(this).setMessage(R.string.confirm_discard_text)
2693 .setPositiveButton(R.string.ok, this)
2694 .setNegativeButton(R.string.cancel, null)
2695 .create().show();
2696 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002697 /**
2698 * Effectively discard the current message.
2699 *
2700 * This method is either invoked from the menu or from the dialog
2701 * once the user has confirmed that they want to discard the message.
2702 * @param showToast show "Message discarded" toast if true
2703 */
2704 private void doDiscardWithoutConfirmation(boolean showToast) {
Mindy Pereira7ed1c112012-01-18 10:59:25 -08002705 synchronized (mDraftLock) {
Mindy Pereira82cc5662012-01-09 17:29:30 -08002706 if (mDraftId != UIProvider.INVALID_MESSAGE_ID) {
2707 ContentValues values = new ContentValues();
Paul Westbrookb7050e62012-03-20 12:59:44 -07002708 values.put(BaseColumns._ID, mDraftId);
Marc Blank78ea8e22012-08-04 11:14:06 -07002709 if (!mAccount.expungeMessageUri.equals(Uri.EMPTY)) {
Mindy Pereiracfb7f332012-02-28 10:23:43 -08002710 getContentResolver().update(mAccount.expungeMessageUri, values, null, null);
2711 } else {
Marc Blank0bbc8582012-04-23 15:07:57 -07002712 getContentResolver().delete(mDraft.uri, null, null);
Mindy Pereiracfb7f332012-02-28 10:23:43 -08002713 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002714 // This is not strictly necessary (since we should not try to
2715 // save the draft after calling this) but it ensures that if we
2716 // do save again for some reason we make a new draft rather than
2717 // trying to resave an expunged draft.
2718 mDraftId = UIProvider.INVALID_MESSAGE_ID;
2719 }
2720 }
2721
2722 if (showToast) {
2723 // Display a toast to let the user know
2724 Toast.makeText(this, R.string.message_discarded, Toast.LENGTH_SHORT).show();
2725 }
2726
2727 // This prevents the draft from being saved in onPause().
2728 discardChanges();
2729 finish();
2730 }
2731
Mindy Pereiraeaea9f12012-01-10 15:05:27 -08002732 private void saveIfNeeded() {
2733 if (mAccount == null) {
2734 // We have not chosen an account yet so there's no way that we can save. This is ok,
2735 // though, since we are saving our state before AccountsActivity is activated. Thus, the
2736 // user has not interacted with us yet and there is no real state to save.
2737 return;
2738 }
2739
2740 if (shouldSave()) {
Mindy Pereira48e31b02012-05-30 13:12:24 -07002741 doSave(!mAddingAttachment /* show toast */);
Mindy Pereiraeaea9f12012-01-10 15:05:27 -08002742 }
2743 }
2744
Mindy Pereiraeaea9f12012-01-10 15:05:27 -08002745 @Override
2746 public void onAttachmentDeleted() {
2747 mAttachmentsChanged = true;
mindyp40882432012-09-06 11:07:40 -07002748 // If we are showing any attachments, make sure we have an upper
2749 // divider.
2750 mQuotedTextView.setUpperDividerVisible(mAttachmentsView.getAttachments().size() > 0);
Mindy Pereiraeaea9f12012-01-10 15:05:27 -08002751 updateSaveUi();
2752 }
Mindy Pereira75f66632012-01-11 11:42:02 -08002753
mindyp40882432012-09-06 11:07:40 -07002754 @Override
2755 public void onAttachmentAdded() {
2756 mQuotedTextView.setUpperDividerVisible(mAttachmentsView.getAttachments().size() > 0);
2757 mAttachmentsView.focusLastAttachment();
2758 }
Mindy Pereira75f66632012-01-11 11:42:02 -08002759
2760 /**
2761 * This is called any time one of our text fields changes.
2762 */
Marc Blank0bbc8582012-04-23 15:07:57 -07002763 @Override
Mindy Pereira75f66632012-01-11 11:42:02 -08002764 public void afterTextChanged(Editable s) {
2765 mTextChanged = true;
2766 updateSaveUi();
2767 }
2768
2769 @Override
2770 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
2771 // Do nothing.
2772 }
2773
Marc Blank0bbc8582012-04-23 15:07:57 -07002774 @Override
Mindy Pereira75f66632012-01-11 11:42:02 -08002775 public void onTextChanged(CharSequence s, int start, int before, int count) {
2776 // Do nothing.
2777 }
2778
2779
2780 // There is a big difference between the text associated with an address changing
2781 // to add the display name or to format properly and a recipient being added or deleted.
2782 // Make sure we only notify of changes when a recipient has been added or deleted.
2783 private class RecipientTextWatcher implements TextWatcher {
2784 private HashMap<String, Integer> mContent = new HashMap<String, Integer>();
2785
2786 private RecipientEditTextView mView;
2787
2788 private TextWatcher mListener;
2789
2790 public RecipientTextWatcher(RecipientEditTextView view, TextWatcher listener) {
2791 mView = view;
2792 mListener = listener;
2793 }
2794
2795 @Override
2796 public void afterTextChanged(Editable s) {
2797 if (hasChanged()) {
2798 mListener.afterTextChanged(s);
2799 }
2800 }
2801
2802 private boolean hasChanged() {
2803 String[] currRecips = tokenizeRecips(getAddressesFromList(mView));
2804 int totalCount = currRecips.length;
2805 int totalPrevCount = 0;
2806 for (Entry<String, Integer> entry : mContent.entrySet()) {
2807 totalPrevCount += entry.getValue();
2808 }
2809 if (totalCount != totalPrevCount) {
2810 return true;
2811 }
2812
2813 for (String recip : currRecips) {
2814 if (!mContent.containsKey(recip)) {
2815 return true;
2816 } else {
2817 int count = mContent.get(recip) - 1;
2818 if (count < 0) {
2819 return true;
2820 } else {
2821 mContent.put(recip, count);
2822 }
2823 }
2824 }
2825 return false;
2826 }
2827
2828 private String[] tokenizeRecips(String[] recips) {
2829 // Tokenize them all and put them in the list.
2830 String[] recipAddresses = new String[recips.length];
2831 for (int i = 0; i < recips.length; i++) {
2832 recipAddresses[i] = Rfc822Tokenizer.tokenize(recips[i])[0].getAddress();
2833 }
2834 return recipAddresses;
2835 }
2836
2837 @Override
2838 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
2839 String[] recips = tokenizeRecips(getAddressesFromList(mView));
2840 for (String recip : recips) {
2841 if (!mContent.containsKey(recip)) {
2842 mContent.put(recip, 1);
2843 } else {
2844 mContent.put(recip, (mContent.get(recip)) + 1);
2845 }
2846 }
2847 }
2848
2849 @Override
2850 public void onTextChanged(CharSequence s, int start, int before, int count) {
2851 // Do nothing.
2852 }
2853 }
Mindy Pereirae011b1d2012-06-18 13:45:26 -07002854
2855 public static void registerTestSendOrSaveCallback(SendOrSaveCallback testCallback) {
2856 if (sTestSendOrSaveCallback != null && testCallback != null) {
2857 throw new IllegalStateException("Attempting to register more than one test callback");
2858 }
2859 sTestSendOrSaveCallback = testCallback;
2860 }
Mindy Pereirabddd6f32012-06-20 12:10:03 -07002861
2862 @VisibleForTesting
2863 protected ArrayList<Attachment> getAttachments() {
2864 return mAttachmentsView.getAttachments();
2865 }
Mindy Pereira96a7f7a2012-07-09 16:51:06 -07002866
2867 @Override
2868 public Loader<Cursor> onCreateLoader(int id, Bundle args) {
2869 switch (id) {
2870 case REFERENCE_MESSAGE_LOADER:
2871 return new CursorLoader(this, mRefMessageUri, UIProvider.MESSAGE_PROJECTION, null,
2872 null, null);
Mindy Pereirab199d172012-08-13 11:04:03 -07002873 case LOADER_ACCOUNT_CURSOR:
2874 return new CursorLoader(this, MailAppProvider.getAccountsUri(),
2875 UIProvider.ACCOUNTS_PROJECTION, null, null, null);
Mindy Pereira96a7f7a2012-07-09 16:51:06 -07002876 }
2877 return null;
2878 }
2879
2880 @Override
2881 public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
Mindy Pereirab199d172012-08-13 11:04:03 -07002882 int id = loader.getId();
2883 switch (id) {
2884 case REFERENCE_MESSAGE_LOADER:
2885 if (data != null && data.moveToFirst()) {
2886 mRefMessage = new Message(data);
2887 // We set these based on EXTRA_TO.
2888 mRefMessage.to = null;
2889 mRefMessage.from = null;
2890 Intent intent = getIntent();
2891 int action = intent.getIntExtra(EXTRA_ACTION, COMPOSE);
2892 initFromRefMessage(action, mAccount.name);
2893 finishSetup(action, intent, null, true);
2894 if (action != FORWARD) {
2895 String to = intent.getStringExtra(EXTRA_TO);
2896 if (!TextUtils.isEmpty(to)) {
2897 clearChangeListeners();
2898 mTo.append(to);
2899 initChangeListeners();
2900 }
2901 }
2902 } else {
2903 finish();
Mindy Pereira96a7f7a2012-07-09 16:51:06 -07002904 }
Mindy Pereirab199d172012-08-13 11:04:03 -07002905 break;
2906 case LOADER_ACCOUNT_CURSOR:
2907 if (data != null && data.moveToFirst()) {
2908 // there are accounts now!
2909 Account account;
2910 ArrayList<Account> accounts = new ArrayList<Account>();
2911 ArrayList<Account> initializedAccounts = new ArrayList<Account>();
2912 do {
2913 account = new Account(data);
Paul Westbrookdfa1dec2012-09-26 16:27:28 -07002914 if (account.isAccountReady()) {
Mindy Pereirab199d172012-08-13 11:04:03 -07002915 initializedAccounts.add(account);
2916 }
2917 accounts.add(account);
2918 } while (data.moveToNext());
2919 if (initializedAccounts.size() > 0) {
2920 findViewById(R.id.wait).setVisibility(View.GONE);
2921 getLoaderManager().destroyLoader(LOADER_ACCOUNT_CURSOR);
2922 findViewById(R.id.compose).setVisibility(View.VISIBLE);
2923 mAccounts = accounts.toArray(new Account[initializedAccounts.size()]);
2924 finishCreate();
2925 invalidateOptionsMenu();
2926 } else {
2927 // Show "waiting"
2928 account = accounts.size() > 0 ? accounts.get(0) : null;
2929 showWaitFragment(account);
2930 }
2931 }
2932 break;
Mindy Pereira96a7f7a2012-07-09 16:51:06 -07002933 }
2934 }
2935
Mindy Pereirab199d172012-08-13 11:04:03 -07002936 private void showWaitFragment(Account account) {
2937 WaitFragment fragment = getWaitFragment();
2938 if (fragment != null) {
2939 fragment.updateAccount(account);
2940 } else {
2941 findViewById(R.id.wait).setVisibility(View.VISIBLE);
2942 replaceFragment(WaitFragment.newInstance(account, true),
2943 FragmentTransaction.TRANSIT_FRAGMENT_OPEN, TAG_WAIT);
2944 }
2945 }
2946
2947 private WaitFragment getWaitFragment() {
2948 return (WaitFragment) getFragmentManager().findFragmentByTag(TAG_WAIT);
2949 }
2950
2951 private int replaceFragment(Fragment fragment, int transition, String tag) {
2952 FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
2953 fragmentTransaction.addToBackStack(null);
2954 fragmentTransaction.setTransition(transition);
2955 fragmentTransaction.replace(R.id.wait, fragment, tag);
2956 final int transactionId = fragmentTransaction.commitAllowingStateLoss();
2957 return transactionId;
2958 }
2959
Mindy Pereira96a7f7a2012-07-09 16:51:06 -07002960 @Override
2961 public void onLoaderReset(Loader<Cursor> arg0) {
2962 // Do nothing.
2963 }
2964}