blob: 14e6da8521f576021ac24d6004061378c421e476 [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
Andrew Sappersteinebc4bda2012-08-07 10:53:28 -070019import android.animation.LayoutTransition;
Mindy Pereira326c6602012-01-04 15:32:42 -080020import android.app.ActionBar;
Andy Huang5c5fd572012-04-08 18:19:29 -070021import android.app.ActionBar.OnNavigationListener;
22import android.app.Activity;
Mindy Pereira82cc5662012-01-09 17:29:30 -080023import android.app.ActivityManager;
24import android.app.AlertDialog;
25import android.app.Dialog;
Mindy Pereirab199d172012-08-13 11:04:03 -070026import android.app.Fragment;
27import android.app.FragmentManager;
28import android.app.FragmentTransaction;
Mindy Pereira96a7f7a2012-07-09 16:51:06 -070029import android.app.LoaderManager;
Mindy Pereira6349a042012-01-04 11:25:01 -080030import android.content.ContentResolver;
Mindy Pereira82cc5662012-01-09 17:29:30 -080031import android.content.ContentValues;
Mindy Pereira6349a042012-01-04 11:25:01 -080032import android.content.Context;
Mindy Pereira96a7f7a2012-07-09 16:51:06 -070033import android.content.CursorLoader;
Mindy Pereira82cc5662012-01-09 17:29:30 -080034import android.content.DialogInterface;
Mindy Pereira6349a042012-01-04 11:25:01 -080035import android.content.Intent;
Mindy Pereira96a7f7a2012-07-09 16:51:06 -070036import android.content.Loader;
Mindy Pereira82cc5662012-01-09 17:29:30 -080037import android.content.pm.ActivityInfo;
Mindy Pereira7ed1c112012-01-18 10:59:25 -080038import android.database.Cursor;
Mindy Pereira6349a042012-01-04 11:25:01 -080039import android.net.Uri;
Mindy Pereira8e9305e2011-12-13 14:25:04 -080040import android.os.Bundle;
Mindy Pereira82cc5662012-01-09 17:29:30 -080041import android.os.Handler;
42import android.os.HandlerThread;
Paul Westbrookf97588b2012-03-20 11:11:37 -070043import android.os.Parcelable;
Mindy Pereira82cc5662012-01-09 17:29:30 -080044import android.provider.BaseColumns;
Mindy Pereira46ce0b12012-01-05 10:32:15 -080045import android.text.Editable;
Mindy Pereira82cc5662012-01-09 17:29:30 -080046import android.text.Html;
47import android.text.Spanned;
Paul Westbrookc1827622012-01-06 11:27:12 -080048import android.text.TextUtils;
Mindy Pereira82cc5662012-01-09 17:29:30 -080049import android.text.TextWatcher;
Mindy Pereira46ce0b12012-01-05 10:32:15 -080050import android.text.util.Rfc822Token;
Mindy Pereirac17d0732011-12-29 10:46:19 -080051import android.text.util.Rfc822Tokenizer;
Mindy Pereira3cd4f402012-07-17 11:16:18 -070052import android.view.Gravity;
mindyp62d3ec72012-08-24 13:04:09 -070053import android.view.KeyEvent;
Mindy Pereira326c6602012-01-04 15:32:42 -080054import android.view.LayoutInflater;
Mindy Pereirab47f3e22011-12-13 14:25:04 -080055import android.view.Menu;
56import android.view.MenuInflater;
57import android.view.MenuItem;
Mindy Pereira8e9305e2011-12-13 14:25:04 -080058import android.view.View;
59import android.view.View.OnClickListener;
Andy Huang5c5fd572012-04-08 18:19:29 -070060import android.view.ViewGroup;
Mindy Pereiraeaea9f12012-01-10 15:05:27 -080061import android.view.inputmethod.BaseInputConnection;
mindyp62d3ec72012-08-24 13:04:09 -070062import android.view.inputmethod.EditorInfo;
Mindy Pereira326c6602012-01-04 15:32:42 -080063import android.widget.ArrayAdapter;
Mindy Pereira8e9305e2011-12-13 14:25:04 -080064import android.widget.Button;
Mindy Pereira433b1982012-04-03 11:53:07 -070065import android.widget.EditText;
Mindy Pereira6349a042012-01-04 11:25:01 -080066import android.widget.TextView;
Mindy Pereira013194c2012-01-06 15:09:33 -080067import android.widget.Toast;
Mindy Pereira7b56a612011-12-14 12:32:28 -080068
Mindy Pereirac17d0732011-12-29 10:46:19 -080069import com.android.common.Rfc822Validator;
Andy Huang5c5fd572012-04-08 18:19:29 -070070import com.android.ex.chips.RecipientEditTextView;
71import com.android.mail.R;
mindyp40882432012-09-06 11:07:40 -070072import com.android.mail.compose.AttachmentsView.AttachmentAddedOrDeletedListener;
Mindy Pereira9932dee2012-01-10 16:09:50 -080073import com.android.mail.compose.AttachmentsView.AttachmentFailureException;
Mindy Pereira5a85e2b2012-01-11 09:53:32 -080074import com.android.mail.compose.FromAddressSpinner.OnAccountChangedListener;
Andy Huang30e2c242012-01-06 18:14:30 -080075import com.android.mail.compose.QuotedTextView.RespondInlineListener;
Mindy Pereira33fe9082012-01-09 16:24:30 -080076import com.android.mail.providers.Account;
Andy Huang30e2c242012-01-06 18:14:30 -080077import com.android.mail.providers.Address;
78import com.android.mail.providers.Attachment;
Mindy Pereira5ad02912012-07-09 09:57:18 -070079import com.android.mail.providers.Folder;
Mindy Pereira47d0e652012-07-23 09:45:07 -070080import com.android.mail.providers.MailAppProvider;
Mindy Pereira3ce64e72012-01-13 14:29:45 -080081import com.android.mail.providers.Message;
Mindy Pereira82cc5662012-01-09 17:29:30 -080082import com.android.mail.providers.MessageModification;
Mindy Pereira92551d02012-04-05 11:31:12 -070083import com.android.mail.providers.ReplyFromAccount;
Mindy Pereira181df782012-03-01 13:32:44 -080084import com.android.mail.providers.Settings;
Andy Huang30e2c242012-01-06 18:14:30 -080085import com.android.mail.providers.UIProvider;
Mindy Pereira3ca5bad2012-04-16 11:02:42 -070086import com.android.mail.providers.UIProvider.AccountCapabilities;
Mindy Pereira12575862012-03-21 16:30:54 -070087import com.android.mail.providers.UIProvider.DraftType;
Mindy Pereirafa20c1a2012-07-23 13:00:02 -070088import com.android.mail.ui.MailActivity;
Mindy Pereirab199d172012-08-13 11:04:03 -070089import com.android.mail.ui.WaitFragment;
Mark Wei62066e42012-09-13 12:07:02 -070090import com.android.mail.ui.AttachmentTile.AttachmentPreview;
Paul Westbrook92227f62012-03-20 10:32:51 -070091import com.android.mail.utils.AccountUtils;
Mark Wei434f2942012-08-24 11:54:02 -070092import com.android.mail.utils.AttachmentUtils;
Paul Westbrookb334c902012-06-25 11:42:46 -070093import com.android.mail.utils.LogTag;
Andy Huang30e2c242012-01-06 18:14:30 -080094import com.android.mail.utils.LogUtils;
Andy Huang30e2c242012-01-06 18:14:30 -080095import com.android.mail.utils.Utils;
Mindy Pereira46ce0b12012-01-05 10:32:15 -080096import com.google.common.annotations.VisibleForTesting;
Mindy Pereira82cc5662012-01-09 17:29:30 -080097import com.google.common.collect.Lists;
Mindy Pereira4a27ea92012-01-05 15:55:25 -080098import com.google.common.collect.Sets;
Mindy Pereira8e9305e2011-12-13 14:25:04 -080099
Mindy Pereira62de1b12012-04-06 12:17:56 -0700100import org.json.JSONException;
101
Mindy Pereira8eca57a2012-03-20 16:42:34 -0700102import java.io.UnsupportedEncodingException;
103import java.net.URLDecoder;
Mindy Pereira46ce0b12012-01-05 10:32:15 -0800104import java.util.ArrayList;
Paul Westbrookbb87b7f2012-03-20 16:20:07 -0700105import java.util.Arrays;
Mindy Pereira46ce0b12012-01-05 10:32:15 -0800106import java.util.Collection;
Mindy Pereira75f66632012-01-11 11:42:02 -0800107import java.util.HashMap;
Mindy Pereira46ce0b12012-01-05 10:32:15 -0800108import java.util.HashSet;
109import java.util.List;
Paul Westbrook1c078cf2012-03-20 16:18:51 -0700110import java.util.Map.Entry;
Paul Westbrookbb87b7f2012-03-20 16:20:07 -0700111import java.util.Set;
Mindy Pereira82cc5662012-01-09 17:29:30 -0800112import java.util.concurrent.ConcurrentHashMap;
Mindy Pereira46ce0b12012-01-05 10:32:15 -0800113
114public class ComposeActivity extends Activity implements OnClickListener, OnNavigationListener,
Mindy Pereira5a85e2b2012-01-11 09:53:32 -0800115 RespondInlineListener, DialogInterface.OnClickListener, TextWatcher,
mindyp40882432012-09-06 11:07:40 -0700116 AttachmentAddedOrDeletedListener, OnAccountChangedListener, LoaderManager.LoaderCallbacks<Cursor>,
mindyp62d3ec72012-08-24 13:04:09 -0700117 TextView.OnEditorActionListener {
Mindy Pereira6349a042012-01-04 11:25:01 -0800118 // Identifiers for which type of composition this is
Mindy Pereira36bbcae2012-04-25 09:27:04 -0700119 static final int COMPOSE = -1;
120 static final int REPLY = 0;
121 static final int REPLY_ALL = 1;
122 static final int FORWARD = 2;
123 static final int EDIT_DRAFT = 3;
Mindy Pereira6349a042012-01-04 11:25:01 -0800124
125 // Integer extra holding one of the above compose action
Mindy Pereira96a7f7a2012-07-09 16:51:06 -0700126 protected static final String EXTRA_ACTION = "action";
Mindy Pereira6349a042012-01-04 11:25:01 -0800127
Mindy Pereira326689d2012-05-17 10:14:14 -0700128 private static final String EXTRA_SHOW_CC = "showCc";
129 private static final String EXTRA_SHOW_BCC = "showBcc";
Mindy Pereiraa34c9a02012-04-17 14:10:53 -0700130
Mindy Pereira8eca57a2012-03-20 16:42:34 -0700131 private static final String UTF8_ENCODING_NAME = "UTF-8";
132
133 private static final String MAIL_TO = "mailto";
134
Mindy Pereira8eca57a2012-03-20 16:42:34 -0700135 private static final String EXTRA_SUBJECT = "subject";
136
137 private static final String EXTRA_BODY = "body";
138
Mindy Pereira9a42bb42012-04-18 15:21:33 -0700139 private static final String EXTRA_FROM_ACCOUNT_STRING = "fromAccountString";
140
Mark Wei62066e42012-09-13 12:07:02 -0700141 private static final String EXTRA_ATTACHMENT_PREVIEWS = "attachmentPreviews";
142
Mindy Pereira8eca57a2012-03-20 16:42:34 -0700143 // Extra that we can get passed from other activities
144 private static final String EXTRA_TO = "to";
145 private static final String EXTRA_CC = "cc";
146 private static final String EXTRA_BCC = "bcc";
147
148 // List of all the fields
149 static final String[] ALL_EXTRAS = { EXTRA_SUBJECT, EXTRA_BODY, EXTRA_TO, EXTRA_CC, EXTRA_BCC };
150
Mindy Pereira82cc5662012-01-09 17:29:30 -0800151 private static SendOrSaveCallback sTestSendOrSaveCallback = null;
152 // Map containing information about requests to create new messages, and the id of the
153 // messages that were the result of those requests.
154 //
155 // This map is used when the activity that initiated the save a of a new message, is killed
156 // before the save has completed (and when we know the id of the newly created message). When
157 // a save is completed, the service that is running in the background, will update the map
158 //
159 // When a new ComposeActivity instance is created, it will attempt to use the information in
160 // the previously instantiated map. If ComposeActivity.onCreate() is called, with a bundle
161 // (restoring data from a previous instance), and the map hasn't been created, we will attempt
162 // to populate the map with data stored in shared preferences.
163 private static ConcurrentHashMap<Integer, Long> sRequestMessageIdMap = null;
164 // Key used to store the above map
165 private static final String CACHED_MESSAGE_REQUEST_IDS_KEY = "cache-message-request-ids";
Mindy Pereira6349a042012-01-04 11:25:01 -0800166 /**
167 * Notifies the {@code Activity} that the caller is an Email
168 * {@code Activity}, so that the back behavior may be modified accordingly.
169 *
170 * @see #onAppUpPressed
171 */
Paul Westbrookdaecb4b2012-05-31 10:21:26 -0700172 public static final String EXTRA_FROM_EMAIL_TASK = "fromemail";
Mindy Pereira6349a042012-01-04 11:25:01 -0800173
Mindy Pereirae011b1d2012-06-18 13:45:26 -0700174 public static final String EXTRA_ATTACHMENTS = "attachments";
Paul Westbrookf97588b2012-03-20 11:11:37 -0700175
Mindy Pereira3ce64e72012-01-13 14:29:45 -0800176 // If this is a reply/forward then this extra will hold the original message
Mindy Pereira36bbcae2012-04-25 09:27:04 -0700177 private static final String EXTRA_IN_REFERENCE_TO_MESSAGE = "in-reference-to-message";
Mindy Pereirab18e5a92012-07-10 11:47:21 -0700178 // If this is a reply/forward then this extra will hold a uri we must query
179 // to get the original message.
180 protected static final String EXTRA_IN_REFERENCE_TO_MESSAGE_URI = "in-reference-to-message-uri";
Mark Wei434f2942012-08-24 11:54:02 -0700181 // If this is an action to edit an existing draft message, this extra will hold the
Paul Westbrookbb87b7f2012-03-20 16:20:07 -0700182 // draft message
183 private static final String ORIGINAL_DRAFT_MESSAGE = "original-draft-message";
Mindy Pereira4a27ea92012-01-05 15:55:25 -0800184 private static final String END_TOKEN = ", ";
Paul Westbrookb334c902012-06-25 11:42:46 -0700185 private static final String LOG_TAG = LogTag.getLogTag();
Mindy Pereira013194c2012-01-06 15:09:33 -0800186 // Request numbers for activities we start
187 private static final int RESULT_PICK_ATTACHMENT = 1;
188 private static final int RESULT_CREATE_ACCOUNT = 2;
Mindy Pereira8eca57a2012-03-20 16:42:34 -0700189 // TODO(mindyp) set mime-type for auto send?
Mindy Pereirae011b1d2012-06-18 13:45:26 -0700190 public static final String AUTO_SEND_ACTION = "com.android.mail.action.AUTO_SEND";
Mindy Pereira8eca57a2012-03-20 16:42:34 -0700191
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700192 private static final String EXTRA_SELECTED_REPLY_FROM_ACCOUNT = "replyFromAccount";
193 private static final String EXTRA_REQUEST_ID = "requestId";
194 private static final String EXTRA_FOCUS_SELECTION_START = "focusSelectionStart";
195 private static final String EXTRA_FOCUS_SELECTION_END = null;
196 private static final String EXTRA_MESSAGE = "extraMessage";
Mindy Pereira96a7f7a2012-07-09 16:51:06 -0700197 private static final int REFERENCE_MESSAGE_LOADER = 0;
Mindy Pereirab199d172012-08-13 11:04:03 -0700198 private static final int LOADER_ACCOUNT_CURSOR = 1;
Mindy Pereira47d0e652012-07-23 09:45:07 -0700199 private static final String EXTRA_SELECTED_ACCOUNT = "selectedAccount";
Mindy Pereirab199d172012-08-13 11:04:03 -0700200 private static final String TAG_WAIT = "wait-fragment";
Mindy Pereira2db7d4a2012-08-15 11:00:02 -0700201 private static final String MIME_TYPE_PHOTO = "image/*";
202 private static final String MIME_TYPE_VIDEO = "video/*";
Mindy Pereira8e9305e2011-12-13 14:25:04 -0800203
Mindy Pereira82cc5662012-01-09 17:29:30 -0800204 /**
205 * A single thread for running tasks in the background.
206 */
207 private Handler mSendSaveTaskHandler = null;
Mindy Pereirac17d0732011-12-29 10:46:19 -0800208 private RecipientEditTextView mTo;
209 private RecipientEditTextView mCc;
210 private RecipientEditTextView mBcc;
Mindy Pereira8e9305e2011-12-13 14:25:04 -0800211 private Button mCcBccButton;
212 private CcBccView mCcBccView;
Mindy Pereira7b56a612011-12-14 12:32:28 -0800213 private AttachmentsView mAttachmentsView;
Mindy Pereira33fe9082012-01-09 16:24:30 -0800214 private Account mAccount;
Mindy Pereira92551d02012-04-05 11:31:12 -0700215 private ReplyFromAccount mReplyFromAccount;
Mindy Pereira181df782012-03-01 13:32:44 -0800216 private Settings mCachedSettings;
Mindy Pereira82cc5662012-01-09 17:29:30 -0800217 private Rfc822Validator mValidator;
Mindy Pereira6349a042012-01-04 11:25:01 -0800218 private TextView mSubject;
219
Mindy Pereira326c6602012-01-04 15:32:42 -0800220 private ComposeModeAdapter mComposeModeAdapter;
221 private int mComposeMode = -1;
Mindy Pereira46ce0b12012-01-05 10:32:15 -0800222 private boolean mForward;
223 private String mRecipient;
Mindy Pereira46ce0b12012-01-05 10:32:15 -0800224 private QuotedTextView mQuotedTextView;
Mindy Pereira433b1982012-04-03 11:53:07 -0700225 private EditText mBodyView;
Mindy Pereira1a95a572012-01-05 12:21:29 -0800226 private View mFromStatic;
Mindy Pereira2eb17322012-03-07 10:07:34 -0800227 private TextView mFromStaticText;
Mindy Pereiraeaea9f12012-01-10 15:05:27 -0800228 private View mFromSpinnerWrapper;
Mindy Pereira1883b342012-06-20 08:34:56 -0700229 @VisibleForTesting
230 protected FromAddressSpinner mFromSpinner;
Mindy Pereira013194c2012-01-06 15:09:33 -0800231 private boolean mAddingAttachment;
Mindy Pereiraeaea9f12012-01-10 15:05:27 -0800232 private boolean mAttachmentsChanged;
Mindy Pereira82cc5662012-01-09 17:29:30 -0800233 private boolean mTextChanged;
234 private boolean mReplyFromChanged;
235 private MenuItem mSave;
236 private MenuItem mSend;
Mindy Pereira82cc5662012-01-09 17:29:30 -0800237 private AlertDialog mRecipientErrorDialog;
Mindy Pereiraeaea9f12012-01-10 15:05:27 -0800238 private AlertDialog mSendConfirmDialog;
Mindy Pereirab3112a22012-06-20 12:10:03 -0700239 @VisibleForTesting
240 protected Message mRefMessage;
Mindy Pereira7ed1c112012-01-18 10:59:25 -0800241 private long mDraftId = UIProvider.INVALID_MESSAGE_ID;
242 private Message mDraft;
243 private Object mDraftLock = new Object();
mindyp93b079b2012-08-29 16:32:15 -0700244 private View mPhotoAttachmentsButton;
245 private View mVideoAttachmentsButton;
Mindy Pereira3ce64e72012-01-13 14:29:45 -0800246
Mindy Pereira326c6602012-01-04 15:32:42 -0800247 /**
Paul Westbrookdaecb4b2012-05-31 10:21:26 -0700248 * Boolean indicating whether ComposeActivity was launched from a Gmail controlled view.
249 */
250 private boolean mLaunchedFromEmail = false;
Mindy Pereiracbfb75a2012-06-25 14:52:23 -0700251 private RecipientTextWatcher mToListener;
252 private RecipientTextWatcher mCcListener;
253 private RecipientTextWatcher mBccListener;
Mindy Pereirab18e5a92012-07-10 11:47:21 -0700254 private Uri mRefMessageUri;
Mindy Pereirab199d172012-08-13 11:04:03 -0700255 private Bundle mSavedInstanceState;
Paul Westbrookdaecb4b2012-05-31 10:21:26 -0700256
257
258 /**
Mindy Pereira326c6602012-01-04 15:32:42 -0800259 * Can be called from a non-UI thread.
260 */
Mindy Pereira3ce64e72012-01-13 14:29:45 -0800261 public static void editDraft(Context launcher, Account account, Message message) {
Paul Westbrookbb87b7f2012-03-20 16:20:07 -0700262 launch(launcher, account, message, EDIT_DRAFT);
Mindy Pereira326c6602012-01-04 15:32:42 -0800263 }
264
Mindy Pereira6349a042012-01-04 11:25:01 -0800265 /**
266 * Can be called from a non-UI thread.
267 */
Mindy Pereira33fe9082012-01-09 16:24:30 -0800268 public static void compose(Context launcher, Account account) {
Mindy Pereira6349a042012-01-04 11:25:01 -0800269 launch(launcher, account, null, COMPOSE);
270 }
271
272 /**
273 * Can be called from a non-UI thread.
274 */
Mindy Pereira3ce64e72012-01-13 14:29:45 -0800275 public static void reply(Context launcher, Account account, Message message) {
276 launch(launcher, account, message, REPLY);
Mindy Pereira6349a042012-01-04 11:25:01 -0800277 }
278
279 /**
280 * Can be called from a non-UI thread.
281 */
Mindy Pereira3ce64e72012-01-13 14:29:45 -0800282 public static void replyAll(Context launcher, Account account, Message message) {
283 launch(launcher, account, message, REPLY_ALL);
Mindy Pereira6349a042012-01-04 11:25:01 -0800284 }
285
286 /**
287 * Can be called from a non-UI thread.
288 */
Mindy Pereira3ce64e72012-01-13 14:29:45 -0800289 public static void forward(Context launcher, Account account, Message message) {
290 launch(launcher, account, message, FORWARD);
Mindy Pereira6349a042012-01-04 11:25:01 -0800291 }
292
Mindy Pereira3ce64e72012-01-13 14:29:45 -0800293 private static void launch(Context launcher, Account account, Message message, int action) {
Mindy Pereira6349a042012-01-04 11:25:01 -0800294 Intent intent = new Intent(launcher, ComposeActivity.class);
295 intent.putExtra(EXTRA_FROM_EMAIL_TASK, true);
296 intent.putExtra(EXTRA_ACTION, action);
297 intent.putExtra(Utils.EXTRA_ACCOUNT, account);
Paul Westbrookbb87b7f2012-03-20 16:20:07 -0700298 if (action == EDIT_DRAFT) {
299 intent.putExtra(ORIGINAL_DRAFT_MESSAGE, message);
300 } else {
301 intent.putExtra(EXTRA_IN_REFERENCE_TO_MESSAGE, message);
302 }
Mindy Pereira6349a042012-01-04 11:25:01 -0800303 launcher.startActivity(intent);
304 }
Mindy Pereira8e9305e2011-12-13 14:25:04 -0800305
306 @Override
307 public void onCreate(Bundle savedInstanceState) {
308 super.onCreate(savedInstanceState);
Mindy Pereira3528d362012-01-05 14:39:44 -0800309 setContentView(R.layout.compose);
Mindy Pereirab199d172012-08-13 11:04:03 -0700310 mSavedInstanceState = savedInstanceState;
311 checkValidAccounts();
312 }
313
314 private void finishCreate() {
315 Bundle savedInstanceState = mSavedInstanceState;
Mindy Pereira3528d362012-01-05 14:39:44 -0800316 findViews();
Mindy Pereira818143e2012-01-11 13:59:49 -0800317 Intent intent = getIntent();
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700318 Message message;
Mark Wei62066e42012-09-13 12:07:02 -0700319 ArrayList<AttachmentPreview> previews;
Mindy Pereira71c9e562012-05-17 11:01:02 -0700320 boolean showQuotedText = false;
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700321 int action;
Mindy Pereira47d0e652012-07-23 09:45:07 -0700322 // Check for any of the possibly supplied accounts.;
323 Account account = null;
Mindy Pereiraf7fc6c32012-06-19 15:18:33 -0700324 if (hadSavedInstanceStateMessage(savedInstanceState)) {
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700325 action = savedInstanceState.getInt(EXTRA_ACTION, COMPOSE);
326 account = savedInstanceState.getParcelable(Utils.EXTRA_ACCOUNT);
327 message = (Message) savedInstanceState.getParcelable(EXTRA_MESSAGE);
Mark Wei62066e42012-09-13 12:07:02 -0700328
329 previews = savedInstanceState.getParcelableArrayList(EXTRA_ATTACHMENT_PREVIEWS);
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700330 mRefMessage = (Message) savedInstanceState.getParcelable(EXTRA_IN_REFERENCE_TO_MESSAGE);
331 } else {
Mindy Pereira47d0e652012-07-23 09:45:07 -0700332 account = obtainAccount(intent);
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700333 action = intent.getIntExtra(EXTRA_ACTION, COMPOSE);
334 // Initialize the message from the message in the intent
335 message = (Message) intent.getParcelableExtra(ORIGINAL_DRAFT_MESSAGE);
Mark Wei62066e42012-09-13 12:07:02 -0700336 previews = intent.getParcelableArrayListExtra(EXTRA_ATTACHMENT_PREVIEWS);
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700337 mRefMessage = (Message) intent.getParcelableExtra(EXTRA_IN_REFERENCE_TO_MESSAGE);
Mindy Pereirab18e5a92012-07-10 11:47:21 -0700338 mRefMessageUri = (Uri) intent.getParcelableExtra(EXTRA_IN_REFERENCE_TO_MESSAGE_URI);
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700339 }
Mark Wei62066e42012-09-13 12:07:02 -0700340 mAttachmentsView.setAttachmentPreviews(previews);
Paul Westbrook92227f62012-03-20 10:32:51 -0700341
342 setAccount(account);
Mindy Pereira818143e2012-01-11 13:59:49 -0800343 if (mAccount == null) {
344 return;
345 }
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700346
Paul Westbrookdaecb4b2012-05-31 10:21:26 -0700347 if (intent.getBooleanExtra(EXTRA_FROM_EMAIL_TASK, false)) {
348 mLaunchedFromEmail = true;
349 } else if (Intent.ACTION_SEND.equals(intent.getAction())) {
350 final Uri dataUri = intent.getData();
351 if (dataUri != null) {
352 final String dataScheme = intent.getData().getScheme();
353 final String accountScheme = mAccount.composeIntentUri.getScheme();
354 mLaunchedFromEmail = TextUtils.equals(dataScheme, accountScheme);
355 }
356 }
357
Mindy Pereira96a7f7a2012-07-09 16:51:06 -0700358 if (mRefMessageUri != null) {
359 // We have a referenced message that we must look up.
360 getLoaderManager().initLoader(REFERENCE_MESSAGE_LOADER, null, this);
361 return;
362 } else if (message != null && action != EDIT_DRAFT) {
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700363 initFromDraftMessage(message);
364 initQuotedTextFromRefMessage(mRefMessage, action);
Mindy Pereiraa34c9a02012-04-17 14:10:53 -0700365 showCcBcc(savedInstanceState);
Mindy Pereira71c9e562012-05-17 11:01:02 -0700366 showQuotedText = message.appendRefMessageContent;
Paul Westbrookbb87b7f2012-03-20 16:20:07 -0700367 } else if (action == EDIT_DRAFT) {
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700368 initFromDraftMessage(message);
Mindy Pereiraef388302012-06-18 19:07:44 -0700369 boolean showBcc = !TextUtils.isEmpty(message.bcc);
370 boolean showCc = showBcc || !TextUtils.isEmpty(message.cc);
371 mCcBccView.show(false, showCc, showBcc);
Paul Westbrookbb87b7f2012-03-20 16:20:07 -0700372 // Update the action to the draft type of the previous draft
373 switch (message.draftType) {
374 case UIProvider.DraftType.REPLY:
375 action = REPLY;
376 break;
377 case UIProvider.DraftType.REPLY_ALL:
378 action = REPLY_ALL;
379 break;
380 case UIProvider.DraftType.FORWARD:
381 action = FORWARD;
382 break;
383 case UIProvider.DraftType.COMPOSE:
384 default:
385 action = COMPOSE;
386 break;
387 }
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700388 initQuotedTextFromRefMessage(mRefMessage, action);
Mindy Pereira71c9e562012-05-17 11:01:02 -0700389 showQuotedText = message.appendRefMessageContent;
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700390 } else if ((action == REPLY || action == REPLY_ALL || action == FORWARD)) {
391 if (mRefMessage != null) {
392 initFromRefMessage(action, mAccount.name);
Mindy Pereira71c9e562012-05-17 11:01:02 -0700393 showQuotedText = true;
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700394 }
Mindy Pereira8eca57a2012-03-20 16:42:34 -0700395 } else {
396 initFromExtras(intent);
Paul Westbrookbb87b7f2012-03-20 16:20:07 -0700397 }
Mindy Pereira96a7f7a2012-07-09 16:51:06 -0700398 finishSetup(action, intent, savedInstanceState, showQuotedText);
399 }
Paul Westbrookbb87b7f2012-03-20 16:20:07 -0700400
Mindy Pereirab199d172012-08-13 11:04:03 -0700401 private void checkValidAccounts() {
402 mAccounts = AccountUtils.getAccounts(this);
403 if (mAccounts == null || mAccounts.length == 0) {
404 final Intent noAccountIntent = MailAppProvider.getNoAccountIntent(this);
405 if (noAccountIntent != null) {
406 startActivityForResult(noAccountIntent, RESULT_CREATE_ACCOUNT);
407 }
408 } else {
mindyp26d4d2d2012-09-18 17:30:32 -0700409 // If none of the accounts are syncing, setup a watcher.
Mindy Pereirab199d172012-08-13 11:04:03 -0700410 boolean anySyncing = false;
411 for (Account a : mAccounts) {
412 if (a.isAccountIntialized()) {
413 anySyncing = true;
414 break;
415 }
416 }
417 if (!anySyncing) {
418 // There are accounts, but none are sync'd, which is just like having no accounts.
419 mAccounts = null;
420 getLoaderManager().initLoader(LOADER_ACCOUNT_CURSOR, null, this);
421 return;
422 }
423 finishCreate();
424 }
425 }
426
Mindy Pereira47d0e652012-07-23 09:45:07 -0700427 private Account obtainAccount(Intent intent) {
428 Account account = null;
429 Object accountExtra = null;
430 if (intent != null && intent.getExtras() != null) {
431 accountExtra = intent.getExtras().get(Utils.EXTRA_ACCOUNT);
432 if (accountExtra instanceof Account) {
433 return (Account) accountExtra;
mindyp7ae042e2012-08-27 13:27:37 -0700434 } else if (accountExtra instanceof String) {
435 // This is the Account attached to the widget compose intent.
436 account = Account.newinstance((String)accountExtra);
437 if (account != null) {
438 return account;
439 }
Mindy Pereira47d0e652012-07-23 09:45:07 -0700440 }
441 accountExtra = intent.getStringExtra(EXTRA_SELECTED_ACCOUNT);
442 }
443 if (account == null) {
444 final String lastAccountUri = MailAppProvider.getInstance().getLastSentFromAccount();
445 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.
570 if (isFinishing()) {
571 saveIfNeeded();
572 }
Mindy Pereiraeaea9f12012-01-10 15:05:27 -0800573 }
574
575 @Override
576 protected final void onActivityResult(int request, int result, Intent data) {
Mindy Pereirab199d172012-08-13 11:04:03 -0700577 if (request == RESULT_PICK_ATTACHMENT && result == RESULT_OK) {
Mindy Pereiraeaea9f12012-01-10 15:05:27 -0800578 addAttachmentAndUpdateView(data);
Mindy Pereirab199d172012-08-13 11:04:03 -0700579 mAddingAttachment = false;
580 } else if (request == RESULT_CREATE_ACCOUNT) {
581 // We were waiting for the user to create an account
582 if (result != RESULT_OK) {
583 finish();
584 } else {
585 // Watch for accounts to show up!
586 // restart the loader to get the updated list of accounts
587 getLoaderManager().initLoader(LOADER_ACCOUNT_CURSOR, null, this);
588 showWaitFragment(null);
589 }
Mindy Pereiraeaea9f12012-01-10 15:05:27 -0800590 }
591 }
592
593 @Override
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700594 public final void onRestoreInstanceState(Bundle savedInstanceState) {
595 super.onRestoreInstanceState(savedInstanceState);
596 if (savedInstanceState != null) {
597 if (savedInstanceState.containsKey(EXTRA_FOCUS_SELECTION_START)) {
598 int selectionStart = savedInstanceState.getInt(EXTRA_FOCUS_SELECTION_START);
599 int selectionEnd = savedInstanceState.getInt(EXTRA_FOCUS_SELECTION_END);
600 // There should be a focus and it should be an EditText since we
601 // only save these extras if these conditions are true.
602 EditText focusEditText = (EditText) getCurrentFocus();
603 final int length = focusEditText.getText().length();
604 if (selectionStart < length && selectionEnd < length) {
605 focusEditText.setSelection(selectionStart, selectionEnd);
606 }
607 }
608 }
609 }
610
611 @Override
Mindy Pereiraeaea9f12012-01-10 15:05:27 -0800612 public final void onSaveInstanceState(Bundle state) {
613 super.onSaveInstanceState(state);
Mindy Pereirab199d172012-08-13 11:04:03 -0700614 // We have no accounts so there is nothing to compose, and therefore, nothing to save.
615 if (mAccounts == null || mAccounts.length == 0) {
616 return;
617 }
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700618 // The framework is happy to save and restore the selection but only if it also saves and
619 // restores the contents of the edit text. That's a lot of text to put in a bundle so we do
620 // this manually.
621 View focus = getCurrentFocus();
622 if (focus != null && focus instanceof EditText) {
623 EditText focusEditText = (EditText) focus;
624 state.putInt(EXTRA_FOCUS_SELECTION_START, focusEditText.getSelectionStart());
625 state.putInt(EXTRA_FOCUS_SELECTION_END, focusEditText.getSelectionEnd());
626 }
Paul Westbrook6273e962012-04-23 10:44:15 -0700627
628 final List<ReplyFromAccount> replyFromAccounts = mFromSpinner.getReplyFromAccounts();
Paul Westbrook151f1ad2012-04-24 09:13:00 -0700629 final int selectedPos = mFromSpinner.getSelectedItemPosition();
Mindy Pereirad90f7ac2012-06-27 10:31:06 -0700630 final ReplyFromAccount selectedReplyFromAccount = (replyFromAccounts != null
631 && replyFromAccounts.size() > 0 && replyFromAccounts.size() > selectedPos) ?
632 replyFromAccounts.get(selectedPos) : null;
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700633 if (selectedReplyFromAccount != null) {
634 state.putString(EXTRA_SELECTED_REPLY_FROM_ACCOUNT, selectedReplyFromAccount.serialize()
635 .toString());
636 state.putParcelable(Utils.EXTRA_ACCOUNT, selectedReplyFromAccount.account);
637 } else {
638 state.putParcelable(Utils.EXTRA_ACCOUNT, mAccount);
639 }
Mindy Pereiraeaea9f12012-01-10 15:05:27 -0800640
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700641 if (mDraftId == UIProvider.INVALID_MESSAGE_ID && mRequestId !=0) {
642 // We don't have a draft id, and we have a request id,
643 // save the request id.
644 state.putInt(EXTRA_REQUEST_ID, mRequestId);
645 }
646
647 // We want to restore the current mode after a pause
648 // or rotation.
649 int mode = getMode();
650 state.putInt(EXTRA_ACTION, mode);
651
652 Message message = createMessage(selectedReplyFromAccount, mode);
653 state.putParcelable(EXTRA_MESSAGE, message);
654
655 if (mRefMessage != null) {
656 state.putParcelable(EXTRA_IN_REFERENCE_TO_MESSAGE, mRefMessage);
657 }
Mindy Pereira326689d2012-05-17 10:14:14 -0700658 state.putBoolean(EXTRA_SHOW_CC, mCcBccView.isCcVisible());
659 state.putBoolean(EXTRA_SHOW_BCC, mCcBccView.isBccVisible());
Mark Wei62066e42012-09-13 12:07:02 -0700660
661 state.putParcelableArrayList(
662 EXTRA_ATTACHMENT_PREVIEWS, mAttachmentsView.getAttachmentPreviews());
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700663 }
664
665 private int getMode() {
666 int mode = ComposeActivity.COMPOSE;
667 ActionBar actionBar = getActionBar();
Mindy Pereirae011b1d2012-06-18 13:45:26 -0700668 if (actionBar != null
669 && actionBar.getNavigationMode() == ActionBar.NAVIGATION_MODE_LIST) {
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700670 mode = actionBar.getSelectedNavigationIndex();
671 }
672 return mode;
673 }
674
675 private Message createMessage(ReplyFromAccount selectedReplyFromAccount, int mode) {
676 Message message = new Message();
677 message.id = UIProvider.INVALID_MESSAGE_ID;
Andy Huangd47877e2012-08-09 19:31:24 -0700678 message.serverId = null;
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700679 message.uri = null;
680 message.conversationUri = null;
681 message.subject = mSubject.getText().toString();
682 message.snippet = null;
Mindy Pereira1eedc752012-07-16 16:34:41 -0700683 message.from = selectedReplyFromAccount != null ? selectedReplyFromAccount.address
Mindy Pereirab67aa8f2012-07-11 15:09:06 -0700684 : mAccount != null ? mAccount.name : null;
Mindy Pereira3c911582012-08-09 16:59:09 -0700685 message.to = formatSenders(mTo.getText().toString());
686 message.cc = formatSenders(mCc.getText().toString());
687 message.bcc = formatSenders(mBcc.getText().toString());
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700688 message.replyTo = null;
689 message.dateReceivedMs = 0;
690 String htmlBody = Html.toHtml(mBodyView.getText());
691 StringBuilder fullBody = new StringBuilder(htmlBody);
692 message.bodyHtml = fullBody.toString();
693 message.bodyText = mBodyView.getText().toString();
694 message.embedsExternalResources = false;
695 message.refMessageId = mRefMessage != null ? mRefMessage.uri.toString() : null;
Mindy Pereirad2bef8b2012-05-30 12:14:52 -0700696 message.draftType = getDraftType(mode);
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700697 message.appendRefMessageContent = mQuotedTextView.getQuotedTextIfIncluded() != null;
698 ArrayList<Attachment> attachments = mAttachmentsView.getAttachments();
699 message.hasAttachments = attachments != null && attachments.size() > 0;
700 message.attachmentListUri = null;
701 message.messageFlags = 0;
702 message.saveUri = null;
703 message.sendUri = null;
704 message.alwaysShowImages = false;
705 message.attachmentsJson = Attachment.toJSONArray(attachments);
706 CharSequence quotedText = mQuotedTextView.getQuotedText();
707 message.quotedTextOffset = !TextUtils.isEmpty(quotedText) ? QuotedTextView
708 .getQuotedTextOffset(quotedText.toString()) : -1;
709 message.accountUri = null;
710 return message;
Mindy Pereiraeaea9f12012-01-10 15:05:27 -0800711 }
712
Mindy Pereira3c911582012-08-09 16:59:09 -0700713 private String formatSenders(String string) {
714 if (!TextUtils.isEmpty(string) && string.charAt(string.length() - 1) == ',') {
715 return string.substring(0, string.length() - 1);
716 }
717 return string;
718 }
719
Mindy Pereira818143e2012-01-11 13:59:49 -0800720 @VisibleForTesting
721 void setAccount(Account account) {
Mindy Pereirabb5217e2012-04-17 11:08:29 -0700722 if (account == null) {
723 return;
724 }
Mindy Pereira23e9fde2012-03-20 15:08:24 -0700725 if (!account.equals(mAccount)) {
726 mAccount = account;
Paul Westbrookb1f573c2012-04-06 11:38:28 -0700727 mCachedSettings = mAccount.settings;
728 appendSignature();
Mindy Pereira23e9fde2012-03-20 15:08:24 -0700729 }
Mindy Pereirafa20c1a2012-07-23 13:00:02 -0700730 if (mAccount != null) {
731 MailActivity.setForegroundNdef(MailActivity.getMailtoNdef(mAccount.name));
732 }
Mindy Pereira818143e2012-01-11 13:59:49 -0800733 }
734
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700735 private void initFromSpinner(Bundle bundle, int action) {
Mindy Pereira9a42bb42012-04-18 15:21:33 -0700736 String accountString = null;
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700737 if (action == EDIT_DRAFT && mDraft.draftType == UIProvider.DraftType.COMPOSE) {
Mindy Pereira62de1b12012-04-06 12:17:56 -0700738 action = COMPOSE;
739 }
Mindy Pereirab199d172012-08-13 11:04:03 -0700740 mFromSpinner.asyncInitFromSpinner(action, mAccount, mAccounts);
Mindy Pereira9a42bb42012-04-18 15:21:33 -0700741 if (bundle != null) {
742 if (bundle.containsKey(EXTRA_SELECTED_REPLY_FROM_ACCOUNT)) {
743 mReplyFromAccount = ReplyFromAccount.deserialize(mAccount,
744 bundle.getString(EXTRA_SELECTED_REPLY_FROM_ACCOUNT));
745 } else if (bundle.containsKey(EXTRA_FROM_ACCOUNT_STRING)) {
746 accountString = bundle.getString(EXTRA_FROM_ACCOUNT_STRING);
747 mReplyFromAccount = mFromSpinner.getMatchingReplyFromAccount(accountString);
748 }
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700749 }
750 if (mReplyFromAccount == null) {
751 if (mDraft != null) {
752 mReplyFromAccount = getReplyFromAccountFromDraft(mAccount, mDraft);
753 } else if (mRefMessage != null) {
754 mReplyFromAccount = getReplyFromAccountForReply(mAccount, mRefMessage);
755 }
Mindy Pereira62de1b12012-04-06 12:17:56 -0700756 }
757 if (mReplyFromAccount == null) {
758 mReplyFromAccount = new ReplyFromAccount(mAccount, mAccount.uri, mAccount.name,
Mindy Pereiracd970dd2012-05-31 10:07:47 -0700759 mAccount.name, mAccount.name, true, false);
Mindy Pereira62de1b12012-04-06 12:17:56 -0700760 }
Mindy Pereira9a42bb42012-04-18 15:21:33 -0700761
Mindy Pereira62de1b12012-04-06 12:17:56 -0700762 mFromSpinner.setCurrentAccount(mReplyFromAccount);
Mindy Pereira9a42bb42012-04-18 15:21:33 -0700763
Mindy Pereira62de1b12012-04-06 12:17:56 -0700764 if (mFromSpinner.getCount() > 1) {
Mindy Pereiraa83e7082012-03-30 08:53:11 -0700765 // If there is only 1 account, just show that account.
766 // Otherwise, give the user the ability to choose which account to
Mindy Pereira62de1b12012-04-06 12:17:56 -0700767 // send mail from / save drafts to.
768 mFromStatic.setVisibility(View.GONE);
Mindy Pereiraa83e7082012-03-30 08:53:11 -0700769 mFromStaticText.setText(mAccount.name);
Mindy Pereira62de1b12012-04-06 12:17:56 -0700770 mFromSpinnerWrapper.setVisibility(View.VISIBLE);
Mindy Pereiraa83e7082012-03-30 08:53:11 -0700771 } else {
772 mFromStatic.setVisibility(View.VISIBLE);
773 mFromStaticText.setText(mAccount.name);
774 mFromSpinnerWrapper.setVisibility(View.GONE);
Mindy Pereiraa83e7082012-03-30 08:53:11 -0700775 }
Mindy Pereira46ce0b12012-01-05 10:32:15 -0800776 }
777
Mindy Pereira62de1b12012-04-06 12:17:56 -0700778 private ReplyFromAccount getReplyFromAccountForReply(Account account, Message refMessage) {
779 if (refMessage.accountUri != null) {
780 // This must be from combined inbox.
781 List<ReplyFromAccount> replyFromAccounts = mFromSpinner.getReplyFromAccounts();
782 for (ReplyFromAccount from : replyFromAccounts) {
783 if (from.account.uri.equals(refMessage.accountUri)) {
784 return from;
785 }
786 }
787 return null;
788 } else {
789 return getReplyFromAccount(account, refMessage);
790 }
791 }
792
793 /**
794 * Given an account and which email address the message was sent to,
795 * return who the message should be sent from.
796 * @param account Account in which the message arrived.
797 * @param sentTo Email address to which the message was sent.
798 * @return the address from which to reply.
799 */
800 public ReplyFromAccount getReplyFromAccount(Account account, Message refMessage) {
801 // First see if we are supposed to use the default address or
802 // the address it was sentTo.
Mindy Pereira326689d2012-05-17 10:14:14 -0700803 if (mCachedSettings.forceReplyFromDefault) {
Mindy Pereira62de1b12012-04-06 12:17:56 -0700804 return getDefaultReplyFromAccount(account);
805 } else {
Mindy Pereira89bae572012-06-18 11:34:36 -0700806 // If we aren't explicitly told which account to look for, look at
Mindy Pereira62de1b12012-04-06 12:17:56 -0700807 // all the message recipients and find one that matches
808 // a custom from or account.
809 List<String> allRecipients = new ArrayList<String>();
mindyp5ed63112012-09-17 17:31:45 -0700810 allRecipients.addAll(Arrays.asList(refMessage.getToAddresses()));
811 allRecipients.addAll(Arrays.asList(refMessage.getCcAddresses()));
Mindy Pereira62de1b12012-04-06 12:17:56 -0700812 return getMatchingRecipient(account, allRecipients);
813 }
814 }
815
816 /**
817 * Compare all the recipients of an email to the current account and all
818 * custom addresses associated with that account. Return the match if there
819 * is one, or the default account if there isn't.
820 */
821 protected ReplyFromAccount getMatchingRecipient(Account account, List<String> sentTo) {
822 // Tokenize the list and place in a hashmap.
823 ReplyFromAccount matchingReplyFrom = null;
824 Rfc822Token[] tokens;
825 HashSet<String> recipientsMap = new HashSet<String>();
826 for (String address : sentTo) {
827 tokens = Rfc822Tokenizer.tokenize(address);
828 for (int i = 0; i < tokens.length; i++) {
829 recipientsMap.add(tokens[i].getAddress());
830 }
831 }
832
833 int matchingAddressCount = 0;
834 List<ReplyFromAccount> customFroms;
Andy Huang16174812012-08-16 16:40:35 -0700835 customFroms = account.getReplyFroms();
836 if (customFroms != null) {
837 for (ReplyFromAccount entry : customFroms) {
838 if (recipientsMap.contains(entry.address)) {
839 matchingReplyFrom = entry;
840 matchingAddressCount++;
Mindy Pereira62de1b12012-04-06 12:17:56 -0700841 }
842 }
Mindy Pereira62de1b12012-04-06 12:17:56 -0700843 }
844 if (matchingAddressCount > 1) {
845 matchingReplyFrom = getDefaultReplyFromAccount(account);
846 }
847 return matchingReplyFrom;
848 }
849
850 private ReplyFromAccount getDefaultReplyFromAccount(Account account) {
851 List<ReplyFromAccount> replyFromAccounts = mFromSpinner.getReplyFromAccounts();
852 for (ReplyFromAccount from : replyFromAccounts) {
853 if (from.isDefault) {
854 return from;
855 }
856 }
Mindy Pereiracd970dd2012-05-31 10:07:47 -0700857 return new ReplyFromAccount(account, account.uri, account.name, account.name, account.name,
858 true, false);
Mindy Pereira62de1b12012-04-06 12:17:56 -0700859 }
860
Mindy Pereirae8f94dc2012-04-16 11:56:21 -0700861 private ReplyFromAccount getReplyFromAccountFromDraft(Account account, Message msg) {
862 String sender = msg.from;
Mindy Pereira62de1b12012-04-06 12:17:56 -0700863 ReplyFromAccount replyFromAccount = null;
864 List<ReplyFromAccount> replyFromAccounts = mFromSpinner.getReplyFromAccounts();
865 if (TextUtils.equals(account.name, sender)) {
866 replyFromAccount = new ReplyFromAccount(mAccount, mAccount.uri, mAccount.name,
Mindy Pereiracd970dd2012-05-31 10:07:47 -0700867 mAccount.name, mAccount.name, true, false);
Mindy Pereira62de1b12012-04-06 12:17:56 -0700868 } else {
869 for (ReplyFromAccount fromAccount : replyFromAccounts) {
870 if (TextUtils.equals(fromAccount.name, sender)) {
871 replyFromAccount = fromAccount;
872 break;
873 }
874 }
875 }
876 return replyFromAccount;
877 }
878
Mindy Pereira46ce0b12012-01-05 10:32:15 -0800879 private void findViews() {
Mindy Pereirab199d172012-08-13 11:04:03 -0700880 findViewById(R.id.compose).setVisibility(View.VISIBLE);
Mindy Pereiraa26b54e2012-01-06 12:54:33 -0800881 mCcBccButton = (Button) findViewById(R.id.add_cc_bcc);
Mindy Pereira8e9305e2011-12-13 14:25:04 -0800882 if (mCcBccButton != null) {
883 mCcBccButton.setOnClickListener(this);
884 }
885 mCcBccView = (CcBccView) findViewById(R.id.cc_bcc_wrapper);
Mindy Pereira7b56a612011-12-14 12:32:28 -0800886 mAttachmentsView = (AttachmentsView)findViewById(R.id.attachments);
mindyp93b079b2012-08-29 16:32:15 -0700887 mPhotoAttachmentsButton = findViewById(R.id.add_photo_attachment);
mindypcd0b0b92012-08-23 14:33:17 -0700888 if (mPhotoAttachmentsButton != null) {
889 mPhotoAttachmentsButton.setOnClickListener(this);
890 }
mindyp93b079b2012-08-29 16:32:15 -0700891 mVideoAttachmentsButton = findViewById(R.id.add_video_attachment);
mindypcd0b0b92012-08-23 14:33:17 -0700892 if (mVideoAttachmentsButton != null) {
893 mVideoAttachmentsButton.setOnClickListener(this);
894 }
Mindy Pereira818143e2012-01-11 13:59:49 -0800895 mTo = (RecipientEditTextView) findViewById(R.id.to);
896 mCc = (RecipientEditTextView) findViewById(R.id.cc);
897 mBcc = (RecipientEditTextView) findViewById(R.id.bcc);
Mindy Pereira82cc5662012-01-09 17:29:30 -0800898 // TODO: add special chips text change watchers before adding
899 // this as a text changed watcher to the to, cc, bcc fields.
Mindy Pereira6349a042012-01-04 11:25:01 -0800900 mSubject = (TextView) findViewById(R.id.subject);
mindyp62d3ec72012-08-24 13:04:09 -0700901 mSubject.setOnEditorActionListener(this);
Mindy Pereira46ce0b12012-01-05 10:32:15 -0800902 mQuotedTextView = (QuotedTextView) findViewById(R.id.quoted_text_view);
903 mQuotedTextView.setRespondInlineListener(this);
Mindy Pereira433b1982012-04-03 11:53:07 -0700904 mBodyView = (EditText) findViewById(R.id.body);
Mindy Pereira1a95a572012-01-05 12:21:29 -0800905 mFromStatic = findViewById(R.id.static_from_content);
Mindy Pereira2eb17322012-03-07 10:07:34 -0800906 mFromStaticText = (TextView) findViewById(R.id.from_account_name);
Mindy Pereiraeaea9f12012-01-10 15:05:27 -0800907 mFromSpinnerWrapper = findViewById(R.id.spinner_from_content);
Mindy Pereira5a85e2b2012-01-11 09:53:32 -0800908 mFromSpinner = (FromAddressSpinner) findViewById(R.id.from_picker);
Mindy Pereira6349a042012-01-04 11:25:01 -0800909 }
910
mindyp62d3ec72012-08-24 13:04:09 -0700911 @Override
912 public boolean onEditorAction(TextView view, int action, KeyEvent keyEvent) {
913 if (action == EditorInfo.IME_ACTION_DONE) {
914 focusBody();
915 return true;
916 }
917 return false;
918 }
919
Mindy Pereirae011b1d2012-06-18 13:45:26 -0700920 protected TextView getBody() {
921 return mBodyView;
922 }
923
924 @VisibleForTesting
925 public Account getFromAccount() {
926 return mReplyFromAccount != null && mReplyFromAccount.account != null ?
927 mReplyFromAccount.account : mAccount;
928 }
929
Mindy Pereiracbfb75a2012-06-25 14:52:23 -0700930 private void clearChangeListeners() {
931 mSubject.removeTextChangedListener(this);
932 mBodyView.removeTextChangedListener(this);
933 mTo.removeTextChangedListener(mToListener);
934 mCc.removeTextChangedListener(mCcListener);
935 mBcc.removeTextChangedListener(mBccListener);
936 mFromSpinner.setOnAccountChangedListener(null);
937 mAttachmentsView.setAttachmentChangesListener(null);
938 }
939
Mindy Pereira75f66632012-01-11 11:42:02 -0800940 // Now that the message has been initialized from any existing draft or
941 // ref message data, set up listeners for any changes that occur to the
942 // message.
943 private void initChangeListeners() {
944 mSubject.addTextChangedListener(this);
945 mBodyView.addTextChangedListener(this);
Mindy Pereiracbfb75a2012-06-25 14:52:23 -0700946 if (mToListener == null) {
947 mToListener = new RecipientTextWatcher(mTo, this);
948 }
949 mTo.addTextChangedListener(mToListener);
950 if (mCcListener == null) {
951 mCcListener = new RecipientTextWatcher(mCc, this);
952 }
953 mCc.addTextChangedListener(mCcListener);
954 if (mBccListener == null) {
955 mBccListener = new RecipientTextWatcher(mBcc, this);
956 }
957 mBcc.addTextChangedListener(mBccListener);
Mindy Pereira75f66632012-01-11 11:42:02 -0800958 mFromSpinner.setOnAccountChangedListener(this);
Mindy Pereira818143e2012-01-11 13:59:49 -0800959 mAttachmentsView.setAttachmentChangesListener(this);
Mindy Pereira75f66632012-01-11 11:42:02 -0800960 }
961
Mindy Pereira326c6602012-01-04 15:32:42 -0800962 private void initActionBar(int action) {
963 mComposeMode = action;
Mindy Pereiraeaea9f12012-01-10 15:05:27 -0800964 ActionBar actionBar = getActionBar();
Mindy Pereirae011b1d2012-06-18 13:45:26 -0700965 if (actionBar == null) {
966 return;
967 }
Mindy Pereira326c6602012-01-04 15:32:42 -0800968 if (action == ComposeActivity.COMPOSE) {
Mindy Pereiraeaea9f12012-01-10 15:05:27 -0800969 actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
970 actionBar.setTitle(R.string.compose);
Mindy Pereira326c6602012-01-04 15:32:42 -0800971 } else {
Mindy Pereiraeaea9f12012-01-10 15:05:27 -0800972 actionBar.setTitle(null);
Mindy Pereira326c6602012-01-04 15:32:42 -0800973 if (mComposeModeAdapter == null) {
974 mComposeModeAdapter = new ComposeModeAdapter(this);
975 }
Mindy Pereiraeaea9f12012-01-10 15:05:27 -0800976 actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
977 actionBar.setListNavigationCallbacks(mComposeModeAdapter, this);
Mindy Pereira326c6602012-01-04 15:32:42 -0800978 switch (action) {
979 case ComposeActivity.REPLY:
Mindy Pereiraeaea9f12012-01-10 15:05:27 -0800980 actionBar.setSelectedNavigationItem(0);
Mindy Pereira326c6602012-01-04 15:32:42 -0800981 break;
982 case ComposeActivity.REPLY_ALL:
Mindy Pereiraeaea9f12012-01-10 15:05:27 -0800983 actionBar.setSelectedNavigationItem(1);
Mindy Pereira326c6602012-01-04 15:32:42 -0800984 break;
985 case ComposeActivity.FORWARD:
Mindy Pereiraeaea9f12012-01-10 15:05:27 -0800986 actionBar.setSelectedNavigationItem(2);
Mindy Pereira326c6602012-01-04 15:32:42 -0800987 break;
988 }
989 }
Mindy Pereirafbe40192012-03-20 10:40:45 -0700990 actionBar.setDisplayOptions(ActionBar.DISPLAY_HOME_AS_UP | ActionBar.DISPLAY_SHOW_HOME,
991 ActionBar.DISPLAY_HOME_AS_UP | ActionBar.DISPLAY_SHOW_HOME);
992 actionBar.setHomeButtonEnabled(true);
Mindy Pereira326c6602012-01-04 15:32:42 -0800993 }
994
Mindy Pereira46ce0b12012-01-05 10:32:15 -0800995 private void initFromRefMessage(int action, String recipientAddress) {
Mindy Pereira96a7f7a2012-07-09 16:51:06 -0700996 setFieldsFromRefMessage(action, recipientAddress);
997 if (mRefMessage != null) {
998 // CC field only gets populated when doing REPLY_ALL.
999 // BCC never gets auto-populated, unless the user is editing
1000 // a draft with one.
Mindy Pereira29a717e2012-07-25 18:05:48 -07001001 if (!TextUtils.isEmpty(mCc.getText()) && action == REPLY_ALL) {
Mindy Pereira96a7f7a2012-07-09 16:51:06 -07001002 mCcBccView.show(false, true, false);
1003 }
1004 }
1005 updateHideOrShowCcBcc();
1006 }
1007
1008 private void setFieldsFromRefMessage(int action, String recipientAddress) {
Mindy Pereira8eca57a2012-03-20 16:42:34 -07001009 setSubject(mRefMessage, action);
1010 // Setup recipients
1011 if (action == FORWARD) {
1012 mForward = true;
Mindy Pereira6349a042012-01-04 11:25:01 -08001013 }
Mindy Pereira8eca57a2012-03-20 16:42:34 -07001014 initRecipientsFromRefMessage(recipientAddress, mRefMessage, action);
Mindy Pereirae8f94dc2012-04-16 11:56:21 -07001015 initQuotedTextFromRefMessage(mRefMessage, action);
Mindy Pereira8eca57a2012-03-20 16:42:34 -07001016 if (action == ComposeActivity.FORWARD || mAttachmentsChanged) {
1017 initAttachments(mRefMessage);
1018 }
Mindy Pereirac17d0732011-12-29 10:46:19 -08001019 }
1020
Mindy Pereirae8f94dc2012-04-16 11:56:21 -07001021 private void initFromDraftMessage(Message message) {
Paul Westbrookbb87b7f2012-03-20 16:20:07 -07001022 LogUtils.d(LOG_TAG, "Intializing draft from previous draft message");
1023
1024 mDraft = message;
1025 mDraftId = message.id;
1026 mSubject.setText(message.subject);
1027 mForward = message.draftType == UIProvider.DraftType.FORWARD;
1028 final List<String> toAddresses = Arrays.asList(message.getToAddresses());
1029 addToAddresses(toAddresses);
1030 addCcAddresses(Arrays.asList(message.getCcAddresses()), toAddresses);
1031 addBccAddresses(Arrays.asList(message.getBccAddresses()));
Mindy Pereira2421dc82012-03-27 13:32:31 -07001032 if (message.hasAttachments) {
1033 List<Attachment> attachments = message.getAttachments();
1034 for (Attachment a : attachments) {
Andy Huang5c5fd572012-04-08 18:19:29 -07001035 addAttachmentAndUpdateView(a);
Mindy Pereira2421dc82012-03-27 13:32:31 -07001036 }
1037 }
Mindy Pereiracc8e7db2012-05-30 12:57:42 -07001038 int quotedTextIndex = message.appendRefMessageContent ?
Mindy Pereira002ff522012-05-30 10:31:26 -07001039 message.quotedTextOffset : -1;
Paul Westbrookbb87b7f2012-03-20 16:20:07 -07001040 // Set the body
Mindy Pereira002ff522012-05-30 10:31:26 -07001041 CharSequence quotedText = null;
Paul Westbrookbb87b7f2012-03-20 16:20:07 -07001042 if (!TextUtils.isEmpty(message.bodyHtml)) {
Mindy Pereira752222d2012-07-19 09:58:53 -07001043 CharSequence htmlText = "";
Mindy Pereira002ff522012-05-30 10:31:26 -07001044 if (quotedTextIndex > -1) {
Mindy Pereira752222d2012-07-19 09:58:53 -07001045 // Find the offset in the htmltext of the actual quoted text and strip it out.
1046 quotedTextIndex = QuotedTextView.findQuotedTextIndex(message.bodyHtml);
1047 if (quotedTextIndex > -1) {
1048 htmlText = Html.fromHtml(message.bodyHtml.substring(0, quotedTextIndex));
1049 quotedText = message.bodyHtml.subSequence(quotedTextIndex,
1050 message.bodyHtml.length());
1051 }
Mindy Pereira1a6e9382012-08-14 15:51:22 -07001052 } else {
1053 htmlText = Html.fromHtml(message.bodyHtml);
Mindy Pereira002ff522012-05-30 10:31:26 -07001054 }
1055 mBodyView.setText(htmlText);
Paul Westbrookbb87b7f2012-03-20 16:20:07 -07001056 } else {
Mindy Pereira752222d2012-07-19 09:58:53 -07001057 final String body = message.bodyText;
1058 final CharSequence bodyText = !TextUtils.isEmpty(body) ?
1059 (quotedTextIndex > -1 ?
1060 message.bodyText.substring(0, quotedTextIndex) : message.bodyText)
1061 : "";
Mindy Pereira002ff522012-05-30 10:31:26 -07001062 if (quotedTextIndex > -1) {
Mindy Pereira752222d2012-07-19 09:58:53 -07001063 quotedText = !TextUtils.isEmpty(body) ? message.bodyText.substring(quotedTextIndex)
1064 : null;
Mindy Pereira002ff522012-05-30 10:31:26 -07001065 }
1066 mBodyView.setText(bodyText);
1067 }
1068 if (quotedTextIndex > -1 && quotedText != null) {
Mindy Pereira39713232012-05-30 11:48:41 -07001069 mQuotedTextView.setQuotedTextFromDraft(quotedText, mForward);
Paul Westbrookbb87b7f2012-03-20 16:20:07 -07001070 }
Paul Westbrookbb87b7f2012-03-20 16:20:07 -07001071 }
1072
Mindy Pereira8eca57a2012-03-20 16:42:34 -07001073 /**
1074 * Fill all the widgets with the content found in the Intent Extra, if any.
1075 * Also apply the same style to all widgets. Note: if initFromExtras is
1076 * called as a result of switching between reply, reply all, and forward per
1077 * the latest revision of Gmail, and the user has already made changes to
1078 * attachments on a previous incarnation of the message (as a reply, reply
1079 * all, or forward), the original attachments from the message will not be
1080 * re-instantiated. The user's changes will be respected. This follows the
1081 * web gmail interaction.
1082 */
1083 public void initFromExtras(Intent intent) {
Mindy Pereira8eca57a2012-03-20 16:42:34 -07001084 // If we were invoked with a SENDTO intent, the value
1085 // should take precedence
1086 final Uri dataUri = intent.getData();
1087 if (dataUri != null) {
1088 if (MAIL_TO.equals(dataUri.getScheme())) {
1089 initFromMailTo(dataUri.toString());
1090 } else {
Mindy Pereira0b4f28e2012-03-28 14:12:21 -07001091 if (!mAccount.composeIntentUri.equals(dataUri)) {
Mindy Pereira8eca57a2012-03-20 16:42:34 -07001092 String toText = dataUri.getSchemeSpecificPart();
1093 if (toText != null) {
1094 mTo.setText("");
Mindy Pereiradbe89962012-04-13 09:42:38 -07001095 addToAddresses(Arrays.asList(TextUtils.split(toText, ",")));
Mindy Pereira8eca57a2012-03-20 16:42:34 -07001096 }
1097 }
1098 }
1099 }
1100
1101 String[] extraStrings = intent.getStringArrayExtra(Intent.EXTRA_EMAIL);
1102 if (extraStrings != null) {
1103 addToAddresses(Arrays.asList(extraStrings));
1104 }
1105 extraStrings = intent.getStringArrayExtra(Intent.EXTRA_CC);
1106 if (extraStrings != null) {
1107 addCcAddresses(Arrays.asList(extraStrings), null);
1108 }
1109 extraStrings = intent.getStringArrayExtra(Intent.EXTRA_BCC);
1110 if (extraStrings != null) {
1111 addBccAddresses(Arrays.asList(extraStrings));
1112 }
1113
1114 String extraString = intent.getStringExtra(Intent.EXTRA_SUBJECT);
1115 if (extraString != null) {
1116 mSubject.setText(extraString);
1117 }
1118
1119 for (String extra : ALL_EXTRAS) {
1120 if (intent.hasExtra(extra)) {
1121 String value = intent.getStringExtra(extra);
1122 if (EXTRA_TO.equals(extra)) {
Mindy Pereiradbe89962012-04-13 09:42:38 -07001123 addToAddresses(Arrays.asList(TextUtils.split(value, ",")));
Mindy Pereira8eca57a2012-03-20 16:42:34 -07001124 } else if (EXTRA_CC.equals(extra)) {
Mindy Pereiradbe89962012-04-13 09:42:38 -07001125 addCcAddresses(Arrays.asList(TextUtils.split(value, ",")), null);
Mindy Pereira8eca57a2012-03-20 16:42:34 -07001126 } else if (EXTRA_BCC.equals(extra)) {
Mindy Pereiradbe89962012-04-13 09:42:38 -07001127 addBccAddresses(Arrays.asList(TextUtils.split(value, ",")));
Mindy Pereira8eca57a2012-03-20 16:42:34 -07001128 } else if (EXTRA_SUBJECT.equals(extra)) {
1129 mSubject.setText(value);
1130 } else if (EXTRA_BODY.equals(extra)) {
1131 setBody(value, true /* with signature */);
1132 }
1133 }
1134 }
1135
1136 Bundle extras = intent.getExtras();
1137 if (extras != null) {
Mindy Pereira8eca57a2012-03-20 16:42:34 -07001138 CharSequence text = extras.getCharSequence(Intent.EXTRA_TEXT);
1139 if (text != null) {
1140 setBody(text, true /* with signature */);
1141 }
Mindy Pereira8eca57a2012-03-20 16:42:34 -07001142 }
Mindy Pereirae8f94dc2012-04-16 11:56:21 -07001143 }
1144
Mindy Pereira8eca57a2012-03-20 16:42:34 -07001145 @VisibleForTesting
1146 protected String decodeEmailInUri(String s) throws UnsupportedEncodingException {
Mindy Pereiraa4069f22012-05-30 15:31:45 -07001147 // TODO: handle the case where there are spaces in the display name as
1148 // well as the email such as "Guy with spaces <guy+with+spaces@gmail.com>"
1149 // as they could be encoded ambiguously.
Mindy Pereira8eca57a2012-03-20 16:42:34 -07001150 // Since URLDecode.decode changes + into ' ', and + is a valid
1151 // email character, we need to find/ replace these ourselves before
1152 // decoding.
1153 String replacePlus = s.replace("+", "%2B");
Mindy Pereiraa4069f22012-05-30 15:31:45 -07001154 try {
1155 return URLDecoder.decode(replacePlus, UTF8_ENCODING_NAME);
1156 } catch (IllegalArgumentException e) {
1157 if (LogUtils.isLoggable(LOG_TAG, LogUtils.VERBOSE)) {
1158 LogUtils.e(LOG_TAG, "%s while decoding '%s'", e.getMessage(), s);
1159 } else {
1160 LogUtils.e(LOG_TAG, e, "Exception while decoding mailto address");
1161 }
1162 return null;
1163 }
Mindy Pereira8eca57a2012-03-20 16:42:34 -07001164 }
1165
1166 /**
1167 * Initialize the compose view from a String representing a mailTo uri.
1168 * @param mailToString The uri as a string.
1169 */
1170 public void initFromMailTo(String mailToString) {
1171 // We need to disguise this string as a URI in order to parse it
1172 // TODO: Remove this hack when http://b/issue?id=1445295 gets fixed
1173 Uri uri = Uri.parse("foo://" + mailToString);
1174 int index = mailToString.indexOf("?");
1175 int length = "mailto".length() + 1;
1176 String to;
1177 try {
1178 // Extract the recipient after mailto:
1179 if (index == -1) {
1180 to = decodeEmailInUri(mailToString.substring(length));
1181 } else {
1182 to = decodeEmailInUri(mailToString.substring(length, index));
1183 }
Mindy Pereiraa4069f22012-05-30 15:31:45 -07001184 if (!TextUtils.isEmpty(to)) {
1185 addToAddresses(Arrays.asList(TextUtils.split(to, ",")));
1186 }
Mindy Pereira8eca57a2012-03-20 16:42:34 -07001187 } catch (UnsupportedEncodingException e) {
1188 if (LogUtils.isLoggable(LOG_TAG, LogUtils.VERBOSE)) {
1189 LogUtils.e(LOG_TAG, "%s while decoding '%s'", e.getMessage(), mailToString);
1190 } else {
1191 LogUtils.e(LOG_TAG, e, "Exception while decoding mailto address");
1192 }
1193 }
1194
1195 List<String> cc = uri.getQueryParameters("cc");
1196 addCcAddresses(Arrays.asList(cc.toArray(new String[cc.size()])), null);
1197
1198 List<String> otherTo = uri.getQueryParameters("to");
1199 addToAddresses(Arrays.asList(otherTo.toArray(new String[otherTo.size()])));
1200
1201 List<String> bcc = uri.getQueryParameters("bcc");
1202 addBccAddresses(Arrays.asList(bcc.toArray(new String[bcc.size()])));
1203
1204 List<String> subject = uri.getQueryParameters("subject");
1205 if (subject.size() > 0) {
1206 try {
1207 mSubject.setText(URLDecoder.decode(subject.get(0), UTF8_ENCODING_NAME));
1208 } catch (UnsupportedEncodingException e) {
1209 LogUtils.e(LOG_TAG, "%s while decoding subject '%s'",
1210 e.getMessage(), subject);
1211 }
1212 }
1213
1214 List<String> body = uri.getQueryParameters("body");
1215 if (body.size() > 0) {
1216 try {
1217 setBody(URLDecoder.decode(body.get(0), UTF8_ENCODING_NAME),
1218 true /* with signature */);
1219 } catch (UnsupportedEncodingException e) {
1220 LogUtils.e(LOG_TAG, "%s while decoding body '%s'", e.getMessage(), body);
1221 }
1222 }
Mindy Pereira8eca57a2012-03-20 16:42:34 -07001223 }
1224
Mindy Pereirabddd6f32012-06-20 12:10:03 -07001225 @VisibleForTesting
1226 protected void initAttachments(Message refMessage) {
Mark Wei434f2942012-08-24 11:54:02 -07001227 addAttachments(refMessage.getAttachments());
1228 }
1229
1230 public long addAttachments(List<Attachment> attachments) {
1231 long size = 0;
1232 AttachmentFailureException error = null;
1233 for (Attachment a : attachments) {
1234 try {
1235 size += mAttachmentsView.addAttachment(mAccount, a);
1236 } catch (AttachmentFailureException e) {
1237 error = e;
1238 }
Mindy Pereira3cd4f402012-07-17 11:16:18 -07001239 }
Mark Wei434f2942012-08-24 11:54:02 -07001240 if (error != null) {
1241 LogUtils.e(LOG_TAG, error, "Error adding attachment");
1242 if (attachments.size() > 1) {
1243 showAttachmentTooBigToast(R.string.too_large_to_attach_multiple);
1244 } else {
1245 showAttachmentTooBigToast(error.getErrorRes());
1246 }
1247 }
1248 return size;
Mindy Pereira3cd4f402012-07-17 11:16:18 -07001249 }
1250
1251 /**
1252 * When an attachment is too large to be added to a message, show a toast.
1253 * This method also updates the position of the toast so that it is shown
1254 * clearly above they keyboard if it happens to be open.
1255 */
Mark Wei434f2942012-08-24 11:54:02 -07001256 private void showAttachmentTooBigToast(int errorRes) {
1257 String maxSize = AttachmentUtils.convertToHumanReadableSize(
1258 getApplicationContext(), mAccount.settings.getMaxAttachmentSize());
1259 showErrorToast(getString(errorRes, maxSize));
Mindy Pereira3cd4f402012-07-17 11:16:18 -07001260 }
1261
Mark Wei434f2942012-08-24 11:54:02 -07001262 private void showErrorToast(String message) {
1263 Toast t = Toast.makeText(this, message, Toast.LENGTH_LONG);
1264 t.setText(message);
Mindy Pereira3cd4f402012-07-17 11:16:18 -07001265 t.setGravity(Gravity.CENTER_HORIZONTAL, 0,
1266 getResources().getDimensionPixelSize(R.dimen.attachment_toast_yoffset));
1267 t.show();
Mindy Pereiraeaea9f12012-01-10 15:05:27 -08001268 }
1269
Paul Westbrookf97588b2012-03-20 11:11:37 -07001270 private void initAttachmentsFromIntent(Intent intent) {
Paul Westbrook03ee9712012-04-02 09:51:51 -07001271 Bundle extras = intent.getExtras();
1272 if (extras == null) {
1273 extras = Bundle.EMPTY;
1274 }
Paul Westbrookf97588b2012-03-20 11:11:37 -07001275 final String action = intent.getAction();
1276 if (!mAttachmentsChanged) {
1277 long totalSize = 0;
1278 if (extras.containsKey(EXTRA_ATTACHMENTS)) {
1279 String[] uris = (String[]) extras.getSerializable(EXTRA_ATTACHMENTS);
1280 for (String uriString : uris) {
1281 final Uri uri = Uri.parse(uriString);
1282 long size = 0;
1283 try {
Andy Huang5c5fd572012-04-08 18:19:29 -07001284 size = mAttachmentsView.addAttachment(mAccount, uri);
Paul Westbrookf97588b2012-03-20 11:11:37 -07001285 } catch (AttachmentFailureException e) {
Paul Westbrookf97588b2012-03-20 11:11:37 -07001286 LogUtils.e(LOG_TAG, e, "Error adding attachment");
Mark Wei434f2942012-08-24 11:54:02 -07001287 showAttachmentTooBigToast(e.getErrorRes());
Paul Westbrookf97588b2012-03-20 11:11:37 -07001288 }
1289 totalSize += size;
1290 }
1291 }
1292 if (Intent.ACTION_SEND.equals(action) && extras.containsKey(Intent.EXTRA_STREAM)) {
1293 final Uri uri = (Uri) extras.getParcelable(Intent.EXTRA_STREAM);
1294 long size = 0;
1295 try {
Andy Huang5c5fd572012-04-08 18:19:29 -07001296 size = mAttachmentsView.addAttachment(mAccount, uri);
Paul Westbrookf97588b2012-03-20 11:11:37 -07001297 } catch (AttachmentFailureException e) {
Paul Westbrookf97588b2012-03-20 11:11:37 -07001298 LogUtils.e(LOG_TAG, e, "Error adding attachment");
Mark Wei434f2942012-08-24 11:54:02 -07001299 showAttachmentTooBigToast(e.getErrorRes());
Paul Westbrookf97588b2012-03-20 11:11:37 -07001300 }
1301 totalSize += size;
1302 }
1303
1304 if (Intent.ACTION_SEND_MULTIPLE.equals(action)
1305 && extras.containsKey(Intent.EXTRA_STREAM)) {
1306 ArrayList<Parcelable> uris = extras.getParcelableArrayList(Intent.EXTRA_STREAM);
Mark Wei434f2942012-08-24 11:54:02 -07001307 ArrayList<Attachment> attachments = new ArrayList<Attachment>();
Paul Westbrookf97588b2012-03-20 11:11:37 -07001308 for (Parcelable uri : uris) {
Paul Westbrookf97588b2012-03-20 11:11:37 -07001309 try {
Mark Wei434f2942012-08-24 11:54:02 -07001310 attachments.add(mAttachmentsView.generateLocalAttachment((Uri) uri));
Paul Westbrookf97588b2012-03-20 11:11:37 -07001311 } catch (AttachmentFailureException e) {
Paul Westbrookf97588b2012-03-20 11:11:37 -07001312 LogUtils.e(LOG_TAG, e, "Error adding attachment");
Mark Wei434f2942012-08-24 11:54:02 -07001313 String maxSize = AttachmentUtils.convertToHumanReadableSize(
1314 getApplicationContext(),
1315 mAccount.settings.getMaxAttachmentSize());
1316 showErrorToast(getString(R.string.generic_attachment_problem, maxSize));
Paul Westbrookf97588b2012-03-20 11:11:37 -07001317 }
Paul Westbrookf97588b2012-03-20 11:11:37 -07001318 }
Mark Wei434f2942012-08-24 11:54:02 -07001319 totalSize += addAttachments(attachments);
Paul Westbrookf97588b2012-03-20 11:11:37 -07001320 }
1321
1322 if (totalSize > 0) {
1323 mAttachmentsChanged = true;
1324 updateSaveUi();
1325 }
1326 }
1327 }
1328
1329
Mindy Pereirae8f94dc2012-04-16 11:56:21 -07001330 private void initQuotedTextFromRefMessage(Message refMessage, int action) {
1331 if (mRefMessage != null && (action == REPLY || action == REPLY_ALL || action == FORWARD)) {
Mindy Pereira9932dee2012-01-10 16:09:50 -08001332 mQuotedTextView.setQuotedText(action, refMessage, action != FORWARD);
1333 }
Mindy Pereira46ce0b12012-01-05 10:32:15 -08001334 }
1335
1336 private void updateHideOrShowCcBcc() {
Mindy Pereiraec8b0ed2012-01-06 10:35:59 -08001337 // Its possible there is a menu item OR a button.
Mindy Pereira326689d2012-05-17 10:14:14 -07001338 boolean ccVisible = mCcBccView.isCcVisible();
1339 boolean bccVisible = mCcBccView.isBccVisible();
Mindy Pereiraec8b0ed2012-01-06 10:35:59 -08001340 if (mCcBccButton != null) {
Mindy Pereira326689d2012-05-17 10:14:14 -07001341 if (!ccVisible || !bccVisible) {
Mindy Pereiraec8b0ed2012-01-06 10:35:59 -08001342 mCcBccButton.setVisibility(View.VISIBLE);
Mindy Pereira326689d2012-05-17 10:14:14 -07001343 mCcBccButton.setText(getString(!ccVisible ? R.string.add_cc_label
Mindy Pereiraec8b0ed2012-01-06 10:35:59 -08001344 : R.string.add_bcc_label));
1345 } else {
mindypcd0b0b92012-08-23 14:33:17 -07001346 mCcBccButton.setVisibility(View.INVISIBLE);
Mindy Pereiraec8b0ed2012-01-06 10:35:59 -08001347 }
1348 }
Mindy Pereira46ce0b12012-01-05 10:32:15 -08001349 }
1350
Mindy Pereiraa34c9a02012-04-17 14:10:53 -07001351 private void showCcBcc(Bundle state) {
Mindy Pereira326689d2012-05-17 10:14:14 -07001352 if (state != null && state.containsKey(EXTRA_SHOW_CC)) {
1353 boolean showCc = state.getBoolean(EXTRA_SHOW_CC);
1354 boolean showBcc = state.getBoolean(EXTRA_SHOW_BCC);
1355 if (showCc || showBcc) {
1356 mCcBccView.show(false, showCc, showBcc);
Mindy Pereira6faeedf2012-04-18 16:11:39 -07001357 }
Mindy Pereiraa34c9a02012-04-17 14:10:53 -07001358 }
1359 }
1360
Mindy Pereira013194c2012-01-06 15:09:33 -08001361 /**
1362 * Add attachment and update the compose area appropriately.
1363 * @param data
1364 */
1365 public void addAttachmentAndUpdateView(Intent data) {
Mindy Pereira2421dc82012-03-27 13:32:31 -07001366 addAttachmentAndUpdateView(data != null ? data.getData() : (Uri) null);
1367 }
1368
Andy Huang5c5fd572012-04-08 18:19:29 -07001369 public void addAttachmentAndUpdateView(Uri contentUri) {
1370 if (contentUri == null) {
Mindy Pereira2421dc82012-03-27 13:32:31 -07001371 return;
1372 }
Mindy Pereira013194c2012-01-06 15:09:33 -08001373 try {
Andy Huang5c5fd572012-04-08 18:19:29 -07001374 addAttachmentAndUpdateView(mAttachmentsView.generateLocalAttachment(contentUri));
1375 } catch (AttachmentFailureException e) {
Andy Huang5c5fd572012-04-08 18:19:29 -07001376 LogUtils.e(LOG_TAG, e, "Error adding attachment");
Mark Wei434f2942012-08-24 11:54:02 -07001377 showErrorToast(getResources().getString(
1378 e.getErrorRes(),
1379 AttachmentUtils.convertToHumanReadableSize(
1380 getApplicationContext(), mAccount.settings.getMaxAttachmentSize())));
Andy Huang5c5fd572012-04-08 18:19:29 -07001381 }
1382 }
1383
1384 public void addAttachmentAndUpdateView(Attachment attachment) {
1385 try {
Mark Wei434f2942012-08-24 11:54:02 -07001386 long size = mAttachmentsView.addAttachment(mAccount, attachment);
Mindy Pereira9932dee2012-01-10 16:09:50 -08001387 if (size > 0) {
1388 mAttachmentsChanged = true;
1389 updateSaveUi();
Mindy Pereira013194c2012-01-06 15:09:33 -08001390 }
Mindy Pereira9932dee2012-01-10 16:09:50 -08001391 } catch (AttachmentFailureException e) {
Mindy Pereira9932dee2012-01-10 16:09:50 -08001392 LogUtils.e(LOG_TAG, e, "Error adding attachment");
Mark Wei434f2942012-08-24 11:54:02 -07001393 showAttachmentTooBigToast(e.getErrorRes());
Mindy Pereira013194c2012-01-06 15:09:33 -08001394 }
1395 }
1396
Mindy Pereira3ce64e72012-01-13 14:29:45 -08001397 void initRecipientsFromRefMessage(String recipientAddress, Message refMessage,
Mindy Pereira46ce0b12012-01-05 10:32:15 -08001398 int action) {
Mindy Pereira4a27ea92012-01-05 15:55:25 -08001399 // Don't populate the address if this is a forward.
1400 if (action == ComposeActivity.FORWARD) {
1401 return;
1402 }
Mindy Pereira33fe9082012-01-09 16:24:30 -08001403 initReplyRecipients(mAccount.name, refMessage, action);
Mindy Pereira4a27ea92012-01-05 15:55:25 -08001404 }
Mindy Pereira46ce0b12012-01-05 10:32:15 -08001405
Mindy Pereira818143e2012-01-11 13:59:49 -08001406 @VisibleForTesting
Mindy Pereira3ce64e72012-01-13 14:29:45 -08001407 void initReplyRecipients(String account, Message refMessage, int action) {
Mindy Pereira4a27ea92012-01-05 15:55:25 -08001408 // This is the email address of the current user, i.e. the one composing
1409 // the reply.
Mindy Pereira4a20b702012-01-05 16:24:24 -08001410 final String accountEmail = Address.getEmailAddress(account).getAddress();
Mindy Pereira1469b4e2012-06-19 19:18:54 -07001411 String fromAddress = getAddress(refMessage.from);
mindyp5ed63112012-09-17 17:31:45 -07001412 String[] sentToAddresses = refMessage.getToAddresses();
Mindy Pereira3ce64e72012-01-13 14:29:45 -08001413 String replytoAddress = refMessage.replyTo;
Mindy Pereiraa26b54e2012-01-06 12:54:33 -08001414 final Collection<String> toAddresses;
Mindy Pereira4a27ea92012-01-05 15:55:25 -08001415
1416 // If this is a reply, the Cc list is empty. If this is a reply-all, the
1417 // Cc list is the union of the To and Cc recipients of the original
1418 // message, excluding the current user's email address and any addresses
Mindy Pereiraa26b54e2012-01-06 12:54:33 -08001419 // already on the To list.
1420 if (action == ComposeActivity.REPLY) {
Mindy Pereira3ce64e72012-01-13 14:29:45 -08001421 toAddresses = initToRecipients(account, accountEmail, fromAddress, replytoAddress,
Mindy Pereira1469b4e2012-06-19 19:18:54 -07001422 sentToAddresses);
Mindy Pereiraa26b54e2012-01-06 12:54:33 -08001423 addToAddresses(toAddresses);
1424 } else if (action == ComposeActivity.REPLY_ALL) {
Mindy Pereira4a27ea92012-01-05 15:55:25 -08001425 final Set<String> ccAddresses = Sets.newHashSet();
Mindy Pereira3ce64e72012-01-13 14:29:45 -08001426 toAddresses = initToRecipients(account, accountEmail, fromAddress, replytoAddress,
Mindy Pereira9b18a9a2012-07-25 18:13:57 -07001427 sentToAddresses);
Mindy Pereira154386a2012-01-11 13:02:33 -08001428 addToAddresses(toAddresses);
Mindy Pereira4a27ea92012-01-05 15:55:25 -08001429 addRecipients(accountEmail, ccAddresses, sentToAddresses);
mindyp5ed63112012-09-17 17:31:45 -07001430 addRecipients(accountEmail, ccAddresses, refMessage.getCcAddresses());
Mindy Pereira4a27ea92012-01-05 15:55:25 -08001431 addCcAddresses(ccAddresses, toAddresses);
1432 }
1433 }
1434
Mindy Pereira1469b4e2012-06-19 19:18:54 -07001435 private String getAddress(String toParse) {
1436 if (!TextUtils.isEmpty(toParse)) {
1437 Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(toParse);
1438 if (tokens.length > 0) {
1439 return tokens[0].getAddress();
1440 }
1441 }
1442 return "";
1443 }
1444
Mindy Pereira4a27ea92012-01-05 15:55:25 -08001445 private void addToAddresses(Collection<String> addresses) {
1446 addAddressesToList(addresses, mTo);
1447 }
1448
1449 private void addCcAddresses(Collection<String> addresses, Collection<String> toAddresses) {
Mindy Pereira8eca57a2012-03-20 16:42:34 -07001450 addCcAddressesToList(tokenizeAddressList(addresses),
1451 toAddresses != null ? tokenizeAddressList(toAddresses) : null, mCc);
Mindy Pereira4a27ea92012-01-05 15:55:25 -08001452 }
1453
Paul Westbrookbb87b7f2012-03-20 16:20:07 -07001454 private void addBccAddresses(Collection<String> addresses) {
1455 addAddressesToList(addresses, mBcc);
1456 }
1457
Mindy Pereira4a27ea92012-01-05 15:55:25 -08001458 @VisibleForTesting
1459 protected void addCcAddressesToList(List<Rfc822Token[]> addresses,
1460 List<Rfc822Token[]> compareToList, RecipientEditTextView list) {
1461 String address;
1462
Mindy Pereira8eca57a2012-03-20 16:42:34 -07001463 if (compareToList == null) {
1464 for (Rfc822Token[] tokens : addresses) {
1465 for (int i = 0; i < tokens.length; i++) {
1466 address = tokens[i].toString();
Mindy Pereira4a27ea92012-01-05 15:55:25 -08001467 list.append(address + END_TOKEN);
1468 }
1469 }
Mindy Pereira8eca57a2012-03-20 16:42:34 -07001470 } else {
1471 HashSet<String> compareTo = convertToHashSet(compareToList);
1472 for (Rfc822Token[] tokens : addresses) {
1473 for (int i = 0; i < tokens.length; i++) {
1474 address = tokens[i].toString();
1475 // Check if this is a duplicate:
1476 if (!compareTo.contains(tokens[i].getAddress())) {
1477 // Get the address here
1478 list.append(address + END_TOKEN);
1479 }
1480 }
1481 }
Mindy Pereira4a27ea92012-01-05 15:55:25 -08001482 }
1483 }
1484
Mindy Pereira4a27ea92012-01-05 15:55:25 -08001485 private HashSet<String> convertToHashSet(List<Rfc822Token[]> list) {
1486 HashSet<String> hash = new HashSet<String>();
1487 for (Rfc822Token[] tokens : list) {
1488 for (int i = 0; i < tokens.length; i++) {
1489 hash.add(tokens[i].getAddress());
1490 }
1491 }
1492 return hash;
1493 }
1494
Mindy Pereira4a27ea92012-01-05 15:55:25 -08001495 protected List<Rfc822Token[]> tokenizeAddressList(Collection<String> addresses) {
1496 @VisibleForTesting
1497 List<Rfc822Token[]> tokenized = new ArrayList<Rfc822Token[]>();
1498
1499 for (String address: addresses) {
1500 tokenized.add(Rfc822Tokenizer.tokenize(address));
1501 }
1502 return tokenized;
1503 }
1504
1505 @VisibleForTesting
1506 void addAddressesToList(Collection<String> addresses, RecipientEditTextView list) {
1507 for (String address : addresses) {
1508 addAddressToList(address, list);
1509 }
1510 }
1511
1512 private void addAddressToList(String address, RecipientEditTextView list) {
1513 if (address == null || list == null)
1514 return;
1515
1516 Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(address);
1517
1518 for (int i = 0; i < tokens.length; i++) {
1519 list.append(tokens[i] + END_TOKEN);
1520 }
1521 }
1522
1523 @VisibleForTesting
1524 protected Collection<String> initToRecipients(String account, String accountEmail,
Mindy Pereira3ce64e72012-01-13 14:29:45 -08001525 String senderAddress, String replyToAddress, String[] inToAddresses) {
Mindy Pereira4a27ea92012-01-05 15:55:25 -08001526 // The To recipient is the reply-to address specified in the original
1527 // message, unless it is:
1528 // the current user OR a custom from of the current user, in which case
1529 // it's the To recipient list of the original message.
1530 // OR missing, in which case use the sender of the original message
1531 Set<String> toAddresses = Sets.newHashSet();
Mindy Pereira3ce64e72012-01-13 14:29:45 -08001532 if (!TextUtils.isEmpty(replyToAddress)) {
1533 toAddresses.add(replyToAddress);
Mindy Pereira4a27ea92012-01-05 15:55:25 -08001534 } else {
Mindy Pereiracecc54a2012-07-31 09:38:11 -07001535 if (!recipientMatchesThisAccount(account, senderAddress)) {
Mindy Pereira1469b4e2012-06-19 19:18:54 -07001536 toAddresses.add(senderAddress);
1537 } else {
1538 // This happens if the user replies to a message they originally
Mindy Pereira1883b342012-06-20 08:34:56 -07001539 // wrote. In this case, "reply" really means "re-send," so we
1540 // target the original recipients. This works as expected even
1541 // if the user sent the original message to themselves.
Mindy Pereira1469b4e2012-06-19 19:18:54 -07001542 toAddresses.addAll(Arrays.asList(inToAddresses));
1543 }
Mindy Pereira4a27ea92012-01-05 15:55:25 -08001544 }
1545 return toAddresses;
1546 }
1547
Mindy Pereiracecc54a2012-07-31 09:38:11 -07001548 private void addRecipients(String accountAddress, Set<String> recipients, String[] addresses) {
Mindy Pereira4a27ea92012-01-05 15:55:25 -08001549 for (String email : addresses) {
Mindy Pereiracecc54a2012-07-31 09:38:11 -07001550 // Do not add this account, or any of its custom from addresses, to
1551 // the list of recipients.
Mindy Pereira4a20b702012-01-05 16:24:24 -08001552 final String recipientAddress = Address.getEmailAddress(email).getAddress();
Mindy Pereiracecc54a2012-07-31 09:38:11 -07001553 if (!recipientMatchesThisAccount(accountAddress, recipientAddress)) {
Mindy Pereira4a27ea92012-01-05 15:55:25 -08001554 recipients.add(email.replace("\"\"", ""));
1555 }
1556 }
Mindy Pereira46ce0b12012-01-05 10:32:15 -08001557 }
1558
Mindy Pereiracecc54a2012-07-31 09:38:11 -07001559 /**
1560 * A recipient matches this account if it has the same address as the
1561 * currently selected account OR one of the custom from addresses associated
1562 * with the currently selected account.
1563 * @param accountAddress currently selected account
1564 * @param recipientAddress address we are comparing with the currently selected account
1565 * @return
1566 */
1567 protected boolean recipientMatchesThisAccount(String accountAddress, String recipientAddress) {
1568 return accountAddress.equalsIgnoreCase(recipientAddress)
1569 || ReplyFromAccount.isCustomFrom(recipientAddress,
1570 mFromSpinner.getReplyFromAccounts());
1571 }
1572
Mindy Pereira3ce64e72012-01-13 14:29:45 -08001573 private void setSubject(Message refMessage, int action) {
1574 String subject = refMessage.subject;
Mindy Pereira46ce0b12012-01-05 10:32:15 -08001575 String prefix;
1576 String correctedSubject = null;
1577 if (action == ComposeActivity.COMPOSE) {
1578 prefix = "";
1579 } else if (action == ComposeActivity.FORWARD) {
1580 prefix = getString(R.string.forward_subject_label);
1581 } else {
1582 prefix = getString(R.string.reply_subject_label);
1583 }
1584
1585 // Don't duplicate the prefix
Mindy Pereirac7a36992012-07-30 14:00:37 -07001586 if (!TextUtils.isEmpty(subject)
1587 && subject.toLowerCase().startsWith(prefix.toLowerCase())) {
Mindy Pereira46ce0b12012-01-05 10:32:15 -08001588 correctedSubject = subject;
1589 } else {
1590 correctedSubject = String
1591 .format(getString(R.string.formatted_subject), prefix, subject);
1592 }
1593 mSubject.setText(correctedSubject);
1594 }
1595
Mindy Pereira818143e2012-01-11 13:59:49 -08001596 private void initRecipients() {
1597 setupRecipients(mTo);
1598 setupRecipients(mCc);
1599 setupRecipients(mBcc);
Mindy Pereiraeaea9f12012-01-10 15:05:27 -08001600 }
1601
Mindy Pereira818143e2012-01-11 13:59:49 -08001602 private void setupRecipients(RecipientEditTextView view) {
Paul Westbrook679a8cc2012-02-21 16:37:58 -08001603 view.setAdapter(new RecipientAdapter(this, mAccount));
Mindy Pereirac17d0732011-12-29 10:46:19 -08001604 view.setTokenizer(new Rfc822Tokenizer());
Mindy Pereira82cc5662012-01-09 17:29:30 -08001605 if (mValidator == null) {
Paul Westbrook679a8cc2012-02-21 16:37:58 -08001606 final String accountName = mAccount.name;
Mindy Pereira33fe9082012-01-09 16:24:30 -08001607 int offset = accountName.indexOf("@") + 1;
1608 String account = accountName;
Mindy Pereirac17d0732011-12-29 10:46:19 -08001609 if (offset > -1) {
Mindy Pereira33fe9082012-01-09 16:24:30 -08001610 account = account.substring(accountName.indexOf("@") + 1);
Mindy Pereirac17d0732011-12-29 10:46:19 -08001611 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08001612 mValidator = new Rfc822Validator(account);
Mindy Pereirac17d0732011-12-29 10:46:19 -08001613 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08001614 view.setValidator(mValidator);
Mindy Pereira8e9305e2011-12-13 14:25:04 -08001615 }
1616
1617 @Override
1618 public void onClick(View v) {
1619 int id = v.getId();
1620 switch (id) {
Mindy Pereiraa26b54e2012-01-06 12:54:33 -08001621 case R.id.add_cc_bcc:
Mindy Pereira8e9305e2011-12-13 14:25:04 -08001622 // Verify that cc/ bcc aren't showing.
1623 // Animate in cc/bcc.
Mindy Pereiraa26b54e2012-01-06 12:54:33 -08001624 showCcBccViews();
Mindy Pereira8e9305e2011-12-13 14:25:04 -08001625 break;
mindypcd0b0b92012-08-23 14:33:17 -07001626 case R.id.add_photo_attachment:
1627 doAttach(MIME_TYPE_PHOTO);
1628 break;
1629 case R.id.add_video_attachment:
1630 doAttach(MIME_TYPE_VIDEO);
1631 break;
Mindy Pereira8e9305e2011-12-13 14:25:04 -08001632 }
1633 }
Mindy Pereirab47f3e22011-12-13 14:25:04 -08001634
1635 @Override
1636 public boolean onCreateOptionsMenu(Menu menu) {
1637 super.onCreateOptionsMenu(menu);
Mindy Pereirab199d172012-08-13 11:04:03 -07001638 // Don't render any menu items when there are no accounts.
1639 if (mAccounts == null || mAccounts.length == 0) {
1640 return true;
1641 }
Mindy Pereirab47f3e22011-12-13 14:25:04 -08001642 MenuInflater inflater = getMenuInflater();
1643 inflater.inflate(R.menu.compose_menu, menu);
Mindy Pereira82cc5662012-01-09 17:29:30 -08001644 mSave = menu.findItem(R.id.save);
1645 mSend = menu.findItem(R.id.send);
Mindy Pereira3ca5bad2012-04-16 11:02:42 -07001646 MenuItem helpItem = menu.findItem(R.id.help_info_menu_item);
1647 MenuItem sendFeedbackItem = menu.findItem(R.id.feedback_menu_item);
1648 if (helpItem != null) {
1649 helpItem.setVisible(mAccount != null
1650 && mAccount.supportsCapability(AccountCapabilities.HELP_CONTENT));
1651 }
1652 if (sendFeedbackItem != null) {
1653 sendFeedbackItem.setVisible(mAccount != null
1654 && mAccount.supportsCapability(AccountCapabilities.SEND_FEEDBACK));
1655 }
Mindy Pereirab47f3e22011-12-13 14:25:04 -08001656 return true;
1657 }
1658
1659 @Override
Mindy Pereiraec8b0ed2012-01-06 10:35:59 -08001660 public boolean onPrepareOptionsMenu(Menu menu) {
1661 MenuItem ccBcc = menu.findItem(R.id.add_cc_bcc);
Mindy Pereira818143e2012-01-11 13:59:49 -08001662 if (ccBcc != null && mCc != null) {
Mindy Pereiraec8b0ed2012-01-06 10:35:59 -08001663 // Its possible there is a menu item OR a button.
1664 boolean ccFieldVisible = mCc.isShown();
1665 boolean bccFieldVisible = mBcc.isShown();
1666 if (!ccFieldVisible || !bccFieldVisible) {
1667 ccBcc.setVisible(true);
1668 ccBcc.setTitle(getString(!ccFieldVisible ? R.string.add_cc_label
1669 : R.string.add_bcc_label));
1670 } else {
1671 ccBcc.setVisible(false);
1672 }
1673 }
Mindy Pereira75f66632012-01-11 11:42:02 -08001674 if (mSave != null) {
1675 mSave.setEnabled(shouldSave());
1676 }
Mindy Pereiraec8b0ed2012-01-06 10:35:59 -08001677 return true;
1678 }
1679
1680 @Override
Mindy Pereirab47f3e22011-12-13 14:25:04 -08001681 public boolean onOptionsItemSelected(MenuItem item) {
1682 int id = item.getItemId();
Mindy Pereira75f66632012-01-11 11:42:02 -08001683 boolean handled = true;
Mindy Pereirab47f3e22011-12-13 14:25:04 -08001684 switch (id) {
Mindy Pereira2db7d4a2012-08-15 11:00:02 -07001685 case R.id.add_photo_attachment:
1686 doAttach(MIME_TYPE_PHOTO);
1687 break;
1688 case R.id.add_video_attachment:
1689 doAttach(MIME_TYPE_VIDEO);
Mindy Pereira7b56a612011-12-14 12:32:28 -08001690 break;
Mindy Pereiraec8b0ed2012-01-06 10:35:59 -08001691 case R.id.add_cc_bcc:
1692 showCcBccViews();
Mindy Pereirab47f3e22011-12-13 14:25:04 -08001693 break;
Mindy Pereira33fe9082012-01-09 16:24:30 -08001694 case R.id.save:
Mindy Pereira48e31b02012-05-30 13:12:24 -07001695 doSave(true);
Mindy Pereira33fe9082012-01-09 16:24:30 -08001696 break;
1697 case R.id.send:
1698 doSend();
Mindy Pereira75f66632012-01-11 11:42:02 -08001699 break;
Mindy Pereiraefe3d252012-03-01 14:20:44 -08001700 case R.id.discard:
1701 doDiscard();
1702 break;
Mindy Pereira1f936682012-03-02 11:30:33 -08001703 case R.id.settings:
1704 Utils.showSettings(this, mAccount);
1705 break;
Mindy Pereirafbe40192012-03-20 10:40:45 -07001706 case android.R.id.home:
Paul Westbrookdaecb4b2012-05-31 10:21:26 -07001707 onAppUpPressed();
Mindy Pereirafbe40192012-03-20 10:40:45 -07001708 break;
1709 case R.id.help_info_menu_item:
Paul Westbrook30745b62012-08-19 14:10:32 -07001710 Utils.showHelp(this, mAccount, getString(R.string.compose_help_context));
Mindy Pereirafbe40192012-03-20 10:40:45 -07001711 break;
1712 case R.id.feedback_menu_item:
Paul Westbrook17beb0b2012-08-20 13:34:37 -07001713 Utils.sendFeedback(this, mAccount, false);
Mindy Pereirafbe40192012-03-20 10:40:45 -07001714 break;
Mindy Pereira75f66632012-01-11 11:42:02 -08001715 default:
1716 handled = false;
Mindy Pereira33fe9082012-01-09 16:24:30 -08001717 break;
Mindy Pereirab47f3e22011-12-13 14:25:04 -08001718 }
1719 return !handled ? super.onOptionsItemSelected(item) : handled;
1720 }
Mindy Pereira326c6602012-01-04 15:32:42 -08001721
Mindy Pereirab199d172012-08-13 11:04:03 -07001722 @Override
1723 public void onBackPressed() {
1724 // If we are showing the wait fragment, just exit.
1725 if (getWaitFragment() != null) {
1726 finish();
1727 } else {
1728 super.onBackPressed();
1729 }
1730 }
1731
Paul Westbrookdaecb4b2012-05-31 10:21:26 -07001732 private void onAppUpPressed() {
1733 if (mLaunchedFromEmail) {
1734 // If this was started from Gmail, simply treat app up as the system back button, so
1735 // that the last view is restored.
1736 onBackPressed();
1737 return;
1738 }
1739
1740 // Fire the main activity to ensure it launches the "top" screen of mail.
1741 // Since the main Activity is singleTask, it should revive that task if it was already
1742 // started.
Mindy Pereira5ad02912012-07-09 09:57:18 -07001743 Folder defaultInbox = new Folder();
1744 defaultInbox.uri = mAccount.settings.defaultInbox;
Paul Westbrookdaecb4b2012-05-31 10:21:26 -07001745 final Intent mailIntent =
Mindy Pereira96a7f7a2012-07-09 16:51:06 -07001746 Utils.createViewFolderIntent(defaultInbox, mAccount);
Paul Westbrookdaecb4b2012-05-31 10:21:26 -07001747
1748 mailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK |
1749 Intent.FLAG_ACTIVITY_TASK_ON_HOME);
1750 startActivity(mailIntent);
1751 finish();
1752 }
1753
Mindy Pereira33fe9082012-01-09 16:24:30 -08001754 private void doSend() {
Mindy Pereira8aa913b2012-07-17 10:58:39 -07001755 clearImeText();
Mindy Pereira82cc5662012-01-09 17:29:30 -08001756 sendOrSaveWithSanityChecks(false, true, false);
Mindy Pereira33fe9082012-01-09 16:24:30 -08001757 }
1758
Mindy Pereira48e31b02012-05-30 13:12:24 -07001759 private void doSave(boolean showToast) {
1760 // Clear the IME composing suggestions from the body and subject before saving.
Mindy Pereira8aa913b2012-07-17 10:58:39 -07001761 clearImeText();
Mindy Pereiraeaea9f12012-01-10 15:05:27 -08001762 sendOrSaveWithSanityChecks(true, showToast, false);
Mindy Pereira48e31b02012-05-30 13:12:24 -07001763 }
1764
Mindy Pereira8aa913b2012-07-17 10:58:39 -07001765 private void clearImeText() {
1766 mBodyView.clearComposingText();
1767 BaseInputConnection.removeComposingSpans(mBodyView.getEditableText());
1768 mSubject.clearComposingText();
1769 BaseInputConnection.removeComposingSpans(mSubject.getEditableText());
Mindy Pereira33fe9082012-01-09 16:24:30 -08001770 }
1771
Mindy Pereirae011b1d2012-06-18 13:45:26 -07001772 @VisibleForTesting
1773 public interface SendOrSaveCallback {
Mindy Pereira82cc5662012-01-09 17:29:30 -08001774 public void initializeSendOrSave(SendOrSaveTask sendOrSaveTask);
Mindy Pereira7ed1c112012-01-18 10:59:25 -08001775 public void notifyMessageIdAllocated(SendOrSaveMessage sendOrSaveMessage, Message message);
1776 public Message getMessage();
Mindy Pereira82cc5662012-01-09 17:29:30 -08001777 public void sendOrSaveFinished(SendOrSaveTask sendOrSaveTask, boolean success);
1778 }
1779
Mindy Pereirae011b1d2012-06-18 13:45:26 -07001780 @VisibleForTesting
1781 public static class SendOrSaveTask implements Runnable {
Mindy Pereira82cc5662012-01-09 17:29:30 -08001782 private final Context mContext;
Mindy Pereira82cc5662012-01-09 17:29:30 -08001783 @VisibleForTesting
Mindy Pereirae011b1d2012-06-18 13:45:26 -07001784 public final SendOrSaveCallback mSendOrSaveCallback;
1785 @VisibleForTesting
1786 public final SendOrSaveMessage mSendOrSaveMessage;
Mindy Pereira82cc5662012-01-09 17:29:30 -08001787
1788 public SendOrSaveTask(Context context, SendOrSaveMessage message,
1789 SendOrSaveCallback callback) {
1790 mContext = context;
1791 mSendOrSaveCallback = callback;
1792 mSendOrSaveMessage = message;
1793 }
1794
1795 @Override
1796 public void run() {
Mindy Pereira7ed1c112012-01-18 10:59:25 -08001797 final SendOrSaveMessage sendOrSaveMessage = mSendOrSaveMessage;
Mindy Pereira82cc5662012-01-09 17:29:30 -08001798
Mindy Pereira92551d02012-04-05 11:31:12 -07001799 final ReplyFromAccount selectedAccount = sendOrSaveMessage.mAccount;
Mindy Pereira7ed1c112012-01-18 10:59:25 -08001800 Message message = mSendOrSaveCallback.getMessage();
1801 long messageId = message != null ? message.id : UIProvider.INVALID_MESSAGE_ID;
Mindy Pereira82cc5662012-01-09 17:29:30 -08001802 // If a previous draft has been saved, in an account that is different
1803 // than what the user wants to send from, remove the old draft, and treat this
1804 // as a new message
Mindy Pereira7ed1c112012-01-18 10:59:25 -08001805 if (!selectedAccount.equals(sendOrSaveMessage.mAccount)) {
Mindy Pereira82cc5662012-01-09 17:29:30 -08001806 if (messageId != UIProvider.INVALID_MESSAGE_ID) {
1807 ContentResolver resolver = mContext.getContentResolver();
1808 ContentValues values = new ContentValues();
1809 values.put(BaseColumns._ID, messageId);
Mindy Pereira92551d02012-04-05 11:31:12 -07001810 if (selectedAccount.account.expungeMessageUri != null) {
1811 resolver.update(selectedAccount.account.expungeMessageUri, values, null,
Mindy Pereiraeaea9f12012-01-10 15:05:27 -08001812 null);
Mindy Pereiracfb7f332012-02-28 10:23:43 -08001813 } else {
1814 // TODO(mindyp) delete the conversation.
Mindy Pereiraeaea9f12012-01-10 15:05:27 -08001815 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08001816 // reset messageId to 0, so a new message will be created
1817 messageId = UIProvider.INVALID_MESSAGE_ID;
1818 }
1819 }
1820
1821 final long messageIdToSave = messageId;
Mindy Pereira82cc5662012-01-09 17:29:30 -08001822 if (messageIdToSave != UIProvider.INVALID_MESSAGE_ID) {
Mindy Pereira7ed1c112012-01-18 10:59:25 -08001823 sendOrSaveMessage.mValues.put(BaseColumns._ID, messageIdToSave);
Mindy Pereira82cc5662012-01-09 17:29:30 -08001824 mContext.getContentResolver().update(
Mindy Pereira7ed1c112012-01-18 10:59:25 -08001825 Uri.parse(sendOrSaveMessage.mSave ? message.saveUri : message.sendUri),
1826 sendOrSaveMessage.mValues, null, null);
Mindy Pereira82cc5662012-01-09 17:29:30 -08001827 } else {
Mindy Pereira7ed1c112012-01-18 10:59:25 -08001828 ContentResolver resolver = mContext.getContentResolver();
Mindy Pereira92551d02012-04-05 11:31:12 -07001829 Uri messageUri = resolver
1830 .insert(sendOrSaveMessage.mSave ? selectedAccount.account.saveDraftUri
1831 : selectedAccount.account.sendMessageUri,
1832 sendOrSaveMessage.mValues);
Mindy Pereira7ed1c112012-01-18 10:59:25 -08001833 if (sendOrSaveMessage.mSave && messageUri != null) {
1834 Cursor messageCursor = resolver.query(messageUri,
1835 UIProvider.MESSAGE_PROJECTION, null, null, null);
Paul Westbrookba558482012-03-19 11:00:24 -07001836 if (messageCursor != null) {
1837 try {
1838 if (messageCursor.moveToFirst()) {
1839 // Broadcast notification that a new message has
1840 // been allocated
1841 mSendOrSaveCallback.notifyMessageIdAllocated(sendOrSaveMessage,
1842 new Message(messageCursor));
1843 }
1844 } finally {
1845 messageCursor.close();
1846 }
Mindy Pereira7ed1c112012-01-18 10:59:25 -08001847 }
1848 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08001849 }
1850
Mindy Pereira7ed1c112012-01-18 10:59:25 -08001851 if (!sendOrSaveMessage.mSave) {
Mindy Pereira82cc5662012-01-09 17:29:30 -08001852 UIProvider.incrementRecipientsTimesContacted(mContext,
Mindy Pereira7ed1c112012-01-18 10:59:25 -08001853 (String) sendOrSaveMessage.mValues.get(UIProvider.MessageColumns.TO));
Mindy Pereira82cc5662012-01-09 17:29:30 -08001854 UIProvider.incrementRecipientsTimesContacted(mContext,
Mindy Pereira7ed1c112012-01-18 10:59:25 -08001855 (String) sendOrSaveMessage.mValues.get(UIProvider.MessageColumns.CC));
Mindy Pereira82cc5662012-01-09 17:29:30 -08001856 UIProvider.incrementRecipientsTimesContacted(mContext,
Mindy Pereira7ed1c112012-01-18 10:59:25 -08001857 (String) sendOrSaveMessage.mValues.get(UIProvider.MessageColumns.BCC));
Mindy Pereira82cc5662012-01-09 17:29:30 -08001858 }
1859 mSendOrSaveCallback.sendOrSaveFinished(SendOrSaveTask.this, true);
1860 }
1861 }
1862
1863 // Array of the outstanding send or save tasks. Access is synchronized
1864 // with the object itself
1865 /* package for testing */
Mindy Pereirae011b1d2012-06-18 13:45:26 -07001866 @VisibleForTesting
1867 public ArrayList<SendOrSaveTask> mActiveTasks = Lists.newArrayList();
Mindy Pereira82cc5662012-01-09 17:29:30 -08001868 private int mRequestId;
Mindy Pereirabdf7a402012-03-01 15:23:26 -08001869 private String mSignature;
Mindy Pereirab199d172012-08-13 11:04:03 -07001870 private Account[] mAccounts;
Mindy Pereira82cc5662012-01-09 17:29:30 -08001871
Mindy Pereirae011b1d2012-06-18 13:45:26 -07001872 @VisibleForTesting
1873 public static class SendOrSaveMessage {
Mindy Pereira92551d02012-04-05 11:31:12 -07001874 final ReplyFromAccount mAccount;
Mindy Pereira82cc5662012-01-09 17:29:30 -08001875 final ContentValues mValues;
Mindy Pereira3ce64e72012-01-13 14:29:45 -08001876 final String mRefMessageId;
Mindy Pereirae011b1d2012-06-18 13:45:26 -07001877 @VisibleForTesting
1878 public final boolean mSave;
Mindy Pereira82cc5662012-01-09 17:29:30 -08001879 final int mRequestId;
1880
Mindy Pereira92551d02012-04-05 11:31:12 -07001881 public SendOrSaveMessage(ReplyFromAccount account, ContentValues values,
Mindy Pereira3ce64e72012-01-13 14:29:45 -08001882 String refMessageId, boolean save) {
Mindy Pereira82cc5662012-01-09 17:29:30 -08001883 mAccount = account;
Mindy Pereira82cc5662012-01-09 17:29:30 -08001884 mValues = values;
1885 mRefMessageId = refMessageId;
1886 mSave = save;
1887 mRequestId = mValues.hashCode() ^ hashCode();
1888 }
1889
1890 int requestId() {
1891 return mRequestId;
1892 }
1893 }
1894
1895 /**
1896 * Get the to recipients.
1897 */
1898 public String[] getToAddresses() {
1899 return getAddressesFromList(mTo);
1900 }
1901
1902 /**
1903 * Get the cc recipients.
1904 */
1905 public String[] getCcAddresses() {
1906 return getAddressesFromList(mCc);
1907 }
1908
1909 /**
1910 * Get the bcc recipients.
1911 */
1912 public String[] getBccAddresses() {
1913 return getAddressesFromList(mBcc);
1914 }
1915
1916 public String[] getAddressesFromList(RecipientEditTextView list) {
1917 if (list == null) {
1918 return new String[0];
1919 }
1920 Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(list.getText());
1921 int count = tokens.length;
1922 String[] result = new String[count];
1923 for (int i = 0; i < count; i++) {
1924 result[i] = tokens[i].toString();
1925 }
1926 return result;
1927 }
1928
1929 /**
1930 * Check for invalid email addresses.
1931 * @param to String array of email addresses to check.
1932 * @param wrongEmailsOut Emails addresses that were invalid.
1933 */
1934 public void checkInvalidEmails(String[] to, List<String> wrongEmailsOut) {
Mindy Pereirae5f20bf2012-06-25 14:20:40 -07001935 if (mValidator == null) {
1936 return;
1937 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08001938 for (String email : to) {
1939 if (!mValidator.isValid(email)) {
1940 wrongEmailsOut.add(email);
1941 }
1942 }
1943 }
1944
1945 /**
1946 * Show an error because the user has entered an invalid recipient.
1947 * @param message
1948 */
1949 public void showRecipientErrorDialog(String message) {
1950 // Only 1 invalid recipients error dialog should be allowed up at a
1951 // time.
1952 if (mRecipientErrorDialog != null) {
1953 mRecipientErrorDialog.dismiss();
1954 }
1955 mRecipientErrorDialog = new AlertDialog.Builder(this).setMessage(message).setTitle(
1956 R.string.recipient_error_dialog_title)
1957 .setIconAttribute(android.R.attr.alertDialogIcon)
Mindy Pereira82cc5662012-01-09 17:29:30 -08001958 .setPositiveButton(
1959 R.string.ok, new Dialog.OnClickListener() {
Marc Blank0bbc8582012-04-23 15:07:57 -07001960 @Override
Mindy Pereira82cc5662012-01-09 17:29:30 -08001961 public void onClick(DialogInterface dialog, int which) {
1962 // after the user dismisses the recipient error
1963 // dialog we want to make sure to refocus the
1964 // recipient to field so they can fix the issue
1965 // easily
1966 if (mTo != null) {
1967 mTo.requestFocus();
1968 }
1969 mRecipientErrorDialog = null;
1970 }
1971 }).show();
1972 }
1973
1974 /**
1975 * Update the state of the UI based on whether or not the current draft
1976 * needs to be saved and the message is not empty.
1977 */
Mindy Pereiraeaea9f12012-01-10 15:05:27 -08001978 public void updateSaveUi() {
Mindy Pereira82cc5662012-01-09 17:29:30 -08001979 if (mSave != null) {
1980 mSave.setEnabled((shouldSave() && !isBlank()));
1981 }
1982 }
1983
1984 /**
1985 * Returns true if we need to save the current draft.
1986 */
1987 private boolean shouldSave() {
Mindy Pereira7ed1c112012-01-18 10:59:25 -08001988 synchronized (mDraftLock) {
Mindy Pereira82cc5662012-01-09 17:29:30 -08001989 // The message should only be saved if:
1990 // It hasn't been sent AND
1991 // Some text has been added to the message OR
1992 // an attachment has been added or removed
Mindy Pereiraa2148332012-07-02 13:54:14 -07001993 // AND there is actually something in the draft to save.
Andy Huangd47877e2012-08-09 19:31:24 -07001994 return (mTextChanged || mAttachmentsChanged || mReplyFromChanged)
Mindy Pereiraa2148332012-07-02 13:54:14 -07001995 && !isBlank();
Mindy Pereira82cc5662012-01-09 17:29:30 -08001996 }
1997 }
1998
1999 /**
Mindy Pereirabdf7a402012-03-01 15:23:26 -08002000 * Check if all fields are blank.
Mindy Pereira82cc5662012-01-09 17:29:30 -08002001 * @return boolean
2002 */
2003 public boolean isBlank() {
2004 return mSubject.getText().length() == 0
Mindy Pereirabdf7a402012-03-01 15:23:26 -08002005 && (mBodyView.getText().length() == 0 || getSignatureStartPosition(mSignature,
2006 mBodyView.getText().toString()) == 0)
2007 && mTo.length() == 0
2008 && mCc.length() == 0 && mBcc.length() == 0
2009 && mAttachmentsView.getAttachments().size() == 0;
2010 }
2011
2012 @VisibleForTesting
2013 protected int getSignatureStartPosition(String signature, String bodyText) {
2014 int startPos = -1;
2015
2016 if (TextUtils.isEmpty(signature) || TextUtils.isEmpty(bodyText)) {
2017 return startPos;
2018 }
2019
2020 int bodyLength = bodyText.length();
2021 int signatureLength = signature.length();
2022 String printableVersion = convertToPrintableSignature(signature);
2023 int printableLength = printableVersion.length();
2024
2025 if (bodyLength >= printableLength
2026 && bodyText.substring(bodyLength - printableLength)
2027 .equals(printableVersion)) {
2028 startPos = bodyLength - printableLength;
2029 } else if (bodyLength >= signatureLength
2030 && bodyText.substring(bodyLength - signatureLength)
2031 .equals(signature)) {
2032 startPos = bodyLength - signatureLength;
2033 }
2034 return startPos;
Mindy Pereira82cc5662012-01-09 17:29:30 -08002035 }
2036
2037 /**
2038 * Allows any changes made by the user to be ignored. Called when the user
2039 * decides to discard a draft.
2040 */
2041 private void discardChanges() {
2042 mTextChanged = false;
Mindy Pereiraeaea9f12012-01-10 15:05:27 -08002043 mAttachmentsChanged = false;
Mindy Pereira82cc5662012-01-09 17:29:30 -08002044 mReplyFromChanged = false;
2045 }
2046
2047 /**
Mindy Pereira181df782012-03-01 13:32:44 -08002048 * @param body
2049 * @param save
2050 * @param showToast
2051 * @return Whether the send or save succeeded.
2052 */
2053 protected boolean sendOrSaveWithSanityChecks(final boolean save, final boolean showToast,
2054 final boolean orientationChanged) {
2055 String[] to, cc, bcc;
2056 Editable body = mBodyView.getEditableText();
Mindy Pereira181df782012-03-01 13:32:44 -08002057 if (orientationChanged) {
2058 to = cc = bcc = new String[0];
2059 } else {
2060 to = getToAddresses();
2061 cc = getCcAddresses();
2062 bcc = getBccAddresses();
2063 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002064
Mindy Pereira181df782012-03-01 13:32:44 -08002065 // Don't let the user send to nobody (but it's okay to save a message
2066 // with no recipients)
2067 if (!save && (to.length == 0 && cc.length == 0 && bcc.length == 0)) {
2068 showRecipientErrorDialog(getString(R.string.recipient_needed));
2069 return false;
2070 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002071
Mindy Pereira181df782012-03-01 13:32:44 -08002072 List<String> wrongEmails = new ArrayList<String>();
2073 if (!save) {
2074 checkInvalidEmails(to, wrongEmails);
2075 checkInvalidEmails(cc, wrongEmails);
2076 checkInvalidEmails(bcc, wrongEmails);
2077 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002078
Mindy Pereira181df782012-03-01 13:32:44 -08002079 // Don't let the user send an email with invalid recipients
2080 if (wrongEmails.size() > 0) {
2081 String errorText = String.format(getString(R.string.invalid_recipient),
2082 wrongEmails.get(0));
2083 showRecipientErrorDialog(errorText);
2084 return false;
2085 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002086
Mindy Pereira181df782012-03-01 13:32:44 -08002087 DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
Marc Blank0bbc8582012-04-23 15:07:57 -07002088 @Override
Mindy Pereira181df782012-03-01 13:32:44 -08002089 public void onClick(DialogInterface dialog, int which) {
2090 sendOrSave(mBodyView.getEditableText(), save, showToast, orientationChanged);
2091 }
2092 };
Mindy Pereira82cc5662012-01-09 17:29:30 -08002093
Mindy Pereira181df782012-03-01 13:32:44 -08002094 // Show a warning before sending only if there are no attachments.
2095 if (!save) {
2096 if (mAttachmentsView.getAttachments().isEmpty() && showEmptyTextWarnings()) {
2097 boolean warnAboutEmptySubject = isSubjectEmpty();
2098 boolean emptyBody = TextUtils.getTrimmedLength(body) == 0;
Mindy Pereira82cc5662012-01-09 17:29:30 -08002099
Mindy Pereira181df782012-03-01 13:32:44 -08002100 // A warning about an empty body may not be warranted when
2101 // forwarding mails, since a common use case is to forward
2102 // quoted text and not append any more text.
2103 boolean warnAboutEmptyBody = emptyBody && (!mForward || isBodyEmpty());
Mindy Pereira82cc5662012-01-09 17:29:30 -08002104
Mindy Pereira181df782012-03-01 13:32:44 -08002105 // When we bring up a dialog warning the user about a send,
2106 // assume that they accept sending the message. If they do not,
2107 // the dialog listener is required to enable sending again.
2108 if (warnAboutEmptySubject) {
2109 showSendConfirmDialog(R.string.confirm_send_message_with_no_subject, listener);
2110 return true;
2111 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002112
Mindy Pereira181df782012-03-01 13:32:44 -08002113 if (warnAboutEmptyBody) {
2114 showSendConfirmDialog(R.string.confirm_send_message_with_no_body, listener);
2115 return true;
2116 }
2117 }
2118 // Ask for confirmation to send (if always required)
2119 if (showSendConfirmation()) {
2120 showSendConfirmDialog(R.string.confirm_send_message, listener);
2121 return true;
2122 }
2123 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002124
Mindy Pereira181df782012-03-01 13:32:44 -08002125 sendOrSave(body, save, showToast, false);
2126 return true;
2127 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002128
Mindy Pereira181df782012-03-01 13:32:44 -08002129 /**
2130 * Returns a boolean indicating whether warnings should be shown for empty
2131 * subject and body fields
Andy Huang5c5fd572012-04-08 18:19:29 -07002132 *
Mindy Pereira181df782012-03-01 13:32:44 -08002133 * @return True if a warning should be shown for empty text fields
2134 */
2135 protected boolean showEmptyTextWarnings() {
2136 return mAttachmentsView.getAttachments().size() == 0;
2137 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002138
Mindy Pereira181df782012-03-01 13:32:44 -08002139 /**
2140 * Returns a boolean indicating whether the user should confirm each send
2141 *
2142 * @return True if a warning should be on each send
2143 */
2144 protected boolean showSendConfirmation() {
2145 return mCachedSettings != null ? mCachedSettings.confirmSend : false;
2146 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002147
Mindy Pereira181df782012-03-01 13:32:44 -08002148 private void showSendConfirmDialog(int messageId, DialogInterface.OnClickListener listener) {
2149 if (mSendConfirmDialog != null) {
2150 mSendConfirmDialog.dismiss();
2151 mSendConfirmDialog = null;
2152 }
2153 mSendConfirmDialog = new AlertDialog.Builder(this).setMessage(messageId)
2154 .setTitle(R.string.confirm_send_title)
2155 .setIconAttribute(android.R.attr.alertDialogIcon)
2156 .setPositiveButton(R.string.send, listener)
Mindy Pereira6edd5972012-06-19 10:22:36 -07002157 .setNegativeButton(R.string.cancel, this)
2158 .show();
Mindy Pereira181df782012-03-01 13:32:44 -08002159 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002160
Mindy Pereira181df782012-03-01 13:32:44 -08002161 /**
2162 * Returns whether the ComposeArea believes there is any text in the body of
2163 * the composition. TODO: When ComposeArea controls the Body as well, add
2164 * that here.
2165 */
2166 public boolean isBodyEmpty() {
2167 return !mQuotedTextView.isTextIncluded();
2168 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002169
Mindy Pereira181df782012-03-01 13:32:44 -08002170 /**
2171 * Test to see if the subject is empty.
2172 *
2173 * @return boolean.
2174 */
2175 // TODO: this will likely go away when composeArea.focus() is implemented
2176 // after all the widget control is moved over.
2177 public boolean isSubjectEmpty() {
2178 return TextUtils.getTrimmedLength(mSubject.getText()) == 0;
2179 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002180
Mindy Pereira181df782012-03-01 13:32:44 -08002181 /* package */
Mindy Pereirae8f94dc2012-04-16 11:56:21 -07002182 static int sendOrSaveInternal(Context context, ReplyFromAccount replyFromAccount,
Paul Westbrook05b92b82012-04-20 13:29:37 -07002183 Message message, final Message refMessage, Spanned body, final CharSequence quotedText,
2184 SendOrSaveCallback callback, Handler handler, boolean save, int composeMode) {
Mindy Pereira29ef1b82012-01-13 11:26:21 -08002185 ContentValues values = new ContentValues();
Mindy Pereira82cc5662012-01-09 17:29:30 -08002186
Mindy Pereirac2031972012-04-03 09:38:35 -07002187 String refMessageId = refMessage != null ? refMessage.uri.toString() : "";
2188
Mindy Pereirae8f94dc2012-04-16 11:56:21 -07002189 MessageModification.putToAddresses(values, message.getToAddresses());
2190 MessageModification.putCcAddresses(values, message.getCcAddresses());
2191 MessageModification.putBccAddresses(values, message.getBccAddresses());
Mindy Pereira82cc5662012-01-09 17:29:30 -08002192
Mindy Pereirae8f94dc2012-04-16 11:56:21 -07002193 MessageModification.putCustomFromAddress(values, message.from);
Mindy Pereira92551d02012-04-05 11:31:12 -07002194
Mindy Pereirae8f94dc2012-04-16 11:56:21 -07002195 MessageModification.putSubject(values, message.subject);
Mindy Pereira29ef1b82012-01-13 11:26:21 -08002196 String htmlBody = Html.toHtml(body);
Paul Westbrook05b92b82012-04-20 13:29:37 -07002197
Mindy Pereira29ef1b82012-01-13 11:26:21 -08002198 boolean includeQuotedText = !TextUtils.isEmpty(quotedText);
2199 StringBuilder fullBody = new StringBuilder(htmlBody);
2200 if (includeQuotedText) {
Mindy Pereirae8caf122012-03-20 15:23:31 -07002201 // HTML gets converted to text for now
2202 final String text = quotedText.toString();
2203 if (QuotedTextView.containsQuotedText(text)) {
2204 int pos = QuotedTextView.getQuotedTextOffset(text);
Paul Westbrook55271cf2012-04-20 16:25:02 -07002205 final int quoteStartPos = fullBody.length() + pos;
2206 fullBody.append(text);
2207 MessageModification.putQuoteStartPos(values, quoteStartPos);
Mindy Pereira12575862012-03-21 16:30:54 -07002208 MessageModification.putForward(values, composeMode == ComposeActivity.FORWARD);
Mindy Pereirae8caf122012-03-20 15:23:31 -07002209 MessageModification.putAppendRefMessageContent(values, includeQuotedText);
Mindy Pereira29ef1b82012-01-13 11:26:21 -08002210 } else {
Mindy Pereirae8caf122012-03-20 15:23:31 -07002211 LogUtils.w(LOG_TAG, "Couldn't find quoted text");
2212 // This shouldn't happen, but just use what we have,
2213 // and don't do server-side expansion
2214 fullBody.append(text);
Mindy Pereira29ef1b82012-01-13 11:26:21 -08002215 }
2216 }
Mindy Pereira002ff522012-05-30 10:31:26 -07002217 int draftType = getDraftType(composeMode);
Mindy Pereira12575862012-03-21 16:30:54 -07002218 MessageModification.putDraftType(values, draftType);
Mindy Pereirac6f1e2a2012-04-04 10:33:45 -07002219 if (refMessage != null) {
2220 if (!TextUtils.isEmpty(refMessage.bodyHtml)) {
2221 MessageModification.putBodyHtml(values, fullBody.toString());
2222 }
2223 if (!TextUtils.isEmpty(refMessage.bodyText)) {
2224 MessageModification.putBody(values, Html.fromHtml(fullBody.toString()).toString());
2225 }
2226 } else {
Mindy Pereirac2031972012-04-03 09:38:35 -07002227 MessageModification.putBodyHtml(values, fullBody.toString());
Mindy Pereirac2031972012-04-03 09:38:35 -07002228 MessageModification.putBody(values, Html.fromHtml(fullBody.toString()).toString());
2229 }
Mindy Pereirae8f94dc2012-04-16 11:56:21 -07002230 MessageModification.putAttachments(values, message.getAttachments());
Mindy Pereira12575862012-03-21 16:30:54 -07002231 if (!TextUtils.isEmpty(refMessageId)) {
2232 MessageModification.putRefMessageId(values, refMessageId);
2233 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002234
Mindy Pereira92551d02012-04-05 11:31:12 -07002235 SendOrSaveMessage sendOrSaveMessage = new SendOrSaveMessage(replyFromAccount,
Mindy Pereira181df782012-03-01 13:32:44 -08002236 values, refMessageId, save);
2237 SendOrSaveTask sendOrSaveTask = new SendOrSaveTask(context, sendOrSaveMessage, callback);
Mindy Pereira82cc5662012-01-09 17:29:30 -08002238
Mindy Pereira181df782012-03-01 13:32:44 -08002239 callback.initializeSendOrSave(sendOrSaveTask);
Mindy Pereira82cc5662012-01-09 17:29:30 -08002240
Mindy Pereira181df782012-03-01 13:32:44 -08002241 // Do the send/save action on the specified handler to avoid possible
2242 // ANRs
2243 handler.post(sendOrSaveTask);
Mindy Pereira82cc5662012-01-09 17:29:30 -08002244
Mindy Pereira181df782012-03-01 13:32:44 -08002245 return sendOrSaveMessage.requestId();
2246 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002247
Mindy Pereira002ff522012-05-30 10:31:26 -07002248 private static int getDraftType(int mode) {
2249 int draftType = -1;
2250 switch (mode) {
2251 case ComposeActivity.COMPOSE:
2252 draftType = DraftType.COMPOSE;
2253 break;
2254 case ComposeActivity.REPLY:
2255 draftType = DraftType.REPLY;
2256 break;
2257 case ComposeActivity.REPLY_ALL:
2258 draftType = DraftType.REPLY_ALL;
2259 break;
2260 case ComposeActivity.FORWARD:
2261 draftType = DraftType.FORWARD;
2262 break;
2263 }
2264 return draftType;
2265 }
2266
Mindy Pereira181df782012-03-01 13:32:44 -08002267 private void sendOrSave(Spanned body, boolean save, boolean showToast,
2268 boolean orientationChanged) {
2269 // Check if user is a monkey. Monkeys can compose and hit send
2270 // button but are not allowed to send anything off the device.
Paul Westbrook3ae824c2012-04-06 13:29:39 -07002271 if (ActivityManager.isUserAMonkey()) {
Mindy Pereira181df782012-03-01 13:32:44 -08002272 return;
2273 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002274
Mindy Pereira181df782012-03-01 13:32:44 -08002275 String[] to, cc, bcc;
2276 if (orientationChanged) {
2277 to = cc = bcc = new String[0];
2278 } else {
2279 to = getToAddresses();
2280 cc = getCcAddresses();
2281 bcc = getBccAddresses();
2282 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002283
Mindy Pereira181df782012-03-01 13:32:44 -08002284 SendOrSaveCallback callback = new SendOrSaveCallback() {
Mindy Pereira82cc5662012-01-09 17:29:30 -08002285 private int mRestoredRequestId;
2286
Marc Blank0bbc8582012-04-23 15:07:57 -07002287 @Override
Mindy Pereira82cc5662012-01-09 17:29:30 -08002288 public void initializeSendOrSave(SendOrSaveTask sendOrSaveTask) {
Mindy Pereira181df782012-03-01 13:32:44 -08002289 synchronized (mActiveTasks) {
2290 int numTasks = mActiveTasks.size();
2291 if (numTasks == 0) {
2292 // Start service so we won't be killed if this app is
2293 // put in the background.
2294 startService(new Intent(ComposeActivity.this, EmptyService.class));
2295 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002296
Mindy Pereira181df782012-03-01 13:32:44 -08002297 mActiveTasks.add(sendOrSaveTask);
2298 }
2299 if (sTestSendOrSaveCallback != null) {
2300 sTestSendOrSaveCallback.initializeSendOrSave(sendOrSaveTask);
2301 }
2302 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002303
Marc Blank0bbc8582012-04-23 15:07:57 -07002304 @Override
Mindy Pereira7ed1c112012-01-18 10:59:25 -08002305 public void notifyMessageIdAllocated(SendOrSaveMessage sendOrSaveMessage,
2306 Message message) {
Mindy Pereira181df782012-03-01 13:32:44 -08002307 synchronized (mDraftLock) {
2308 mDraftId = message.id;
2309 mDraft = message;
Mindy Pereira7ed1c112012-01-18 10:59:25 -08002310 if (sRequestMessageIdMap != null) {
2311 sRequestMessageIdMap.put(sendOrSaveMessage.requestId(), mDraftId);
2312 }
Mindy Pereira181df782012-03-01 13:32:44 -08002313 // Cache request message map, in case the process is killed
2314 saveRequestMap();
2315 }
2316 if (sTestSendOrSaveCallback != null) {
Mindy Pereira7ed1c112012-01-18 10:59:25 -08002317 sTestSendOrSaveCallback.notifyMessageIdAllocated(sendOrSaveMessage, message);
Mindy Pereira181df782012-03-01 13:32:44 -08002318 }
2319 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002320
Marc Blank0bbc8582012-04-23 15:07:57 -07002321 @Override
Mindy Pereira7ed1c112012-01-18 10:59:25 -08002322 public Message getMessage() {
2323 synchronized (mDraftLock) {
2324 return mDraft;
2325 }
2326 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002327
Marc Blank0bbc8582012-04-23 15:07:57 -07002328 @Override
Mindy Pereira7ed1c112012-01-18 10:59:25 -08002329 public void sendOrSaveFinished(SendOrSaveTask task, boolean success) {
Mindy Pereira47d0e652012-07-23 09:45:07 -07002330 // Update the last sent from account.
2331 if (mAccount != null) {
2332 MailAppProvider.getInstance().setLastSentFromAccount(mAccount.uri.toString());
2333 }
Mindy Pereira7ed1c112012-01-18 10:59:25 -08002334 if (success) {
2335 // Successfully sent or saved so reset change markers
2336 discardChanges();
2337 } else {
2338 // A failure happened with saving/sending the draft
2339 // TODO(pwestbro): add a better string that should be used
2340 // when failing to send or save
2341 Toast.makeText(ComposeActivity.this, R.string.send_failed, Toast.LENGTH_SHORT)
2342 .show();
2343 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002344
Mindy Pereira7ed1c112012-01-18 10:59:25 -08002345 int numTasks;
2346 synchronized (mActiveTasks) {
2347 // Remove the task from the list of active tasks
2348 mActiveTasks.remove(task);
2349 numTasks = mActiveTasks.size();
2350 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002351
Mindy Pereira7ed1c112012-01-18 10:59:25 -08002352 if (numTasks == 0) {
2353 // Stop service so we can be killed.
2354 stopService(new Intent(ComposeActivity.this, EmptyService.class));
2355 }
2356 if (sTestSendOrSaveCallback != null) {
2357 sTestSendOrSaveCallback.sendOrSaveFinished(task, success);
2358 }
2359 }
Mindy Pereira181df782012-03-01 13:32:44 -08002360 };
Mindy Pereira82cc5662012-01-09 17:29:30 -08002361
Mindy Pereira181df782012-03-01 13:32:44 -08002362 // Get the selected account if the from spinner has been setup.
Mindy Pereira92551d02012-04-05 11:31:12 -07002363 ReplyFromAccount selectedAccount = mReplyFromAccount;
Mindy Pereira181df782012-03-01 13:32:44 -08002364 String fromAddress = selectedAccount.name;
2365 if (selectedAccount == null || fromAddress == null) {
2366 // We don't have either the selected account or from address,
2367 // use mAccount.
Mindy Pereira92551d02012-04-05 11:31:12 -07002368 selectedAccount = mReplyFromAccount;
Mindy Pereira181df782012-03-01 13:32:44 -08002369 fromAddress = mAccount.name;
2370 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002371
Mindy Pereira181df782012-03-01 13:32:44 -08002372 if (mSendSaveTaskHandler == null) {
2373 HandlerThread handlerThread = new HandlerThread("Send Message Task Thread");
2374 handlerThread.start();
Mindy Pereira82cc5662012-01-09 17:29:30 -08002375
Mindy Pereira181df782012-03-01 13:32:44 -08002376 mSendSaveTaskHandler = new Handler(handlerThread.getLooper());
2377 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002378
Mindy Pereirae8f94dc2012-04-16 11:56:21 -07002379 Message msg = createMessage(mReplyFromAccount, getMode());
Paul Westbrook05b92b82012-04-20 13:29:37 -07002380 mRequestId = sendOrSaveInternal(this, mReplyFromAccount, msg, mRefMessage, body,
2381 mQuotedTextView.getQuotedTextIfIncluded(), callback,
Mindy Pereira12575862012-03-21 16:30:54 -07002382 mSendSaveTaskHandler, save, mComposeMode);
Mindy Pereira82cc5662012-01-09 17:29:30 -08002383
Mindy Pereira181df782012-03-01 13:32:44 -08002384 if (mRecipient != null && mRecipient.equals(mAccount.name)) {
2385 mRecipient = selectedAccount.name;
2386 }
Paul Westbrookb1f573c2012-04-06 11:38:28 -07002387 setAccount(selectedAccount.account);
Mindy Pereira82cc5662012-01-09 17:29:30 -08002388
Mindy Pereira181df782012-03-01 13:32:44 -08002389 // Don't display the toast if the user is just changing the orientation,
2390 // but we still need to save the draft to the cursor because this is how we restore
2391 // the attachments when the configuration change completes.
2392 if (showToast && (getChangingConfigurations() & ActivityInfo.CONFIG_ORIENTATION) == 0) {
2393 Toast.makeText(this, save ? R.string.message_saved : R.string.sending_message,
2394 Toast.LENGTH_LONG).show();
2395 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002396
Mindy Pereira181df782012-03-01 13:32:44 -08002397 // Need to update variables here because the send or save completes
2398 // asynchronously even though the toast shows right away.
2399 discardChanges();
2400 updateSaveUi();
Mindy Pereira82cc5662012-01-09 17:29:30 -08002401
Mindy Pereira181df782012-03-01 13:32:44 -08002402 // If we are sending, finish the activity
2403 if (!save) {
2404 finish();
2405 }
2406 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002407
Mindy Pereira181df782012-03-01 13:32:44 -08002408 /**
2409 * Save the state of the request messageid map. This allows for the Gmail
2410 * process to be killed, but and still allow for ComposeActivity instances
2411 * to be recreated correctly.
2412 */
2413 private void saveRequestMap() {
2414 // TODO: store the request map in user preferences.
2415 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002416
Mindy Pereira2db7d4a2012-08-15 11:00:02 -07002417 private void doAttach(String type) {
Mindy Pereira013194c2012-01-06 15:09:33 -08002418 Intent i = new Intent(Intent.ACTION_GET_CONTENT);
2419 i.addCategory(Intent.CATEGORY_OPENABLE);
Paul Westbrookd6a9a3f2012-04-26 18:47:23 -07002420 i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
Mindy Pereira2db7d4a2012-08-15 11:00:02 -07002421 i.setType(type);
Mindy Pereira013194c2012-01-06 15:09:33 -08002422 mAddingAttachment = true;
Mindy Pereira181df782012-03-01 13:32:44 -08002423 startActivityForResult(Intent.createChooser(i, getText(R.string.select_attachment_type)),
2424 RESULT_PICK_ATTACHMENT);
Mindy Pereira013194c2012-01-06 15:09:33 -08002425 }
2426
Mindy Pereiraec8b0ed2012-01-06 10:35:59 -08002427 private void showCcBccViews() {
Mindy Pereiraa26b54e2012-01-06 12:54:33 -08002428 mCcBccView.show(true, true, true);
Mindy Pereiraec8b0ed2012-01-06 10:35:59 -08002429 if (mCcBccButton != null) {
mindypcd0b0b92012-08-23 14:33:17 -07002430 mCcBccButton.setVisibility(View.INVISIBLE);
Mindy Pereiraec8b0ed2012-01-06 10:35:59 -08002431 }
2432 }
2433
Mindy Pereira326c6602012-01-04 15:32:42 -08002434 @Override
2435 public boolean onNavigationItemSelected(int position, long itemId) {
Mindy Pereiraa26b54e2012-01-06 12:54:33 -08002436 int initialComposeMode = mComposeMode;
Mindy Pereira326c6602012-01-04 15:32:42 -08002437 if (position == ComposeActivity.REPLY) {
2438 mComposeMode = ComposeActivity.REPLY;
2439 } else if (position == ComposeActivity.REPLY_ALL) {
2440 mComposeMode = ComposeActivity.REPLY_ALL;
2441 } else if (position == ComposeActivity.FORWARD) {
2442 mComposeMode = ComposeActivity.FORWARD;
2443 }
Mindy Pereiracbfb75a2012-06-25 14:52:23 -07002444 clearChangeListeners();
Mindy Pereiraa26b54e2012-01-06 12:54:33 -08002445 if (initialComposeMode != mComposeMode) {
Mindy Pereira154386a2012-01-11 13:02:33 -08002446 resetMessageForModeChange();
Mindy Pereiraef388302012-06-18 19:07:44 -07002447 if (mDraft == null && mRefMessage != null) {
Mindy Pereira96a7f7a2012-07-09 16:51:06 -07002448 setFieldsFromRefMessage(mComposeMode, mAccount.name);
Mindy Pereira8eca57a2012-03-20 16:42:34 -07002449 }
Mindy Pereiraef388302012-06-18 19:07:44 -07002450 boolean showCc = false;
2451 boolean showBcc = false;
2452 if (mDraft != null) {
2453 // Following desktop behavior, if the user has added a BCC
2454 // field to a draft, we show it regardless of compose mode.
2455 showBcc = !TextUtils.isEmpty(mDraft.bcc);
2456 // Use the draft to determine what to populate.
2457 // If the Bcc field is showing, show the Cc field whether it is populated or not.
2458 showCc = showBcc || (!TextUtils.isEmpty(mDraft.cc) && mComposeMode == REPLY_ALL);
2459 } else if (mRefMessage != null) {
2460 showCc = mComposeMode == REPLY_ALL && !TextUtils.isEmpty(mRefMessage.cc);
2461 }
2462 mCcBccView.show(false, showCc, showBcc);
Mindy Pereiraa26b54e2012-01-06 12:54:33 -08002463 }
Mindy Pereiraef388302012-06-18 19:07:44 -07002464 updateHideOrShowCcBcc();
Mindy Pereiracbfb75a2012-06-25 14:52:23 -07002465 initChangeListeners();
Mindy Pereira326c6602012-01-04 15:32:42 -08002466 return true;
2467 }
2468
Mindy Pereirab3112a22012-06-20 12:10:03 -07002469 @VisibleForTesting
2470 protected void resetMessageForModeChange() {
Mindy Pereira154386a2012-01-11 13:02:33 -08002471 // When switching between reply, reply all, forward,
2472 // follow the behavior of webview.
2473 // The contents of the following fields are cleared
2474 // so that they can be populated directly from the
2475 // ref message:
2476 // 1) Any recipient fields
2477 // 2) The subject
2478 mTo.setText("");
2479 mCc.setText("");
2480 mBcc.setText("");
2481 // Any edits to the subject are replaced with the original subject.
2482 mSubject.setText("");
2483
2484 // Any changes to the contents of the following fields are kept:
2485 // 1) Body
2486 // 2) Attachments
2487 // If the user made changes to attachments, keep their changes.
2488 if (!mAttachmentsChanged) {
2489 mAttachmentsView.deleteAllAttachments();
2490 }
2491 }
2492
Mindy Pereira326c6602012-01-04 15:32:42 -08002493 private class ComposeModeAdapter extends ArrayAdapter<String> {
2494
2495 private LayoutInflater mInflater;
2496
2497 public ComposeModeAdapter(Context context) {
2498 super(context, R.layout.compose_mode_item, R.id.mode, getResources()
2499 .getStringArray(R.array.compose_modes));
2500 }
2501
2502 private LayoutInflater getInflater() {
2503 if (mInflater == null) {
2504 mInflater = LayoutInflater.from(getContext());
2505 }
2506 return mInflater;
2507 }
2508
2509 @Override
2510 public View getView(int position, View convertView, ViewGroup parent) {
2511 if (convertView == null) {
2512 convertView = getInflater().inflate(R.layout.compose_mode_display_item, null);
2513 }
2514 ((TextView) convertView.findViewById(R.id.mode)).setText(getItem(position));
2515 return super.getView(position, convertView, parent);
2516 }
2517 }
Mindy Pereira46ce0b12012-01-05 10:32:15 -08002518
2519 @Override
2520 public void onRespondInline(String text) {
2521 appendToBody(text, false);
mindyp40882432012-09-06 11:07:40 -07002522 mQuotedTextView.setUpperDividerVisible(false);
2523 mTo.requestFocus();
Mindy Pereira46ce0b12012-01-05 10:32:15 -08002524 }
2525
2526 /**
2527 * Append text to the body of the message. If there is no existing body
2528 * text, just sets the body to text.
2529 *
2530 * @param text
2531 * @param withSignature True to append a signature.
2532 */
2533 public void appendToBody(CharSequence text, boolean withSignature) {
Mindy Pereiraeaea9f12012-01-10 15:05:27 -08002534 Editable bodyText = mBodyView.getEditableText();
Mindy Pereira46ce0b12012-01-05 10:32:15 -08002535 if (bodyText != null && bodyText.length() > 0) {
2536 bodyText.append(text);
2537 } else {
2538 setBody(text, withSignature);
2539 }
2540 }
2541
2542 /**
2543 * Set the body of the message.
Mindy Pereirabdf7a402012-03-01 15:23:26 -08002544 *
Mindy Pereira46ce0b12012-01-05 10:32:15 -08002545 * @param text
2546 * @param withSignature True to append a signature.
2547 */
2548 public void setBody(CharSequence text, boolean withSignature) {
Mindy Pereiraeaea9f12012-01-10 15:05:27 -08002549 mBodyView.setText(text);
Mindy Pereirabdf7a402012-03-01 15:23:26 -08002550 if (withSignature) {
2551 appendSignature();
2552 }
2553 }
2554
2555 private void appendSignature() {
Mindy Pereirab13917c2012-03-29 08:08:19 -07002556 String newSignature = mCachedSettings != null ? mCachedSettings.signature : null;
Mindy Pereira433b1982012-04-03 11:53:07 -07002557 boolean hasFocus = mBodyView.hasFocus();
Mindy Pereirab13917c2012-03-29 08:08:19 -07002558 if (!TextUtils.equals(newSignature, mSignature)) {
2559 mSignature = newSignature;
2560 if (!TextUtils.isEmpty(mSignature)
2561 && getSignatureStartPosition(mSignature,
2562 mBodyView.getText().toString()) < 0) {
2563 // Appending a signature does not count as changing text.
2564 mBodyView.removeTextChangedListener(this);
2565 mBodyView.append(convertToPrintableSignature(mSignature));
2566 mBodyView.addTextChangedListener(this);
2567 }
Mindy Pereira433b1982012-04-03 11:53:07 -07002568 if (hasFocus) {
2569 focusBody();
2570 }
Mindy Pereirabdf7a402012-03-01 15:23:26 -08002571 }
2572 }
2573
2574 private String convertToPrintableSignature(String signature) {
2575 String signatureResource = getResources().getString(R.string.signature);
2576 if (signature == null) {
2577 signature = "";
2578 }
2579 return String.format(signatureResource, signature);
Mindy Pereira46ce0b12012-01-05 10:32:15 -08002580 }
Mindy Pereira1a95a572012-01-05 12:21:29 -08002581
Mindy Pereira5a85e2b2012-01-11 09:53:32 -08002582 @Override
2583 public void onAccountChanged() {
Mindy Pereira92551d02012-04-05 11:31:12 -07002584 mReplyFromAccount = mFromSpinner.getCurrentAccount();
2585 if (!mAccount.equals(mReplyFromAccount.account)) {
Paul Westbrookb1f573c2012-04-06 11:38:28 -07002586 setAccount(mReplyFromAccount.account);
2587
Mindy Pereira181df782012-03-01 13:32:44 -08002588 // TODO: handle discarding attachments when switching accounts.
2589 // Only enable save for this draft if there is any other content
2590 // in the message.
2591 if (!isBlank()) {
2592 enableSave(true);
2593 }
2594 mReplyFromChanged = true;
2595 initRecipients();
Mindy Pereira82cc5662012-01-09 17:29:30 -08002596 }
Mindy Pereira1a95a572012-01-05 12:21:29 -08002597 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002598
2599 public void enableSave(boolean enabled) {
2600 if (mSave != null) {
2601 mSave.setEnabled(enabled);
2602 }
2603 }
2604
2605 public void enableSend(boolean enabled) {
2606 if (mSend != null) {
2607 mSend.setEnabled(enabled);
2608 }
2609 }
2610
2611 /**
2612 * Handles button clicks from any error dialogs dealing with sending
2613 * a message.
2614 */
2615 @Override
2616 public void onClick(DialogInterface dialog, int which) {
2617 switch (which) {
2618 case DialogInterface.BUTTON_POSITIVE: {
2619 doDiscardWithoutConfirmation(true /* show toast */ );
2620 break;
2621 }
2622 case DialogInterface.BUTTON_NEGATIVE: {
2623 // If the user cancels the send, re-enable the send button.
2624 enableSend(true);
2625 break;
2626 }
2627 }
2628
2629 }
2630
Mindy Pereiraefe3d252012-03-01 14:20:44 -08002631 private void doDiscard() {
2632 new AlertDialog.Builder(this).setMessage(R.string.confirm_discard_text)
2633 .setPositiveButton(R.string.ok, this)
2634 .setNegativeButton(R.string.cancel, null)
2635 .create().show();
2636 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002637 /**
2638 * Effectively discard the current message.
2639 *
2640 * This method is either invoked from the menu or from the dialog
2641 * once the user has confirmed that they want to discard the message.
2642 * @param showToast show "Message discarded" toast if true
2643 */
2644 private void doDiscardWithoutConfirmation(boolean showToast) {
Mindy Pereira7ed1c112012-01-18 10:59:25 -08002645 synchronized (mDraftLock) {
Mindy Pereira82cc5662012-01-09 17:29:30 -08002646 if (mDraftId != UIProvider.INVALID_MESSAGE_ID) {
2647 ContentValues values = new ContentValues();
Paul Westbrookb7050e62012-03-20 12:59:44 -07002648 values.put(BaseColumns._ID, mDraftId);
Marc Blank78ea8e22012-08-04 11:14:06 -07002649 if (!mAccount.expungeMessageUri.equals(Uri.EMPTY)) {
Mindy Pereiracfb7f332012-02-28 10:23:43 -08002650 getContentResolver().update(mAccount.expungeMessageUri, values, null, null);
2651 } else {
Marc Blank0bbc8582012-04-23 15:07:57 -07002652 getContentResolver().delete(mDraft.uri, null, null);
Mindy Pereiracfb7f332012-02-28 10:23:43 -08002653 }
Mindy Pereira82cc5662012-01-09 17:29:30 -08002654 // This is not strictly necessary (since we should not try to
2655 // save the draft after calling this) but it ensures that if we
2656 // do save again for some reason we make a new draft rather than
2657 // trying to resave an expunged draft.
2658 mDraftId = UIProvider.INVALID_MESSAGE_ID;
2659 }
2660 }
2661
2662 if (showToast) {
2663 // Display a toast to let the user know
2664 Toast.makeText(this, R.string.message_discarded, Toast.LENGTH_SHORT).show();
2665 }
2666
2667 // This prevents the draft from being saved in onPause().
2668 discardChanges();
2669 finish();
2670 }
2671
Mindy Pereiraeaea9f12012-01-10 15:05:27 -08002672 private void saveIfNeeded() {
2673 if (mAccount == null) {
2674 // We have not chosen an account yet so there's no way that we can save. This is ok,
2675 // though, since we are saving our state before AccountsActivity is activated. Thus, the
2676 // user has not interacted with us yet and there is no real state to save.
2677 return;
2678 }
2679
2680 if (shouldSave()) {
Mindy Pereira48e31b02012-05-30 13:12:24 -07002681 doSave(!mAddingAttachment /* show toast */);
Mindy Pereiraeaea9f12012-01-10 15:05:27 -08002682 }
2683 }
2684
Mindy Pereiraeaea9f12012-01-10 15:05:27 -08002685 @Override
2686 public void onAttachmentDeleted() {
2687 mAttachmentsChanged = true;
mindyp40882432012-09-06 11:07:40 -07002688 // If we are showing any attachments, make sure we have an upper
2689 // divider.
2690 mQuotedTextView.setUpperDividerVisible(mAttachmentsView.getAttachments().size() > 0);
Mindy Pereiraeaea9f12012-01-10 15:05:27 -08002691 updateSaveUi();
2692 }
Mindy Pereira75f66632012-01-11 11:42:02 -08002693
mindyp40882432012-09-06 11:07:40 -07002694 @Override
2695 public void onAttachmentAdded() {
2696 mQuotedTextView.setUpperDividerVisible(mAttachmentsView.getAttachments().size() > 0);
2697 mAttachmentsView.focusLastAttachment();
2698 }
Mindy Pereira75f66632012-01-11 11:42:02 -08002699
2700 /**
2701 * This is called any time one of our text fields changes.
2702 */
Marc Blank0bbc8582012-04-23 15:07:57 -07002703 @Override
Mindy Pereira75f66632012-01-11 11:42:02 -08002704 public void afterTextChanged(Editable s) {
2705 mTextChanged = true;
2706 updateSaveUi();
2707 }
2708
2709 @Override
2710 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
2711 // Do nothing.
2712 }
2713
Marc Blank0bbc8582012-04-23 15:07:57 -07002714 @Override
Mindy Pereira75f66632012-01-11 11:42:02 -08002715 public void onTextChanged(CharSequence s, int start, int before, int count) {
2716 // Do nothing.
2717 }
2718
2719
2720 // There is a big difference between the text associated with an address changing
2721 // to add the display name or to format properly and a recipient being added or deleted.
2722 // Make sure we only notify of changes when a recipient has been added or deleted.
2723 private class RecipientTextWatcher implements TextWatcher {
2724 private HashMap<String, Integer> mContent = new HashMap<String, Integer>();
2725
2726 private RecipientEditTextView mView;
2727
2728 private TextWatcher mListener;
2729
2730 public RecipientTextWatcher(RecipientEditTextView view, TextWatcher listener) {
2731 mView = view;
2732 mListener = listener;
2733 }
2734
2735 @Override
2736 public void afterTextChanged(Editable s) {
2737 if (hasChanged()) {
2738 mListener.afterTextChanged(s);
2739 }
2740 }
2741
2742 private boolean hasChanged() {
2743 String[] currRecips = tokenizeRecips(getAddressesFromList(mView));
2744 int totalCount = currRecips.length;
2745 int totalPrevCount = 0;
2746 for (Entry<String, Integer> entry : mContent.entrySet()) {
2747 totalPrevCount += entry.getValue();
2748 }
2749 if (totalCount != totalPrevCount) {
2750 return true;
2751 }
2752
2753 for (String recip : currRecips) {
2754 if (!mContent.containsKey(recip)) {
2755 return true;
2756 } else {
2757 int count = mContent.get(recip) - 1;
2758 if (count < 0) {
2759 return true;
2760 } else {
2761 mContent.put(recip, count);
2762 }
2763 }
2764 }
2765 return false;
2766 }
2767
2768 private String[] tokenizeRecips(String[] recips) {
2769 // Tokenize them all and put them in the list.
2770 String[] recipAddresses = new String[recips.length];
2771 for (int i = 0; i < recips.length; i++) {
2772 recipAddresses[i] = Rfc822Tokenizer.tokenize(recips[i])[0].getAddress();
2773 }
2774 return recipAddresses;
2775 }
2776
2777 @Override
2778 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
2779 String[] recips = tokenizeRecips(getAddressesFromList(mView));
2780 for (String recip : recips) {
2781 if (!mContent.containsKey(recip)) {
2782 mContent.put(recip, 1);
2783 } else {
2784 mContent.put(recip, (mContent.get(recip)) + 1);
2785 }
2786 }
2787 }
2788
2789 @Override
2790 public void onTextChanged(CharSequence s, int start, int before, int count) {
2791 // Do nothing.
2792 }
2793 }
Mindy Pereirae011b1d2012-06-18 13:45:26 -07002794
2795 public static void registerTestSendOrSaveCallback(SendOrSaveCallback testCallback) {
2796 if (sTestSendOrSaveCallback != null && testCallback != null) {
2797 throw new IllegalStateException("Attempting to register more than one test callback");
2798 }
2799 sTestSendOrSaveCallback = testCallback;
2800 }
Mindy Pereirabddd6f32012-06-20 12:10:03 -07002801
2802 @VisibleForTesting
2803 protected ArrayList<Attachment> getAttachments() {
2804 return mAttachmentsView.getAttachments();
2805 }
Mindy Pereira96a7f7a2012-07-09 16:51:06 -07002806
2807 @Override
2808 public Loader<Cursor> onCreateLoader(int id, Bundle args) {
2809 switch (id) {
2810 case REFERENCE_MESSAGE_LOADER:
2811 return new CursorLoader(this, mRefMessageUri, UIProvider.MESSAGE_PROJECTION, null,
2812 null, null);
Mindy Pereirab199d172012-08-13 11:04:03 -07002813 case LOADER_ACCOUNT_CURSOR:
2814 return new CursorLoader(this, MailAppProvider.getAccountsUri(),
2815 UIProvider.ACCOUNTS_PROJECTION, null, null, null);
Mindy Pereira96a7f7a2012-07-09 16:51:06 -07002816 }
2817 return null;
2818 }
2819
2820 @Override
2821 public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
Mindy Pereirab199d172012-08-13 11:04:03 -07002822 int id = loader.getId();
2823 switch (id) {
2824 case REFERENCE_MESSAGE_LOADER:
2825 if (data != null && data.moveToFirst()) {
2826 mRefMessage = new Message(data);
2827 // We set these based on EXTRA_TO.
2828 mRefMessage.to = null;
2829 mRefMessage.from = null;
2830 Intent intent = getIntent();
2831 int action = intent.getIntExtra(EXTRA_ACTION, COMPOSE);
2832 initFromRefMessage(action, mAccount.name);
2833 finishSetup(action, intent, null, true);
2834 if (action != FORWARD) {
2835 String to = intent.getStringExtra(EXTRA_TO);
2836 if (!TextUtils.isEmpty(to)) {
2837 clearChangeListeners();
2838 mTo.append(to);
2839 initChangeListeners();
2840 }
2841 }
2842 } else {
2843 finish();
Mindy Pereira96a7f7a2012-07-09 16:51:06 -07002844 }
Mindy Pereirab199d172012-08-13 11:04:03 -07002845 break;
2846 case LOADER_ACCOUNT_CURSOR:
2847 if (data != null && data.moveToFirst()) {
2848 // there are accounts now!
2849 Account account;
2850 ArrayList<Account> accounts = new ArrayList<Account>();
2851 ArrayList<Account> initializedAccounts = new ArrayList<Account>();
2852 do {
2853 account = new Account(data);
2854 if (account.isAccountIntialized()) {
2855 initializedAccounts.add(account);
2856 }
2857 accounts.add(account);
2858 } while (data.moveToNext());
2859 if (initializedAccounts.size() > 0) {
2860 findViewById(R.id.wait).setVisibility(View.GONE);
2861 getLoaderManager().destroyLoader(LOADER_ACCOUNT_CURSOR);
2862 findViewById(R.id.compose).setVisibility(View.VISIBLE);
2863 mAccounts = accounts.toArray(new Account[initializedAccounts.size()]);
2864 finishCreate();
2865 invalidateOptionsMenu();
2866 } else {
2867 // Show "waiting"
2868 account = accounts.size() > 0 ? accounts.get(0) : null;
2869 showWaitFragment(account);
2870 }
2871 }
2872 break;
Mindy Pereira96a7f7a2012-07-09 16:51:06 -07002873 }
2874 }
2875
Mindy Pereirab199d172012-08-13 11:04:03 -07002876 private void showWaitFragment(Account account) {
2877 WaitFragment fragment = getWaitFragment();
2878 if (fragment != null) {
2879 fragment.updateAccount(account);
2880 } else {
2881 findViewById(R.id.wait).setVisibility(View.VISIBLE);
2882 replaceFragment(WaitFragment.newInstance(account, true),
2883 FragmentTransaction.TRANSIT_FRAGMENT_OPEN, TAG_WAIT);
2884 }
2885 }
2886
2887 private WaitFragment getWaitFragment() {
2888 return (WaitFragment) getFragmentManager().findFragmentByTag(TAG_WAIT);
2889 }
2890
2891 private int replaceFragment(Fragment fragment, int transition, String tag) {
2892 FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
2893 fragmentTransaction.addToBackStack(null);
2894 fragmentTransaction.setTransition(transition);
2895 fragmentTransaction.replace(R.id.wait, fragment, tag);
2896 final int transactionId = fragmentTransaction.commitAllowingStateLoss();
2897 return transactionId;
2898 }
2899
Mindy Pereira96a7f7a2012-07-09 16:51:06 -07002900 @Override
2901 public void onLoaderReset(Loader<Cursor> arg0) {
2902 // Do nothing.
2903 }
2904}