2009-03-04 03:32:22 +00:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2008 The Android Open Source Project
|
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package com.android.email;
|
|
|
|
|
2011-06-13 21:57:17 +00:00
|
|
|
import android.content.ContentResolver;
|
|
|
|
import android.content.ContentUris;
|
|
|
|
import android.content.ContentValues;
|
|
|
|
import android.content.Context;
|
|
|
|
import android.database.Cursor;
|
|
|
|
import android.net.Uri;
|
|
|
|
import android.os.Process;
|
2011-06-23 17:52:21 +00:00
|
|
|
import android.text.TextUtils;
|
2011-06-13 21:57:17 +00:00
|
|
|
import android.util.Log;
|
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
import com.android.email.mail.Sender;
|
|
|
|
import com.android.email.mail.Store;
|
2011-02-11 23:05:17 +00:00
|
|
|
import com.android.emailcommon.Logging;
|
2011-02-10 02:47:43 +00:00
|
|
|
import com.android.emailcommon.internet.MimeBodyPart;
|
|
|
|
import com.android.emailcommon.internet.MimeHeader;
|
|
|
|
import com.android.emailcommon.internet.MimeMultipart;
|
|
|
|
import com.android.emailcommon.internet.MimeUtility;
|
|
|
|
import com.android.emailcommon.mail.AuthenticationFailedException;
|
|
|
|
import com.android.emailcommon.mail.FetchProfile;
|
|
|
|
import com.android.emailcommon.mail.Flag;
|
|
|
|
import com.android.emailcommon.mail.Folder;
|
|
|
|
import com.android.emailcommon.mail.Folder.FolderType;
|
|
|
|
import com.android.emailcommon.mail.Folder.MessageRetrievalListener;
|
2011-03-23 21:36:16 +00:00
|
|
|
import com.android.emailcommon.mail.Folder.MessageUpdateCallbacks;
|
2011-02-10 02:47:43 +00:00
|
|
|
import com.android.emailcommon.mail.Folder.OpenMode;
|
2011-03-23 21:36:16 +00:00
|
|
|
import com.android.emailcommon.mail.Message;
|
|
|
|
import com.android.emailcommon.mail.MessagingException;
|
|
|
|
import com.android.emailcommon.mail.Part;
|
2011-06-13 22:32:27 +00:00
|
|
|
import com.android.emailcommon.provider.Account;
|
2011-02-10 18:26:56 +00:00
|
|
|
import com.android.emailcommon.provider.EmailContent;
|
|
|
|
import com.android.emailcommon.provider.EmailContent.Attachment;
|
|
|
|
import com.android.emailcommon.provider.EmailContent.AttachmentColumns;
|
|
|
|
import com.android.emailcommon.provider.EmailContent.MailboxColumns;
|
|
|
|
import com.android.emailcommon.provider.EmailContent.MessageColumns;
|
|
|
|
import com.android.emailcommon.provider.EmailContent.SyncColumns;
|
2011-05-14 00:26:27 +00:00
|
|
|
import com.android.emailcommon.provider.Mailbox;
|
2011-06-13 21:57:17 +00:00
|
|
|
import com.android.emailcommon.service.SearchParams;
|
2011-02-09 01:50:30 +00:00
|
|
|
import com.android.emailcommon.utility.AttachmentUtilities;
|
|
|
|
import com.android.emailcommon.utility.ConversionUtilities;
|
2011-02-11 23:05:17 +00:00
|
|
|
import com.android.emailcommon.utility.Utility;
|
2009-03-04 03:32:22 +00:00
|
|
|
|
2009-08-11 22:02:57 +00:00
|
|
|
import java.io.IOException;
|
2009-04-15 19:58:19 +00:00
|
|
|
import java.util.ArrayList;
|
2011-06-13 21:57:17 +00:00
|
|
|
import java.util.Arrays;
|
|
|
|
import java.util.Comparator;
|
2009-04-15 19:58:19 +00:00
|
|
|
import java.util.Date;
|
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.HashSet;
|
|
|
|
import java.util.concurrent.BlockingQueue;
|
|
|
|
import java.util.concurrent.LinkedBlockingQueue;
|
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
/**
|
|
|
|
* Starts a long running (application) Thread that will run through commands
|
|
|
|
* that require remote mailbox access. This class is used to serialize and
|
|
|
|
* prioritize these commands. Each method that will submit a command requires a
|
|
|
|
* MessagingListener instance to be provided. It is expected that that listener
|
|
|
|
* has also been added as a registered listener using addListener(). When a
|
|
|
|
* command is to be executed, if the listener that was provided with the command
|
|
|
|
* is no longer registered the command is skipped. The design idea for the above
|
|
|
|
* is that when an Activity starts it registers as a listener. When it is paused
|
|
|
|
* it removes itself. Thus, any commands that that activity submitted are
|
|
|
|
* removed from the queue once the activity is no longer active.
|
|
|
|
*/
|
|
|
|
public class MessagingController implements Runnable {
|
2009-09-25 21:54:32 +00:00
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
/**
|
|
|
|
* The maximum message size that we'll consider to be "small". A small message is downloaded
|
|
|
|
* in full immediately instead of in pieces. Anything over this size will be downloaded in
|
|
|
|
* pieces with attachments being left off completely and downloaded on demand.
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* 25k for a "small" message was picked by educated trial and error.
|
|
|
|
* http://answers.google.com/answers/threadview?id=312463 claims that the
|
|
|
|
* average size of an email is 59k, which I feel is too large for our
|
|
|
|
* blind download. The following tests were performed on a download of
|
|
|
|
* 25 random messages.
|
|
|
|
* <pre>
|
|
|
|
* 5k - 61 seconds,
|
|
|
|
* 25k - 51 seconds,
|
|
|
|
* 55k - 53 seconds,
|
|
|
|
* </pre>
|
|
|
|
* So 25k gives good performance and a reasonable data footprint. Sounds good to me.
|
|
|
|
*/
|
|
|
|
private static final int MAX_SMALL_MESSAGE_SIZE = (25 * 1024);
|
|
|
|
|
2010-05-10 21:20:16 +00:00
|
|
|
private static final Flag[] FLAG_LIST_SEEN = new Flag[] { Flag.SEEN };
|
|
|
|
private static final Flag[] FLAG_LIST_FLAGGED = new Flag[] { Flag.FLAGGED };
|
2009-03-04 03:32:22 +00:00
|
|
|
|
2009-09-25 21:54:32 +00:00
|
|
|
/**
|
|
|
|
* We write this into the serverId field of messages that will never be upsynced.
|
|
|
|
*/
|
|
|
|
private static final String LOCAL_SERVERID_PREFIX = "Local-";
|
|
|
|
|
2011-06-17 18:43:32 +00:00
|
|
|
/**
|
|
|
|
* Cache search results by account; this allows for "load more" support without having to
|
|
|
|
* redo the search (which can be quite slow). SortableMessage is a smallish class, so memory
|
|
|
|
* shouldn't be an issue
|
|
|
|
*/
|
|
|
|
private static final HashMap<Long, SortableMessage[]> sSearchResults =
|
|
|
|
new HashMap<Long, SortableMessage[]>();
|
|
|
|
|
2010-05-10 21:20:16 +00:00
|
|
|
private static final ContentValues PRUNE_ATTACHMENT_CV = new ContentValues();
|
2009-08-19 20:15:13 +00:00
|
|
|
static {
|
|
|
|
PRUNE_ATTACHMENT_CV.putNull(AttachmentColumns.CONTENT_URI);
|
|
|
|
}
|
|
|
|
|
2010-05-10 21:20:16 +00:00
|
|
|
private static MessagingController sInstance = null;
|
|
|
|
private final BlockingQueue<Command> mCommands = new LinkedBlockingQueue<Command>();
|
|
|
|
private final Thread mThread;
|
2009-06-22 23:13:03 +00:00
|
|
|
|
2009-04-24 23:17:04 +00:00
|
|
|
/**
|
|
|
|
* All access to mListeners *must* be synchronized
|
|
|
|
*/
|
2010-05-10 21:20:16 +00:00
|
|
|
private final GroupMessagingListener mListeners = new GroupMessagingListener();
|
2009-03-04 03:32:22 +00:00
|
|
|
private boolean mBusy;
|
2010-05-10 21:20:16 +00:00
|
|
|
private final Context mContext;
|
2010-08-18 15:50:45 +00:00
|
|
|
private final Controller mController;
|
2009-03-04 03:32:22 +00:00
|
|
|
|
2011-06-23 17:52:21 +00:00
|
|
|
/**
|
|
|
|
* Simple cache for last search result mailbox by account and serverId, since the most common
|
|
|
|
* case will be repeated use of the same mailbox
|
|
|
|
*/
|
|
|
|
private long mLastSearchAccountKey = Account.NO_ACCOUNT;
|
|
|
|
private String mLastSearchServerId = null;
|
|
|
|
private Mailbox mLastSearchRemoteMailbox = null;
|
|
|
|
|
2010-08-18 15:50:45 +00:00
|
|
|
protected MessagingController(Context _context, Controller _controller) {
|
2010-05-20 23:11:26 +00:00
|
|
|
mContext = _context.getApplicationContext();
|
2010-08-18 15:50:45 +00:00
|
|
|
mController = _controller;
|
2009-03-04 03:32:22 +00:00
|
|
|
mThread = new Thread(this);
|
|
|
|
mThread.start();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets or creates the singleton instance of MessagingController. Application is used to
|
|
|
|
* provide a Context to classes that need it.
|
|
|
|
*/
|
2010-08-18 15:50:45 +00:00
|
|
|
public synchronized static MessagingController getInstance(Context _context,
|
|
|
|
Controller _controller) {
|
2010-05-10 21:20:16 +00:00
|
|
|
if (sInstance == null) {
|
2010-08-18 15:50:45 +00:00
|
|
|
sInstance = new MessagingController(_context, _controller);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
2010-05-10 21:20:16 +00:00
|
|
|
return sInstance;
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
2009-06-22 23:13:03 +00:00
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
/**
|
|
|
|
* Inject a mock controller. Used only for testing. Affects future calls to getInstance().
|
|
|
|
*/
|
|
|
|
public static void injectMockController(MessagingController mockController) {
|
2010-05-10 21:20:16 +00:00
|
|
|
sInstance = mockController;
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
|
2009-06-27 02:54:06 +00:00
|
|
|
// TODO: seems that this reading of mBusy isn't thread-safe
|
2009-03-04 03:32:22 +00:00
|
|
|
public boolean isBusy() {
|
|
|
|
return mBusy;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void run() {
|
|
|
|
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
|
2009-06-27 02:54:06 +00:00
|
|
|
// TODO: add an end test to this infinite loop
|
2009-03-04 03:32:22 +00:00
|
|
|
while (true) {
|
2009-06-27 02:54:06 +00:00
|
|
|
Command command;
|
2009-03-04 03:32:22 +00:00
|
|
|
try {
|
2009-06-27 02:54:06 +00:00
|
|
|
command = mCommands.take();
|
|
|
|
} catch (InterruptedException e) {
|
|
|
|
continue; //re-test the condition on the eclosing while
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
2009-06-27 02:54:06 +00:00
|
|
|
if (command.listener == null || isActiveListener(command.listener)) {
|
|
|
|
mBusy = true;
|
|
|
|
command.runnable.run();
|
|
|
|
mListeners.controllerCommandCompleted(mCommands.size() > 0);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
mBusy = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void put(String description, MessagingListener listener, Runnable runnable) {
|
|
|
|
try {
|
|
|
|
Command command = new Command();
|
|
|
|
command.listener = listener;
|
|
|
|
command.runnable = runnable;
|
|
|
|
command.description = description;
|
2009-04-24 23:17:04 +00:00
|
|
|
mCommands.add(command);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
2009-04-24 23:17:04 +00:00
|
|
|
catch (IllegalStateException ie) {
|
2009-03-04 03:32:22 +00:00
|
|
|
throw new Error(ie);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void addListener(MessagingListener listener) {
|
2009-06-27 02:54:06 +00:00
|
|
|
mListeners.addListener(listener);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public void removeListener(MessagingListener listener) {
|
2009-06-27 02:54:06 +00:00
|
|
|
mListeners.removeListener(listener);
|
2009-04-24 23:17:04 +00:00
|
|
|
}
|
2009-06-22 23:13:03 +00:00
|
|
|
|
2009-04-24 23:17:04 +00:00
|
|
|
private boolean isActiveListener(MessagingListener listener) {
|
2009-06-27 02:54:06 +00:00
|
|
|
return mListeners.isActiveListener(listener);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
|
2011-04-20 23:22:41 +00:00
|
|
|
private static final int MAILBOX_COLUMN_ID = 0;
|
2011-04-22 22:45:11 +00:00
|
|
|
private static final int MAILBOX_COLUMN_SERVER_ID = 1;
|
2011-04-20 23:22:41 +00:00
|
|
|
private static final int MAILBOX_COLUMN_TYPE = 2;
|
2009-06-22 18:39:40 +00:00
|
|
|
|
2011-04-20 23:22:41 +00:00
|
|
|
/** Small projection for just the columns required for a sync. */
|
|
|
|
private static final String[] MAILBOX_PROJECTION = new String[] {
|
|
|
|
MailboxColumns.ID,
|
2011-04-22 22:45:11 +00:00
|
|
|
MailboxColumns.SERVER_ID,
|
2011-04-20 23:22:41 +00:00
|
|
|
MailboxColumns.TYPE,
|
|
|
|
};
|
2009-06-22 18:39:40 +00:00
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
/**
|
2011-04-20 23:22:41 +00:00
|
|
|
* Synchronize the folder list with the remote server. Synchronization occurs in the
|
|
|
|
* background and results are passed through the {@link MessagingListener}. If the
|
|
|
|
* given listener is not {@code null}, it must have been previously added to the set
|
|
|
|
* of listeners using the {@link #addListener(MessagingListener)}. Otherwise, no
|
|
|
|
* actions will be performed.
|
2009-06-17 23:15:37 +00:00
|
|
|
*
|
2009-04-27 16:18:01 +00:00
|
|
|
* TODO this needs to cache the remote folder list
|
|
|
|
* TODO break out an inner listFoldersSynchronized which could simplify checkMail
|
2009-03-04 03:32:22 +00:00
|
|
|
*
|
2011-04-20 15:04:46 +00:00
|
|
|
* @param accountId ID of the account for which to list the folders
|
|
|
|
* @param listener A listener to notify
|
2009-03-04 03:32:22 +00:00
|
|
|
*/
|
2011-04-20 15:04:46 +00:00
|
|
|
void listFolders(final long accountId, MessagingListener listener) {
|
2011-04-20 23:22:41 +00:00
|
|
|
final Account account = Account.restoreAccountWithId(mContext, accountId);
|
2009-09-16 17:58:54 +00:00
|
|
|
if (account == null) {
|
2011-04-20 23:22:41 +00:00
|
|
|
Log.i(Logging.LOG_TAG, "Could not load account id " + accountId
|
|
|
|
+ ". Has it been removed?");
|
2009-09-16 17:58:54 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
mListeners.listFoldersStarted(accountId);
|
2009-06-17 23:15:37 +00:00
|
|
|
put("listFolders", listener, new Runnable() {
|
2011-04-20 23:22:41 +00:00
|
|
|
// TODO For now, mailbox addition occurs in the server-dependent store implementation,
|
|
|
|
// but, mailbox removal occurs here. Instead, each store should be responsible for
|
|
|
|
// content synchronization (addition AND removal) since each store will likely need
|
|
|
|
// to implement it's own, unique synchronization methodology.
|
2009-06-17 23:15:37 +00:00
|
|
|
public void run() {
|
2009-06-22 18:39:40 +00:00
|
|
|
Cursor localFolderCursor = null;
|
2009-06-17 23:15:37 +00:00
|
|
|
try {
|
2011-04-20 23:22:41 +00:00
|
|
|
// Step 1: Get remote mailboxes
|
2011-06-29 19:47:56 +00:00
|
|
|
Store store = Store.getInstance(account, mContext);
|
2011-04-20 23:22:41 +00:00
|
|
|
Folder[] remoteFolders = store.updateFolders();
|
2009-06-17 23:15:37 +00:00
|
|
|
HashSet<String> remoteFolderNames = new HashSet<String>();
|
|
|
|
for (int i = 0, count = remoteFolders.length; i < count; i++) {
|
|
|
|
remoteFolderNames.add(remoteFolders[i].getName());
|
|
|
|
}
|
2009-08-27 22:58:44 +00:00
|
|
|
|
2011-04-20 23:22:41 +00:00
|
|
|
// Step 2: Get local mailboxes
|
2009-06-22 18:39:40 +00:00
|
|
|
localFolderCursor = mContext.getContentResolver().query(
|
2011-05-14 00:26:27 +00:00
|
|
|
Mailbox.CONTENT_URI,
|
2011-04-20 23:22:41 +00:00
|
|
|
MAILBOX_PROJECTION,
|
2009-06-17 23:15:37 +00:00
|
|
|
EmailContent.MailboxColumns.ACCOUNT_KEY + "=?",
|
2009-06-22 23:13:03 +00:00
|
|
|
new String[] { String.valueOf(account.mId) },
|
2009-06-17 23:15:37 +00:00
|
|
|
null);
|
2009-06-22 23:13:03 +00:00
|
|
|
|
2011-04-20 23:22:41 +00:00
|
|
|
// Step 3: Remove any local mailbox not on the remote list
|
|
|
|
while (localFolderCursor.moveToNext()) {
|
2011-04-22 22:45:11 +00:00
|
|
|
String mailboxPath = localFolderCursor.getString(MAILBOX_COLUMN_SERVER_ID);
|
2011-04-20 23:22:41 +00:00
|
|
|
// Short circuit if we have a remote mailbox with the same name
|
|
|
|
if (remoteFolderNames.contains(mailboxPath)) {
|
|
|
|
continue;
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
2009-06-22 23:13:03 +00:00
|
|
|
|
2011-04-20 23:22:41 +00:00
|
|
|
int mailboxType = localFolderCursor.getInt(MAILBOX_COLUMN_TYPE);
|
|
|
|
long mailboxId = localFolderCursor.getLong(MAILBOX_COLUMN_ID);
|
|
|
|
switch (mailboxType) {
|
|
|
|
case Mailbox.TYPE_INBOX:
|
|
|
|
case Mailbox.TYPE_DRAFTS:
|
|
|
|
case Mailbox.TYPE_OUTBOX:
|
|
|
|
case Mailbox.TYPE_SENT:
|
|
|
|
case Mailbox.TYPE_TRASH:
|
2011-06-23 17:52:21 +00:00
|
|
|
case Mailbox.TYPE_SEARCH:
|
2011-04-20 23:22:41 +00:00
|
|
|
// Never, ever delete special mailboxes
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
// Drop all attachment files related to this mailbox
|
|
|
|
AttachmentUtilities.deleteAllMailboxAttachmentFiles(
|
|
|
|
mContext, accountId, mailboxId);
|
|
|
|
// Delete the mailbox; database triggers take care of related
|
|
|
|
// Message, Body and Attachment records
|
|
|
|
Uri uri = ContentUris.withAppendedId(
|
|
|
|
Mailbox.CONTENT_URI, mailboxId);
|
|
|
|
mContext.getContentResolver().delete(uri, null, null);
|
|
|
|
break;
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
}
|
2009-09-16 17:58:54 +00:00
|
|
|
mListeners.listFoldersFinished(accountId);
|
2009-06-17 23:15:37 +00:00
|
|
|
} catch (Exception e) {
|
2011-04-20 23:22:41 +00:00
|
|
|
mListeners.listFoldersFailed(accountId, e.toString());
|
2009-06-17 23:15:37 +00:00
|
|
|
} finally {
|
2009-06-22 18:39:40 +00:00
|
|
|
if (localFolderCursor != null) {
|
|
|
|
localFolderCursor.close();
|
2009-06-17 23:15:37 +00:00
|
|
|
}
|
2009-04-24 23:17:04 +00:00
|
|
|
}
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
2009-06-17 23:15:37 +00:00
|
|
|
});
|
|
|
|
}
|
2009-06-22 23:13:03 +00:00
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
/**
|
|
|
|
* Start background synchronization of the specified folder.
|
|
|
|
* @param account
|
|
|
|
* @param folder
|
|
|
|
* @param listener
|
|
|
|
*/
|
2011-06-13 22:32:27 +00:00
|
|
|
public void synchronizeMailbox(final Account account,
|
2011-05-14 00:26:27 +00:00
|
|
|
final Mailbox folder, MessagingListener listener) {
|
2009-03-04 03:32:22 +00:00
|
|
|
/*
|
|
|
|
* We don't ever sync the Outbox.
|
|
|
|
*/
|
2011-05-14 00:26:27 +00:00
|
|
|
if (folder.mType == Mailbox.TYPE_OUTBOX) {
|
2009-03-04 03:32:22 +00:00
|
|
|
return;
|
|
|
|
}
|
2009-09-16 17:58:54 +00:00
|
|
|
mListeners.synchronizeMailboxStarted(account.mId, folder.mId);
|
2009-03-04 03:32:22 +00:00
|
|
|
put("synchronizeMailbox", listener, new Runnable() {
|
|
|
|
public void run() {
|
2009-04-24 20:25:34 +00:00
|
|
|
synchronizeMailboxSynchronous(account, folder);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2009-04-24 20:25:34 +00:00
|
|
|
* Start foreground synchronization of the specified folder. This is called by
|
|
|
|
* synchronizeMailbox or checkMail.
|
2009-08-18 07:54:34 +00:00
|
|
|
* TODO this should use ID's instead of fully-restored objects
|
2009-03-04 03:32:22 +00:00
|
|
|
* @param account
|
|
|
|
* @param folder
|
|
|
|
*/
|
2011-06-13 22:32:27 +00:00
|
|
|
private void synchronizeMailboxSynchronous(final Account account,
|
2011-05-14 00:26:27 +00:00
|
|
|
final Mailbox folder) {
|
2009-09-16 17:58:54 +00:00
|
|
|
mListeners.synchronizeMailboxStarted(account.mId, folder.mId);
|
2011-04-26 21:51:29 +00:00
|
|
|
if ((folder.mFlags & Mailbox.FLAG_HOLDS_MAIL) == 0) {
|
|
|
|
// We don't hold messages, so, nothing to synchronize
|
2011-05-03 21:42:26 +00:00
|
|
|
mListeners.synchronizeMailboxFinished(account.mId, folder.mId, 0, 0, null);
|
2011-04-26 21:51:29 +00:00
|
|
|
return;
|
|
|
|
}
|
2010-10-18 20:14:20 +00:00
|
|
|
NotificationController nc = NotificationController.getInstance(mContext);
|
2009-03-04 03:32:22 +00:00
|
|
|
try {
|
2009-09-10 23:01:48 +00:00
|
|
|
processPendingActionsSynchronous(account);
|
2009-03-04 03:32:22 +00:00
|
|
|
|
2009-04-24 20:25:34 +00:00
|
|
|
// Select generic sync or store-specific sync
|
2011-05-03 21:42:26 +00:00
|
|
|
SyncResults results = synchronizeMailboxGeneric(account, folder);
|
2009-09-16 17:58:54 +00:00
|
|
|
mListeners.synchronizeMailboxFinished(account.mId, folder.mId,
|
2009-08-27 22:58:44 +00:00
|
|
|
results.mTotalMessages,
|
2011-05-03 21:42:26 +00:00
|
|
|
results.mAddedMessages.size(),
|
|
|
|
results.mAddedMessages);
|
2010-10-18 20:14:20 +00:00
|
|
|
// Clear authentication notification for this account
|
|
|
|
nc.cancelLoginFailedNotification(account.mId);
|
2009-06-27 02:54:06 +00:00
|
|
|
} catch (MessagingException e) {
|
2011-05-13 18:20:04 +00:00
|
|
|
if (Logging.LOGD) {
|
2011-02-11 23:05:17 +00:00
|
|
|
Log.v(Logging.LOG_TAG, "synchronizeMailbox", e);
|
2009-04-24 20:25:34 +00:00
|
|
|
}
|
2010-10-18 20:14:20 +00:00
|
|
|
if (e instanceof AuthenticationFailedException) {
|
|
|
|
// Generate authentication notification
|
|
|
|
nc.showLoginFailedNotification(account.mId);
|
|
|
|
}
|
2009-09-16 17:58:54 +00:00
|
|
|
mListeners.synchronizeMailboxFailed(account.mId, folder.mId, e);
|
2009-04-24 20:25:34 +00:00
|
|
|
}
|
|
|
|
}
|
2009-06-22 23:13:03 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Lightweight record for the first pass of message sync, where I'm just seeing if
|
|
|
|
* the local message requires sync. Later (for messages that need syncing) we'll do a full
|
|
|
|
* readout from the DB.
|
|
|
|
*/
|
|
|
|
private static class LocalMessageInfo {
|
|
|
|
private static final int COLUMN_ID = 0;
|
|
|
|
private static final int COLUMN_FLAG_READ = 1;
|
2009-08-26 05:45:11 +00:00
|
|
|
private static final int COLUMN_FLAG_FAVORITE = 2;
|
|
|
|
private static final int COLUMN_FLAG_LOADED = 3;
|
|
|
|
private static final int COLUMN_SERVER_ID = 4;
|
2009-06-22 23:13:03 +00:00
|
|
|
private static final String[] PROJECTION = new String[] {
|
|
|
|
EmailContent.RECORD_ID,
|
2009-08-26 05:45:11 +00:00
|
|
|
MessageColumns.FLAG_READ, MessageColumns.FLAG_FAVORITE, MessageColumns.FLAG_LOADED,
|
2009-06-22 23:13:03 +00:00
|
|
|
SyncColumns.SERVER_ID, MessageColumns.MAILBOX_KEY, MessageColumns.ACCOUNT_KEY
|
|
|
|
};
|
2009-08-27 22:58:44 +00:00
|
|
|
|
2010-05-10 21:20:16 +00:00
|
|
|
final long mId;
|
|
|
|
final boolean mFlagRead;
|
|
|
|
final boolean mFlagFavorite;
|
|
|
|
final int mFlagLoaded;
|
|
|
|
final String mServerId;
|
2009-08-27 22:58:44 +00:00
|
|
|
|
2009-06-22 23:13:03 +00:00
|
|
|
public LocalMessageInfo(Cursor c) {
|
|
|
|
mId = c.getLong(COLUMN_ID);
|
|
|
|
mFlagRead = c.getInt(COLUMN_FLAG_READ) != 0;
|
2009-08-26 05:45:11 +00:00
|
|
|
mFlagFavorite = c.getInt(COLUMN_FLAG_FAVORITE) != 0;
|
2009-06-22 23:13:03 +00:00
|
|
|
mFlagLoaded = c.getInt(COLUMN_FLAG_LOADED);
|
|
|
|
mServerId = c.getString(COLUMN_SERVER_ID);
|
|
|
|
// Note: mailbox key and account key not needed - they are projected for the SELECT
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-05-04 22:33:08 +00:00
|
|
|
private void saveOrUpdate(EmailContent content, Context context) {
|
2009-07-22 22:13:30 +00:00
|
|
|
if (content.isSaved()) {
|
2010-05-04 22:33:08 +00:00
|
|
|
content.update(context, content.toContentValues());
|
2009-07-22 22:13:30 +00:00
|
|
|
} else {
|
2010-05-04 22:33:08 +00:00
|
|
|
content.save(context);
|
2009-07-22 22:13:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-06-13 21:57:17 +00:00
|
|
|
/**
|
|
|
|
* Load the structure and body of messages not yet synced
|
|
|
|
* @param account the account we're syncing
|
|
|
|
* @param remoteFolder the (open) Folder we're working on
|
|
|
|
* @param unsyncedMessages an array of Message's we've got headers for
|
|
|
|
* @param toMailbox the destination mailbox we're syncing
|
|
|
|
* @throws MessagingException
|
|
|
|
*/
|
|
|
|
void loadUnsyncedMessages(final Account account, Folder remoteFolder,
|
|
|
|
ArrayList<Message> unsyncedMessages, final Mailbox toMailbox)
|
|
|
|
throws MessagingException {
|
|
|
|
|
|
|
|
// 1. Divide the unsynced messages into small & large (by size)
|
|
|
|
|
|
|
|
// TODO doing this work here (synchronously) is problematic because it prevents the UI
|
|
|
|
// from affecting the order (e.g. download a message because the user requested it.) Much
|
|
|
|
// of this logic should move out to a different sync loop that attempts to update small
|
|
|
|
// groups of messages at a time, as a background task. However, we can't just return
|
|
|
|
// (yet) because POP messages don't have an envelope yet....
|
|
|
|
|
|
|
|
ArrayList<Message> largeMessages = new ArrayList<Message>();
|
|
|
|
ArrayList<Message> smallMessages = new ArrayList<Message>();
|
|
|
|
for (Message message : unsyncedMessages) {
|
|
|
|
if (message.getSize() > (MAX_SMALL_MESSAGE_SIZE)) {
|
|
|
|
largeMessages.add(message);
|
|
|
|
} else {
|
|
|
|
smallMessages.add(message);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 2. Download small messages
|
|
|
|
|
|
|
|
// TODO Problems with this implementation. 1. For IMAP, where we get a real envelope,
|
|
|
|
// this is going to be inefficient and duplicate work we've already done. 2. It's going
|
|
|
|
// back to the DB for a local message that we already had (and discarded).
|
|
|
|
|
|
|
|
// For small messages, we specify "body", which returns everything (incl. attachments)
|
|
|
|
FetchProfile fp = new FetchProfile();
|
|
|
|
fp.add(FetchProfile.Item.BODY);
|
|
|
|
remoteFolder.fetch(smallMessages.toArray(new Message[smallMessages.size()]), fp,
|
|
|
|
new MessageRetrievalListener() {
|
|
|
|
public void messageRetrieved(Message message) {
|
|
|
|
// Store the updated message locally and mark it fully loaded
|
|
|
|
copyOneMessageToProvider(message, account, toMailbox,
|
|
|
|
EmailContent.Message.FLAG_LOADED_COMPLETE);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void loadAttachmentProgress(int progress) {
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// 3. Download large messages. We ask the server to give us the message structure,
|
|
|
|
// but not all of the attachments.
|
|
|
|
fp.clear();
|
|
|
|
fp.add(FetchProfile.Item.STRUCTURE);
|
|
|
|
remoteFolder.fetch(largeMessages.toArray(new Message[largeMessages.size()]), fp, null);
|
|
|
|
for (Message message : largeMessages) {
|
|
|
|
if (message.getBody() == null) {
|
|
|
|
// POP doesn't support STRUCTURE mode, so we'll just do a partial download
|
|
|
|
// (hopefully enough to see some/all of the body) and mark the message for
|
|
|
|
// further download.
|
|
|
|
fp.clear();
|
|
|
|
fp.add(FetchProfile.Item.BODY_SANE);
|
|
|
|
// TODO a good optimization here would be to make sure that all Stores set
|
|
|
|
// the proper size after this fetch and compare the before and after size. If
|
|
|
|
// they equal we can mark this SYNCHRONIZED instead of PARTIALLY_SYNCHRONIZED
|
|
|
|
remoteFolder.fetch(new Message[] { message }, fp, null);
|
|
|
|
|
|
|
|
// Store the partially-loaded message and mark it partially loaded
|
|
|
|
copyOneMessageToProvider(message, account, toMailbox,
|
|
|
|
EmailContent.Message.FLAG_LOADED_PARTIAL);
|
|
|
|
} else {
|
|
|
|
// We have a structure to deal with, from which
|
|
|
|
// we can pull down the parts we want to actually store.
|
|
|
|
// Build a list of parts we are interested in. Text parts will be downloaded
|
|
|
|
// right now, attachments will be left for later.
|
|
|
|
ArrayList<Part> viewables = new ArrayList<Part>();
|
|
|
|
ArrayList<Part> attachments = new ArrayList<Part>();
|
|
|
|
MimeUtility.collectParts(message, viewables, attachments);
|
|
|
|
// Download the viewables immediately
|
|
|
|
for (Part part : viewables) {
|
|
|
|
fp.clear();
|
|
|
|
fp.add(part);
|
|
|
|
// TODO what happens if the network connection dies? We've got partial
|
|
|
|
// messages with incorrect status stored.
|
|
|
|
remoteFolder.fetch(new Message[] { message }, fp, null);
|
|
|
|
}
|
|
|
|
// Store the updated message locally and mark it fully loaded
|
|
|
|
copyOneMessageToProvider(message, account, toMailbox,
|
|
|
|
EmailContent.Message.FLAG_LOADED_COMPLETE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
public void downloadFlagAndEnvelope(final Account account, final Mailbox mailbox,
|
|
|
|
Folder remoteFolder, ArrayList<Message> unsyncedMessages,
|
|
|
|
HashMap<String, LocalMessageInfo> localMessageMap, final ArrayList<Long> unseenMessages)
|
|
|
|
throws MessagingException {
|
|
|
|
FetchProfile fp = new FetchProfile();
|
|
|
|
fp.add(FetchProfile.Item.FLAGS);
|
|
|
|
fp.add(FetchProfile.Item.ENVELOPE);
|
|
|
|
|
|
|
|
final HashMap<String, LocalMessageInfo> localMapCopy;
|
|
|
|
if (localMessageMap != null)
|
|
|
|
localMapCopy = new HashMap<String, LocalMessageInfo>(localMessageMap);
|
|
|
|
else {
|
|
|
|
localMapCopy = new HashMap<String, LocalMessageInfo>();
|
|
|
|
}
|
|
|
|
|
|
|
|
remoteFolder.fetch(unsyncedMessages.toArray(new Message[0]), fp,
|
|
|
|
new MessageRetrievalListener() {
|
|
|
|
@Override
|
|
|
|
public void messageRetrieved(Message message) {
|
|
|
|
try {
|
|
|
|
// Determine if the new message was already known (e.g. partial)
|
|
|
|
// And create or reload the full message info
|
|
|
|
LocalMessageInfo localMessageInfo =
|
|
|
|
localMapCopy.get(message.getUid());
|
|
|
|
EmailContent.Message localMessage = null;
|
|
|
|
if (localMessageInfo == null) {
|
|
|
|
localMessage = new EmailContent.Message();
|
|
|
|
} else {
|
|
|
|
localMessage = EmailContent.Message.restoreMessageWithId(
|
|
|
|
mContext, localMessageInfo.mId);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (localMessage != null) {
|
|
|
|
try {
|
|
|
|
// Copy the fields that are available into the message
|
|
|
|
LegacyConversions.updateMessageFields(localMessage,
|
|
|
|
message, account.mId, mailbox.mId);
|
|
|
|
// Commit the message to the local store
|
|
|
|
saveOrUpdate(localMessage, mContext);
|
|
|
|
// Track the "new" ness of the downloaded message
|
|
|
|
if (!message.isSet(Flag.SEEN) && unseenMessages != null) {
|
|
|
|
unseenMessages.add(localMessage.mId);
|
|
|
|
}
|
|
|
|
} catch (MessagingException me) {
|
|
|
|
Log.e(Logging.LOG_TAG,
|
|
|
|
"Error while copying downloaded message." + me);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (Exception e) {
|
|
|
|
Log.e(Logging.LOG_TAG,
|
|
|
|
"Error while storing downloaded message." + e.toString());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void loadAttachmentProgress(int progress) {
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A message and numeric uid that's easily sortable
|
|
|
|
*/
|
|
|
|
private static class SortableMessage {
|
|
|
|
private final Message mMessage;
|
|
|
|
private final long mUid;
|
|
|
|
|
|
|
|
SortableMessage(Message message, long uid) {
|
|
|
|
mMessage = message;
|
|
|
|
mUid = uid;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-06-28 01:16:04 +00:00
|
|
|
public int searchMailbox(long accountId, SearchParams searchParams, long destMailboxId)
|
2011-06-13 21:57:17 +00:00
|
|
|
throws MessagingException {
|
2011-06-17 18:43:32 +00:00
|
|
|
try {
|
2011-06-28 01:16:04 +00:00
|
|
|
return searchMailboxImpl(accountId, searchParams, destMailboxId);
|
2011-06-17 18:43:32 +00:00
|
|
|
} finally {
|
|
|
|
// Tell UI that we're done loading any search results (no harm calling this even if we
|
|
|
|
// encountered an error or never sent a "started" message)
|
|
|
|
mListeners.synchronizeMailboxFinished(accountId, destMailboxId, 0, 0, null);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-06-28 01:16:04 +00:00
|
|
|
private int searchMailboxImpl(long accountId, SearchParams searchParams,
|
2011-06-17 18:43:32 +00:00
|
|
|
final long destMailboxId) throws MessagingException {
|
2011-06-13 21:57:17 +00:00
|
|
|
final Account account = Account.restoreAccountWithId(mContext, accountId);
|
|
|
|
final Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, searchParams.mMailboxId);
|
|
|
|
final Mailbox destMailbox = Mailbox.restoreMailboxWithId(mContext, destMailboxId);
|
2011-06-17 18:55:35 +00:00
|
|
|
if (account == null || mailbox == null || destMailbox == null) {
|
|
|
|
Log.d(Logging.LOG_TAG, "Attempted search for " + searchParams
|
|
|
|
+ " but account or mailbox information was missing");
|
2011-06-28 01:16:04 +00:00
|
|
|
return 0;
|
2011-06-17 18:55:35 +00:00
|
|
|
}
|
2011-06-13 21:57:17 +00:00
|
|
|
|
2011-06-17 18:43:32 +00:00
|
|
|
// Tell UI that we're loading messages
|
|
|
|
mListeners.synchronizeMailboxStarted(accountId, destMailbox.mId);
|
|
|
|
|
2011-06-29 19:47:56 +00:00
|
|
|
Store remoteStore = Store.getInstance(account, mContext);
|
2011-06-13 21:57:17 +00:00
|
|
|
Folder remoteFolder = remoteStore.getFolder(mailbox.mServerId);
|
2011-06-29 21:00:43 +00:00
|
|
|
remoteFolder.open(OpenMode.READ_WRITE);
|
2011-06-13 21:57:17 +00:00
|
|
|
|
2011-06-17 18:43:32 +00:00
|
|
|
SortableMessage[] sortableMessages = new SortableMessage[0];
|
|
|
|
if (searchParams.mOffset == 0) {
|
|
|
|
// Get the "bare" messages (basically uid)
|
|
|
|
Message[] remoteMessages = remoteFolder.getMessages(searchParams, null);
|
|
|
|
int remoteCount = remoteMessages.length;
|
|
|
|
if (remoteCount > 0) {
|
|
|
|
sortableMessages = new SortableMessage[remoteCount];
|
|
|
|
int i = 0;
|
|
|
|
for (Message msg : remoteMessages) {
|
|
|
|
sortableMessages[i++] = new SortableMessage(msg, Long.parseLong(msg.getUid()));
|
2011-06-13 21:57:17 +00:00
|
|
|
}
|
2011-06-17 18:43:32 +00:00
|
|
|
// Sort the uid's, most recent first
|
|
|
|
// Note: Not all servers will be nice and return results in the order of request;
|
|
|
|
// those that do will see messages arrive from newest to oldest
|
|
|
|
Arrays.sort(sortableMessages, new Comparator<SortableMessage>() {
|
|
|
|
@Override
|
|
|
|
public int compare(SortableMessage lhs, SortableMessage rhs) {
|
|
|
|
return lhs.mUid > rhs.mUid ? -1 : lhs.mUid < rhs.mUid ? 1 : 0;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
sSearchResults.put(accountId, sortableMessages);
|
2011-06-13 21:57:17 +00:00
|
|
|
}
|
2011-06-17 18:43:32 +00:00
|
|
|
} else {
|
|
|
|
sortableMessages = sSearchResults.get(accountId);
|
|
|
|
}
|
2011-06-13 21:57:17 +00:00
|
|
|
|
2011-06-28 01:16:04 +00:00
|
|
|
final int numSearchResults = sortableMessages.length;
|
|
|
|
final int numToLoad =
|
|
|
|
Math.min(numSearchResults - searchParams.mOffset, searchParams.mLimit);
|
2011-06-17 18:43:32 +00:00
|
|
|
if (numToLoad <= 0) {
|
2011-06-28 01:16:04 +00:00
|
|
|
return 0;
|
2011-06-13 21:57:17 +00:00
|
|
|
}
|
2011-06-17 18:43:32 +00:00
|
|
|
|
|
|
|
final ArrayList<Message> messageList = new ArrayList<Message>();
|
|
|
|
for (int i = searchParams.mOffset; i < numToLoad + searchParams.mOffset; i++) {
|
|
|
|
messageList.add(sortableMessages[i].mMessage);
|
2011-06-13 21:57:17 +00:00
|
|
|
}
|
2011-06-17 18:43:32 +00:00
|
|
|
// Get everything in one pass, rather than two (as in sync); this starts getting us
|
|
|
|
// usable results quickly.
|
|
|
|
FetchProfile fp = new FetchProfile();
|
|
|
|
fp.add(FetchProfile.Item.FLAGS);
|
|
|
|
fp.add(FetchProfile.Item.ENVELOPE);
|
|
|
|
fp.add(FetchProfile.Item.STRUCTURE);
|
|
|
|
fp.add(FetchProfile.Item.BODY_SANE);
|
|
|
|
remoteFolder.fetch(messageList.toArray(new Message[0]), fp,
|
|
|
|
new MessageRetrievalListener() {
|
|
|
|
public void messageRetrieved(Message message) {
|
|
|
|
try {
|
|
|
|
// Determine if the new message was already known (e.g. partial)
|
|
|
|
// And create or reload the full message info
|
|
|
|
EmailContent.Message localMessage = new EmailContent.Message();
|
|
|
|
try {
|
|
|
|
// Copy the fields that are available into the message
|
|
|
|
LegacyConversions.updateMessageFields(localMessage,
|
|
|
|
message, account.mId, mailbox.mId);
|
|
|
|
// Commit the message to the local store
|
|
|
|
saveOrUpdate(localMessage, mContext);
|
|
|
|
localMessage.mMailboxKey = destMailboxId;
|
|
|
|
// We load 50k or so; maybe it's complete, maybe not...
|
|
|
|
int flag = EmailContent.Message.FLAG_LOADED_COMPLETE;
|
2011-06-23 17:52:21 +00:00
|
|
|
// We store the serverId of the source mailbox into protocolSearchInfo
|
|
|
|
// This will be used by loadMessageForView, etc. to use the proper remote
|
|
|
|
// folder
|
|
|
|
localMessage.mProtocolSearchInfo = mailbox.mServerId;
|
2011-06-17 18:43:32 +00:00
|
|
|
if (message.getSize() > Store.FETCH_BODY_SANE_SUGGESTED_SIZE) {
|
|
|
|
flag = EmailContent.Message.FLAG_LOADED_PARTIAL;
|
|
|
|
}
|
|
|
|
copyOneMessageToProvider(message, localMessage, flag, mContext);
|
|
|
|
} catch (MessagingException me) {
|
|
|
|
Log.e(Logging.LOG_TAG,
|
|
|
|
"Error while copying downloaded message." + me);
|
|
|
|
}
|
|
|
|
} catch (Exception e) {
|
|
|
|
Log.e(Logging.LOG_TAG,
|
|
|
|
"Error while storing downloaded message." + e.toString());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void loadAttachmentProgress(int progress) {
|
|
|
|
}
|
|
|
|
});
|
2011-06-28 01:16:04 +00:00
|
|
|
return numSearchResults;
|
2011-06-17 18:43:32 +00:00
|
|
|
}
|
|
|
|
|
2011-06-13 21:57:17 +00:00
|
|
|
|
2009-04-24 20:25:34 +00:00
|
|
|
/**
|
|
|
|
* Generic synchronizer - used for POP3 and IMAP.
|
|
|
|
*
|
|
|
|
* TODO Break this method up into smaller chunks.
|
|
|
|
*
|
2009-06-22 23:13:03 +00:00
|
|
|
* @param account the account to sync
|
2011-06-13 21:57:17 +00:00
|
|
|
* @param mailbox the mailbox to sync
|
2009-06-22 23:13:03 +00:00
|
|
|
* @return results of the sync pass
|
2009-04-24 20:25:34 +00:00
|
|
|
* @throws MessagingException
|
|
|
|
*/
|
2011-06-13 21:57:17 +00:00
|
|
|
private SyncResults synchronizeMailboxGeneric(final Account account, final Mailbox mailbox)
|
2009-06-22 23:13:03 +00:00
|
|
|
throws MessagingException {
|
|
|
|
|
2011-05-03 21:42:26 +00:00
|
|
|
/*
|
|
|
|
* A list of IDs for messages that were downloaded and did not have the seen flag set.
|
|
|
|
* This serves as the "true" new message count reported to the user via notification.
|
|
|
|
*/
|
|
|
|
final ArrayList<Long> unseenMessages = new ArrayList<Long>();
|
|
|
|
|
2011-02-11 23:05:17 +00:00
|
|
|
Log.d(Logging.LOG_TAG, "*** synchronizeMailboxGeneric ***");
|
2009-09-10 18:52:36 +00:00
|
|
|
ContentResolver resolver = mContext.getContentResolver();
|
2009-08-27 22:58:44 +00:00
|
|
|
|
2009-09-25 21:54:32 +00:00
|
|
|
// 0. We do not ever sync DRAFTS or OUTBOX (down or up)
|
2011-06-13 21:57:17 +00:00
|
|
|
if (mailbox.mType == Mailbox.TYPE_DRAFTS || mailbox.mType == Mailbox.TYPE_OUTBOX) {
|
|
|
|
int totalMessages = EmailContent.count(mContext, mailbox.getUri(), null, null);
|
2011-05-03 21:42:26 +00:00
|
|
|
return new SyncResults(totalMessages, unseenMessages);
|
2009-09-25 21:54:32 +00:00
|
|
|
}
|
|
|
|
|
2009-06-22 23:13:03 +00:00
|
|
|
// 1. Get the message list from the local store and create an index of the uids
|
2009-08-27 22:58:44 +00:00
|
|
|
|
2009-06-22 23:13:03 +00:00
|
|
|
Cursor localUidCursor = null;
|
|
|
|
HashMap<String, LocalMessageInfo> localMessageMap = new HashMap<String, LocalMessageInfo>();
|
2009-08-27 22:58:44 +00:00
|
|
|
|
2009-06-22 23:13:03 +00:00
|
|
|
try {
|
2009-09-10 18:52:36 +00:00
|
|
|
localUidCursor = resolver.query(
|
2009-06-22 23:13:03 +00:00
|
|
|
EmailContent.Message.CONTENT_URI,
|
|
|
|
LocalMessageInfo.PROJECTION,
|
|
|
|
EmailContent.MessageColumns.ACCOUNT_KEY + "=?" +
|
|
|
|
" AND " + MessageColumns.MAILBOX_KEY + "=?",
|
|
|
|
new String[] {
|
|
|
|
String.valueOf(account.mId),
|
2011-06-13 21:57:17 +00:00
|
|
|
String.valueOf(mailbox.mId)
|
2009-06-22 23:13:03 +00:00
|
|
|
},
|
|
|
|
null);
|
|
|
|
while (localUidCursor.moveToNext()) {
|
|
|
|
LocalMessageInfo info = new LocalMessageInfo(localUidCursor);
|
|
|
|
localMessageMap.put(info.mServerId, info);
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
if (localUidCursor != null) {
|
|
|
|
localUidCursor.close();
|
2009-05-20 17:36:16 +00:00
|
|
|
}
|
2009-04-24 20:25:34 +00:00
|
|
|
}
|
2009-08-27 22:58:44 +00:00
|
|
|
|
2009-06-22 23:13:03 +00:00
|
|
|
// 2. Open the remote folder and create the remote folder if necessary
|
2009-08-27 22:58:44 +00:00
|
|
|
|
2011-06-29 19:47:56 +00:00
|
|
|
Store remoteStore = Store.getInstance(account, mContext);
|
2011-06-13 21:57:17 +00:00
|
|
|
Folder remoteFolder = remoteStore.getFolder(mailbox.mServerId);
|
2009-03-04 03:32:22 +00:00
|
|
|
|
2009-04-24 20:25:34 +00:00
|
|
|
/*
|
|
|
|
* If the folder is a "special" folder we need to see if it exists
|
|
|
|
* on the remote server. It if does not exist we'll try to create it. If we
|
|
|
|
* can't create we'll abort. This will happen on every single Pop3 folder as
|
|
|
|
* designed and on Imap folders during error conditions. This allows us
|
|
|
|
* to treat Pop3 and Imap the same in this code.
|
|
|
|
*/
|
2011-06-13 21:57:17 +00:00
|
|
|
if (mailbox.mType == Mailbox.TYPE_TRASH || mailbox.mType == Mailbox.TYPE_SENT
|
|
|
|
|| mailbox.mType == Mailbox.TYPE_DRAFTS) {
|
2009-04-24 20:25:34 +00:00
|
|
|
if (!remoteFolder.exists()) {
|
|
|
|
if (!remoteFolder.create(FolderType.HOLDS_MESSAGES)) {
|
2011-05-03 21:42:26 +00:00
|
|
|
return new SyncResults(0, unseenMessages);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
}
|
2009-04-24 20:25:34 +00:00
|
|
|
}
|
2009-08-27 22:58:44 +00:00
|
|
|
|
2009-06-22 23:13:03 +00:00
|
|
|
// 3, Open the remote folder. This pre-loads certain metadata like message count.
|
2011-06-29 21:00:43 +00:00
|
|
|
remoteFolder.open(OpenMode.READ_WRITE);
|
2009-03-04 03:32:22 +00:00
|
|
|
|
2009-06-22 23:13:03 +00:00
|
|
|
// 4. Trash any remote messages that are marked as trashed locally.
|
|
|
|
// TODO - this comment was here, but no code was here.
|
2009-03-04 03:32:22 +00:00
|
|
|
|
2009-06-22 23:13:03 +00:00
|
|
|
// 5. Get the remote message count.
|
2009-04-24 20:25:34 +00:00
|
|
|
int remoteMessageCount = remoteFolder.getMessageCount();
|
2009-03-04 03:32:22 +00:00
|
|
|
|
2009-06-22 23:13:03 +00:00
|
|
|
// 6. Determine the limit # of messages to download
|
2011-06-13 21:57:17 +00:00
|
|
|
int visibleLimit = mailbox.mVisibleLimit;
|
2009-04-24 20:25:34 +00:00
|
|
|
if (visibleLimit <= 0) {
|
2011-06-08 20:50:00 +00:00
|
|
|
visibleLimit = Email.VISIBLE_LIMIT_DEFAULT;
|
2009-04-24 20:25:34 +00:00
|
|
|
}
|
2009-03-04 03:32:22 +00:00
|
|
|
|
2009-06-22 23:13:03 +00:00
|
|
|
// 7. Create a list of messages to download
|
2009-04-24 20:25:34 +00:00
|
|
|
Message[] remoteMessages = new Message[0];
|
|
|
|
final ArrayList<Message> unsyncedMessages = new ArrayList<Message>();
|
|
|
|
HashMap<String, Message> remoteUidMap = new HashMap<String, Message>();
|
2009-03-04 03:32:22 +00:00
|
|
|
|
2009-05-20 17:36:16 +00:00
|
|
|
int newMessageCount = 0;
|
2009-04-24 20:25:34 +00:00
|
|
|
if (remoteMessageCount > 0) {
|
2009-03-04 03:32:22 +00:00
|
|
|
/*
|
2009-04-24 20:25:34 +00:00
|
|
|
* Message numbers start at 1.
|
2009-03-04 03:32:22 +00:00
|
|
|
*/
|
2009-04-24 20:25:34 +00:00
|
|
|
int remoteStart = Math.max(0, remoteMessageCount - visibleLimit) + 1;
|
|
|
|
int remoteEnd = remoteMessageCount;
|
|
|
|
remoteMessages = remoteFolder.getMessages(remoteStart, remoteEnd, null);
|
2011-03-23 21:36:16 +00:00
|
|
|
// TODO Why are we running through the list twice? Combine w/ for loop below
|
2009-04-24 20:25:34 +00:00
|
|
|
for (Message message : remoteMessages) {
|
|
|
|
remoteUidMap.put(message.getUid(), message);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2009-04-24 20:25:34 +00:00
|
|
|
* Get a list of the messages that are in the remote list but not on the
|
|
|
|
* local store, or messages that are in the local store but failed to download
|
|
|
|
* on the last sync. These are the new messages that we will download.
|
2009-09-10 18:52:36 +00:00
|
|
|
* Note, we also skip syncing messages which are flagged as "deleted message" sentinels,
|
|
|
|
* because they are locally deleted and we don't need or want the old message from
|
|
|
|
* the server.
|
2009-03-04 03:32:22 +00:00
|
|
|
*/
|
2009-04-24 20:25:34 +00:00
|
|
|
for (Message message : remoteMessages) {
|
2009-06-22 23:13:03 +00:00
|
|
|
LocalMessageInfo localMessage = localMessageMap.get(message.getUid());
|
2009-05-20 17:36:16 +00:00
|
|
|
if (localMessage == null) {
|
|
|
|
newMessageCount++;
|
|
|
|
}
|
2010-09-04 23:38:08 +00:00
|
|
|
// localMessage == null -> message has never been created (not even headers)
|
|
|
|
// mFlagLoaded = UNLOADED -> message created, but none of body loaded
|
|
|
|
// mFlagLoaded = PARTIAL -> message created, a "sane" amt of body has been loaded
|
|
|
|
// mFlagLoaded = COMPLETE -> message body has been completely loaded
|
|
|
|
// mFlagLoaded = DELETED -> message has been deleted
|
|
|
|
// Only the first two of these are "unsynced", so let's retrieve them
|
|
|
|
if (localMessage == null ||
|
|
|
|
(localMessage.mFlagLoaded == EmailContent.Message.FLAG_LOADED_UNLOADED)) {
|
2009-04-24 20:25:34 +00:00
|
|
|
unsyncedMessages.add(message);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
}
|
2009-04-24 20:25:34 +00:00
|
|
|
}
|
2009-08-27 22:58:44 +00:00
|
|
|
|
2009-06-22 23:13:03 +00:00
|
|
|
// 8. Download basic info about the new/unloaded messages (if any)
|
2009-04-24 20:25:34 +00:00
|
|
|
/*
|
|
|
|
* Fetch the flags and envelope only of the new messages. This is intended to get us
|
|
|
|
* critical data as fast as possible, and then we'll fill in the details.
|
|
|
|
*/
|
|
|
|
if (unsyncedMessages.size() > 0) {
|
2011-06-13 21:57:17 +00:00
|
|
|
downloadFlagAndEnvelope(account, mailbox, remoteFolder, unsyncedMessages,
|
|
|
|
localMessageMap, unseenMessages);
|
2009-04-24 20:25:34 +00:00
|
|
|
}
|
2009-03-04 03:32:22 +00:00
|
|
|
|
2009-06-22 23:13:03 +00:00
|
|
|
// 9. Refresh the flags for any messages in the local store that we didn't just download.
|
2009-04-24 20:25:34 +00:00
|
|
|
FetchProfile fp = new FetchProfile();
|
|
|
|
fp.add(FetchProfile.Item.FLAGS);
|
|
|
|
remoteFolder.fetch(remoteMessages, fp, null);
|
2009-08-26 05:45:11 +00:00
|
|
|
boolean remoteSupportsSeen = false;
|
|
|
|
boolean remoteSupportsFlagged = false;
|
2009-05-20 17:36:16 +00:00
|
|
|
for (Flag flag : remoteFolder.getPermanentFlags()) {
|
|
|
|
if (flag == Flag.SEEN) {
|
2009-08-26 05:45:11 +00:00
|
|
|
remoteSupportsSeen = true;
|
|
|
|
}
|
|
|
|
if (flag == Flag.FLAGGED) {
|
|
|
|
remoteSupportsFlagged = true;
|
2009-05-20 17:36:16 +00:00
|
|
|
}
|
|
|
|
}
|
2009-08-26 05:45:11 +00:00
|
|
|
// Update the SEEN & FLAGGED (star) flags (if supported remotely - e.g. not for POP3)
|
|
|
|
if (remoteSupportsSeen || remoteSupportsFlagged) {
|
2009-06-22 23:13:03 +00:00
|
|
|
for (Message remoteMessage : remoteMessages) {
|
|
|
|
LocalMessageInfo localMessageInfo = localMessageMap.get(remoteMessage.getUid());
|
|
|
|
if (localMessageInfo == null) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
boolean localSeen = localMessageInfo.mFlagRead;
|
|
|
|
boolean remoteSeen = remoteMessage.isSet(Flag.SEEN);
|
2009-08-26 05:45:11 +00:00
|
|
|
boolean newSeen = (remoteSupportsSeen && (remoteSeen != localSeen));
|
|
|
|
boolean localFlagged = localMessageInfo.mFlagFavorite;
|
|
|
|
boolean remoteFlagged = remoteMessage.isSet(Flag.FLAGGED);
|
|
|
|
boolean newFlagged = (remoteSupportsFlagged && (localFlagged != remoteFlagged));
|
|
|
|
if (newSeen || newFlagged) {
|
2009-06-22 23:13:03 +00:00
|
|
|
Uri uri = ContentUris.withAppendedId(
|
|
|
|
EmailContent.Message.CONTENT_URI, localMessageInfo.mId);
|
|
|
|
ContentValues updateValues = new ContentValues();
|
2009-08-26 05:45:11 +00:00
|
|
|
updateValues.put(EmailContent.Message.FLAG_READ, remoteSeen);
|
|
|
|
updateValues.put(EmailContent.Message.FLAG_FAVORITE, remoteFlagged);
|
2009-09-10 18:52:36 +00:00
|
|
|
resolver.update(uri, updateValues, null, null);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
2009-04-24 20:25:34 +00:00
|
|
|
}
|
|
|
|
}
|
2009-03-04 03:32:22 +00:00
|
|
|
|
2011-04-29 16:27:35 +00:00
|
|
|
// 10. Remove any messages that are in the local store but no longer on the remote store.
|
2009-06-22 23:13:03 +00:00
|
|
|
HashSet<String> localUidsToDelete = new HashSet<String>(localMessageMap.keySet());
|
|
|
|
localUidsToDelete.removeAll(remoteUidMap.keySet());
|
|
|
|
for (String uidToDelete : localUidsToDelete) {
|
|
|
|
LocalMessageInfo infoToDelete = localMessageMap.get(uidToDelete);
|
2009-08-27 22:58:44 +00:00
|
|
|
|
2009-09-10 18:52:36 +00:00
|
|
|
// Delete associated data (attachment files)
|
|
|
|
// Attachment & Body records are auto-deleted when we delete the Message record
|
2011-02-09 01:50:30 +00:00
|
|
|
AttachmentUtilities.deleteAllAttachmentFiles(mContext, account.mId,
|
|
|
|
infoToDelete.mId);
|
2009-09-10 18:52:36 +00:00
|
|
|
|
|
|
|
// Delete the message itself
|
2009-06-22 23:13:03 +00:00
|
|
|
Uri uriToDelete = ContentUris.withAppendedId(
|
|
|
|
EmailContent.Message.CONTENT_URI, infoToDelete.mId);
|
2009-09-10 18:52:36 +00:00
|
|
|
resolver.delete(uriToDelete, null, null);
|
|
|
|
|
|
|
|
// Delete extra rows (e.g. synced or deleted)
|
|
|
|
Uri syncRowToDelete = ContentUris.withAppendedId(
|
|
|
|
EmailContent.Message.UPDATED_CONTENT_URI, infoToDelete.mId);
|
|
|
|
resolver.delete(syncRowToDelete, null, null);
|
|
|
|
Uri deletERowToDelete = ContentUris.withAppendedId(
|
|
|
|
EmailContent.Message.UPDATED_CONTENT_URI, infoToDelete.mId);
|
|
|
|
resolver.delete(deletERowToDelete, null, null);
|
2009-04-24 20:25:34 +00:00
|
|
|
}
|
2009-08-27 22:58:44 +00:00
|
|
|
|
2011-06-13 21:57:17 +00:00
|
|
|
loadUnsyncedMessages(account, remoteFolder, unsyncedMessages, mailbox);
|
2009-06-24 19:48:57 +00:00
|
|
|
|
2011-04-29 16:27:35 +00:00
|
|
|
// 14. Clean up and report results
|
2009-07-16 00:46:43 +00:00
|
|
|
remoteFolder.close(false);
|
2009-06-22 23:13:03 +00:00
|
|
|
|
2011-05-03 21:42:26 +00:00
|
|
|
return new SyncResults(remoteMessageCount, unseenMessages);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
2009-08-19 20:15:13 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Copy one downloaded message (which may have partially-loaded sections)
|
2010-05-04 22:33:08 +00:00
|
|
|
* into a newly created EmailProvider Message, given the account and mailbox
|
2009-08-19 20:15:13 +00:00
|
|
|
*
|
|
|
|
* @param message the remote message we've just downloaded
|
|
|
|
* @param account the account it will be stored into
|
|
|
|
* @param folder the mailbox it will be stored into
|
|
|
|
* @param loadStatus when complete, the message will be marked with this status (e.g.
|
|
|
|
* EmailContent.Message.LOADED)
|
|
|
|
*/
|
2011-06-13 22:32:27 +00:00
|
|
|
public void copyOneMessageToProvider(Message message, Account account,
|
2011-05-14 00:26:27 +00:00
|
|
|
Mailbox folder, int loadStatus) {
|
2010-05-04 22:33:08 +00:00
|
|
|
EmailContent.Message localMessage = null;
|
|
|
|
Cursor c = null;
|
2009-08-19 20:15:13 +00:00
|
|
|
try {
|
2010-05-04 22:33:08 +00:00
|
|
|
c = mContext.getContentResolver().query(
|
|
|
|
EmailContent.Message.CONTENT_URI,
|
|
|
|
EmailContent.Message.CONTENT_PROJECTION,
|
|
|
|
EmailContent.MessageColumns.ACCOUNT_KEY + "=?" +
|
|
|
|
" AND " + MessageColumns.MAILBOX_KEY + "=?" +
|
|
|
|
" AND " + SyncColumns.SERVER_ID + "=?",
|
|
|
|
new String[] {
|
|
|
|
String.valueOf(account.mId),
|
|
|
|
String.valueOf(folder.mId),
|
|
|
|
String.valueOf(message.getUid())
|
|
|
|
},
|
|
|
|
null);
|
|
|
|
if (c.moveToNext()) {
|
|
|
|
localMessage = EmailContent.getContent(c, EmailContent.Message.class);
|
|
|
|
localMessage.mMailboxKey = folder.mId;
|
|
|
|
localMessage.mAccountKey = account.mId;
|
|
|
|
copyOneMessageToProvider(message, localMessage, loadStatus, mContext);
|
2009-08-19 20:15:13 +00:00
|
|
|
}
|
2010-05-04 22:33:08 +00:00
|
|
|
} finally {
|
|
|
|
if (c != null) {
|
|
|
|
c.close();
|
2009-08-19 20:15:13 +00:00
|
|
|
}
|
2010-05-04 22:33:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Copy one downloaded message (which may have partially-loaded sections)
|
|
|
|
* into an already-created EmailProvider Message
|
|
|
|
*
|
|
|
|
* @param message the remote message we've just downloaded
|
|
|
|
* @param localMessage the EmailProvider Message, already created
|
|
|
|
* @param loadStatus when complete, the message will be marked with this status (e.g.
|
|
|
|
* EmailContent.Message.LOADED)
|
|
|
|
* @param context the context to be used for EmailProvider
|
|
|
|
*/
|
|
|
|
public void copyOneMessageToProvider(Message message, EmailContent.Message localMessage,
|
|
|
|
int loadStatus, Context context) {
|
|
|
|
try {
|
2009-08-19 20:15:13 +00:00
|
|
|
|
2010-05-04 22:33:08 +00:00
|
|
|
EmailContent.Body body = EmailContent.Body.restoreBodyWithMessageId(context,
|
2009-08-19 20:15:13 +00:00
|
|
|
localMessage.mId);
|
|
|
|
if (body == null) {
|
|
|
|
body = new EmailContent.Body();
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
// Copy the fields that are available into the message object
|
2010-05-04 22:33:08 +00:00
|
|
|
LegacyConversions.updateMessageFields(localMessage, message,
|
|
|
|
localMessage.mAccountKey, localMessage.mMailboxKey);
|
2009-08-19 20:15:13 +00:00
|
|
|
|
|
|
|
// Now process body parts & attachments
|
|
|
|
ArrayList<Part> viewables = new ArrayList<Part>();
|
|
|
|
ArrayList<Part> attachments = new ArrayList<Part>();
|
|
|
|
MimeUtility.collectParts(message, viewables, attachments);
|
|
|
|
|
2011-02-09 01:50:30 +00:00
|
|
|
ConversionUtilities.updateBodyFields(body, localMessage, viewables);
|
2009-08-19 20:15:13 +00:00
|
|
|
|
|
|
|
// Commit the message & body to the local store immediately
|
2010-05-04 22:33:08 +00:00
|
|
|
saveOrUpdate(localMessage, context);
|
|
|
|
saveOrUpdate(body, context);
|
2009-08-19 20:15:13 +00:00
|
|
|
|
|
|
|
// process (and save) attachments
|
2011-02-06 08:54:39 +00:00
|
|
|
LegacyConversions.updateAttachments(context, localMessage, attachments);
|
2009-08-19 20:15:13 +00:00
|
|
|
|
|
|
|
// One last update of message with two updated flags
|
|
|
|
localMessage.mFlagLoaded = loadStatus;
|
|
|
|
|
|
|
|
ContentValues cv = new ContentValues();
|
|
|
|
cv.put(EmailContent.MessageColumns.FLAG_ATTACHMENT, localMessage.mFlagAttachment);
|
|
|
|
cv.put(EmailContent.MessageColumns.FLAG_LOADED, localMessage.mFlagLoaded);
|
|
|
|
Uri uri = ContentUris.withAppendedId(EmailContent.Message.CONTENT_URI,
|
|
|
|
localMessage.mId);
|
2010-05-04 22:33:08 +00:00
|
|
|
context.getContentResolver().update(uri, cv, null, null);
|
2009-08-19 20:15:13 +00:00
|
|
|
|
|
|
|
} catch (MessagingException me) {
|
2011-02-11 23:05:17 +00:00
|
|
|
Log.e(Logging.LOG_TAG, "Error while copying downloaded message." + me);
|
2009-08-19 20:15:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
} catch (RuntimeException rte) {
|
2011-02-11 23:05:17 +00:00
|
|
|
Log.e(Logging.LOG_TAG, "Error while storing downloaded message." + rte.toString());
|
2009-08-19 20:15:13 +00:00
|
|
|
} catch (IOException ioe) {
|
2011-02-11 23:05:17 +00:00
|
|
|
Log.e(Logging.LOG_TAG, "Error while storing attachment." + ioe.toString());
|
2009-08-19 20:15:13 +00:00
|
|
|
}
|
|
|
|
}
|
2009-08-27 06:12:02 +00:00
|
|
|
|
2009-09-10 23:01:48 +00:00
|
|
|
public void processPendingActions(final long accountId) {
|
|
|
|
put("processPendingActions", null, new Runnable() {
|
2009-08-27 06:12:02 +00:00
|
|
|
public void run() {
|
|
|
|
try {
|
2011-06-29 19:47:56 +00:00
|
|
|
Account account = Account.restoreAccountWithId(mContext, accountId);
|
2009-09-16 17:58:54 +00:00
|
|
|
if (account == null) {
|
|
|
|
return;
|
|
|
|
}
|
2009-09-10 23:01:48 +00:00
|
|
|
processPendingActionsSynchronous(account);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
catch (MessagingException me) {
|
2011-05-13 18:20:04 +00:00
|
|
|
if (Logging.LOGD) {
|
2011-02-11 23:05:17 +00:00
|
|
|
Log.v(Logging.LOG_TAG, "processPendingActions", me);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
/*
|
|
|
|
* Ignore any exceptions from the commands. Commands will be processed
|
|
|
|
* on the next round.
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2009-08-27 06:12:02 +00:00
|
|
|
/**
|
|
|
|
* Find messages in the updated table that need to be written back to server.
|
|
|
|
*
|
|
|
|
* Handles:
|
|
|
|
* Read/Unread
|
|
|
|
* Flagged
|
2009-09-25 21:54:32 +00:00
|
|
|
* Append (upload)
|
2009-09-10 18:52:36 +00:00
|
|
|
* Move To Trash
|
|
|
|
* Empty trash
|
2009-09-10 23:01:48 +00:00
|
|
|
* TODO:
|
2009-08-27 06:12:02 +00:00
|
|
|
* Move
|
2009-08-27 22:58:44 +00:00
|
|
|
*
|
2009-09-10 18:52:36 +00:00
|
|
|
* @param account the account to scan for pending actions
|
2009-08-27 06:12:02 +00:00
|
|
|
* @throws MessagingException
|
|
|
|
*/
|
2011-06-13 22:32:27 +00:00
|
|
|
private void processPendingActionsSynchronous(Account account)
|
2009-08-27 06:12:02 +00:00
|
|
|
throws MessagingException {
|
|
|
|
ContentResolver resolver = mContext.getContentResolver();
|
|
|
|
String[] accountIdArgs = new String[] { Long.toString(account.mId) };
|
2009-09-10 23:01:48 +00:00
|
|
|
|
|
|
|
// Handle deletes first, it's always better to get rid of things first
|
|
|
|
processPendingDeletesSynchronous(account, resolver, accountIdArgs);
|
|
|
|
|
2009-09-25 21:54:32 +00:00
|
|
|
// Handle uploads (currently, only to sent messages)
|
|
|
|
processPendingUploadsSynchronous(account, resolver, accountIdArgs);
|
|
|
|
|
2009-09-10 23:01:48 +00:00
|
|
|
// Now handle updates / upsyncs
|
|
|
|
processPendingUpdatesSynchronous(account, resolver, accountIdArgs);
|
|
|
|
}
|
|
|
|
|
2011-06-23 17:52:21 +00:00
|
|
|
/**
|
|
|
|
* Get the mailbox corresponding to the remote location of a message; this will normally be
|
|
|
|
* the mailbox whose _id is mailboxKey, except for search results, where we must look it up
|
|
|
|
* by serverId
|
|
|
|
* @param message the message in question
|
|
|
|
* @return the mailbox in which the message resides on the server
|
|
|
|
*/
|
|
|
|
private Mailbox getRemoteMailboxForMessage(EmailContent.Message message) {
|
|
|
|
// If this is a search result, use the protocolSearchInfo field to get the server info
|
|
|
|
if (!TextUtils.isEmpty(message.mProtocolSearchInfo)) {
|
|
|
|
long accountKey = message.mAccountKey;
|
|
|
|
String protocolSearchInfo = message.mProtocolSearchInfo;
|
|
|
|
if (accountKey == mLastSearchAccountKey &&
|
|
|
|
protocolSearchInfo.equals(mLastSearchServerId)) {
|
|
|
|
return mLastSearchRemoteMailbox;
|
|
|
|
}
|
|
|
|
Cursor c = mContext.getContentResolver().query(Mailbox.CONTENT_URI,
|
|
|
|
Mailbox.CONTENT_PROJECTION, Mailbox.PATH_AND_ACCOUNT_SELECTION,
|
|
|
|
new String[] {protocolSearchInfo, Long.toString(accountKey)},
|
|
|
|
null);
|
|
|
|
try {
|
|
|
|
if (c.moveToNext()) {
|
|
|
|
Mailbox mailbox = new Mailbox();
|
|
|
|
mailbox.restore(c);
|
|
|
|
mLastSearchAccountKey = accountKey;
|
|
|
|
mLastSearchServerId = protocolSearchInfo;
|
|
|
|
mLastSearchRemoteMailbox = mailbox;
|
|
|
|
return mailbox;
|
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
c.close();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return Mailbox.restoreMailboxWithId(mContext, message.mMailboxKey);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-09-10 23:01:48 +00:00
|
|
|
/**
|
|
|
|
* Scan for messages that are in the Message_Deletes table, look for differences that
|
|
|
|
* we can deal with, and do the work.
|
|
|
|
*
|
|
|
|
* @param account
|
|
|
|
* @param resolver
|
|
|
|
* @param accountIdArgs
|
|
|
|
*/
|
2011-06-13 22:32:27 +00:00
|
|
|
private void processPendingDeletesSynchronous(Account account,
|
2009-09-10 23:01:48 +00:00
|
|
|
ContentResolver resolver, String[] accountIdArgs) {
|
|
|
|
Cursor deletes = resolver.query(EmailContent.Message.DELETED_CONTENT_URI,
|
|
|
|
EmailContent.Message.CONTENT_PROJECTION,
|
|
|
|
EmailContent.MessageColumns.ACCOUNT_KEY + "=?", accountIdArgs,
|
|
|
|
EmailContent.MessageColumns.MAILBOX_KEY);
|
|
|
|
long lastMessageId = -1;
|
|
|
|
try {
|
|
|
|
// Defer setting up the store until we know we need to access it
|
|
|
|
Store remoteStore = null;
|
|
|
|
// loop through messages marked as deleted
|
|
|
|
while (deletes.moveToNext()) {
|
|
|
|
boolean deleteFromTrash = false;
|
|
|
|
|
|
|
|
EmailContent.Message oldMessage =
|
2010-05-07 21:04:50 +00:00
|
|
|
EmailContent.getContent(deletes, EmailContent.Message.class);
|
2009-09-10 23:01:48 +00:00
|
|
|
|
|
|
|
if (oldMessage != null) {
|
2010-05-07 21:04:50 +00:00
|
|
|
lastMessageId = oldMessage.mId;
|
2011-06-23 17:52:21 +00:00
|
|
|
|
|
|
|
Mailbox mailbox = getRemoteMailboxForMessage(oldMessage);
|
|
|
|
if (mailbox == null) {
|
|
|
|
continue; // Mailbox removed. Move to the next message.
|
2009-09-10 23:01:48 +00:00
|
|
|
}
|
|
|
|
deleteFromTrash = mailbox.mType == Mailbox.TYPE_TRASH;
|
|
|
|
|
2011-06-23 17:52:21 +00:00
|
|
|
// Load the remote store if it will be needed
|
|
|
|
if (remoteStore == null && deleteFromTrash) {
|
2011-06-29 19:47:56 +00:00
|
|
|
remoteStore = Store.getInstance(account, mContext);
|
2011-06-23 17:52:21 +00:00
|
|
|
}
|
2009-09-10 23:01:48 +00:00
|
|
|
|
2011-06-23 17:52:21 +00:00
|
|
|
// Dispatch here for specific change types
|
|
|
|
if (deleteFromTrash) {
|
|
|
|
// Move message to trash
|
|
|
|
processPendingDeleteFromTrash(remoteStore, account, mailbox, oldMessage);
|
|
|
|
}
|
2009-09-10 23:01:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Finally, delete the update
|
|
|
|
Uri uri = ContentUris.withAppendedId(EmailContent.Message.DELETED_CONTENT_URI,
|
|
|
|
oldMessage.mId);
|
|
|
|
resolver.delete(uri, null, null);
|
|
|
|
}
|
|
|
|
} catch (MessagingException me) {
|
|
|
|
// Presumably an error here is an account connection failure, so there is
|
|
|
|
// no point in continuing through the rest of the pending updates.
|
|
|
|
if (Email.DEBUG) {
|
2011-02-11 23:05:17 +00:00
|
|
|
Log.d(Logging.LOG_TAG, "Unable to process pending delete for id="
|
2009-09-10 23:01:48 +00:00
|
|
|
+ lastMessageId + ": " + me);
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
deletes.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-09-25 21:54:32 +00:00
|
|
|
/**
|
|
|
|
* Scan for messages that are in Sent, and are in need of upload,
|
|
|
|
* and send them to the server. "In need of upload" is defined as:
|
|
|
|
* serverId == null (no UID has been assigned)
|
|
|
|
* or
|
|
|
|
* message is in the updated list
|
|
|
|
*
|
|
|
|
* Note we also look for messages that are moving from drafts->outbox->sent. They never
|
|
|
|
* go through "drafts" or "outbox" on the server, so we hang onto these until they can be
|
|
|
|
* uploaded directly to the Sent folder.
|
|
|
|
*
|
|
|
|
* @param account
|
|
|
|
* @param resolver
|
|
|
|
* @param accountIdArgs
|
|
|
|
*/
|
2011-06-13 22:32:27 +00:00
|
|
|
private void processPendingUploadsSynchronous(Account account,
|
2011-04-20 15:04:46 +00:00
|
|
|
ContentResolver resolver, String[] accountIdArgs) {
|
2009-09-25 21:54:32 +00:00
|
|
|
// Find the Sent folder (since that's all we're uploading for now
|
|
|
|
Cursor mailboxes = resolver.query(Mailbox.CONTENT_URI, Mailbox.ID_PROJECTION,
|
|
|
|
MailboxColumns.ACCOUNT_KEY + "=?"
|
|
|
|
+ " and " + MailboxColumns.TYPE + "=" + Mailbox.TYPE_SENT,
|
|
|
|
accountIdArgs, null);
|
|
|
|
long lastMessageId = -1;
|
|
|
|
try {
|
|
|
|
// Defer setting up the store until we know we need to access it
|
|
|
|
Store remoteStore = null;
|
|
|
|
while (mailboxes.moveToNext()) {
|
|
|
|
long mailboxId = mailboxes.getLong(Mailbox.ID_PROJECTION_COLUMN);
|
|
|
|
String[] mailboxKeyArgs = new String[] { Long.toString(mailboxId) };
|
|
|
|
// Demand load mailbox
|
|
|
|
Mailbox mailbox = null;
|
|
|
|
|
|
|
|
// First handle the "new" messages (serverId == null)
|
|
|
|
Cursor upsyncs1 = resolver.query(EmailContent.Message.CONTENT_URI,
|
|
|
|
EmailContent.Message.ID_PROJECTION,
|
|
|
|
EmailContent.Message.MAILBOX_KEY + "=?"
|
|
|
|
+ " and (" + EmailContent.Message.SERVER_ID + " is null"
|
|
|
|
+ " or " + EmailContent.Message.SERVER_ID + "=''" + ")",
|
|
|
|
mailboxKeyArgs,
|
|
|
|
null);
|
|
|
|
try {
|
|
|
|
while (upsyncs1.moveToNext()) {
|
|
|
|
// Load the remote store if it will be needed
|
|
|
|
if (remoteStore == null) {
|
2011-06-29 19:47:56 +00:00
|
|
|
remoteStore = Store.getInstance(account, mContext);
|
2009-09-25 21:54:32 +00:00
|
|
|
}
|
|
|
|
// Load the mailbox if it will be needed
|
|
|
|
if (mailbox == null) {
|
|
|
|
mailbox = Mailbox.restoreMailboxWithId(mContext, mailboxId);
|
2010-06-11 18:37:10 +00:00
|
|
|
if (mailbox == null) {
|
|
|
|
continue; // Mailbox removed. Move to the next message.
|
|
|
|
}
|
2009-09-25 21:54:32 +00:00
|
|
|
}
|
|
|
|
// upsync the message
|
|
|
|
long id = upsyncs1.getLong(EmailContent.Message.ID_PROJECTION_COLUMN);
|
|
|
|
lastMessageId = id;
|
|
|
|
processUploadMessage(resolver, remoteStore, account, mailbox, id);
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
if (upsyncs1 != null) {
|
|
|
|
upsyncs1.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Next, handle any updates (e.g. edited in place, although this shouldn't happen)
|
|
|
|
Cursor upsyncs2 = resolver.query(EmailContent.Message.UPDATED_CONTENT_URI,
|
|
|
|
EmailContent.Message.ID_PROJECTION,
|
|
|
|
EmailContent.MessageColumns.MAILBOX_KEY + "=?", mailboxKeyArgs,
|
|
|
|
null);
|
|
|
|
try {
|
|
|
|
while (upsyncs2.moveToNext()) {
|
|
|
|
// Load the remote store if it will be needed
|
|
|
|
if (remoteStore == null) {
|
2011-06-29 19:47:56 +00:00
|
|
|
remoteStore = Store.getInstance(account, mContext);
|
2009-09-25 21:54:32 +00:00
|
|
|
}
|
|
|
|
// Load the mailbox if it will be needed
|
|
|
|
if (mailbox == null) {
|
|
|
|
mailbox = Mailbox.restoreMailboxWithId(mContext, mailboxId);
|
2010-06-11 18:37:10 +00:00
|
|
|
if (mailbox == null) {
|
|
|
|
continue; // Mailbox removed. Move to the next message.
|
|
|
|
}
|
2009-09-25 21:54:32 +00:00
|
|
|
}
|
|
|
|
// upsync the message
|
|
|
|
long id = upsyncs2.getLong(EmailContent.Message.ID_PROJECTION_COLUMN);
|
|
|
|
lastMessageId = id;
|
|
|
|
processUploadMessage(resolver, remoteStore, account, mailbox, id);
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
if (upsyncs2 != null) {
|
|
|
|
upsyncs2.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (MessagingException me) {
|
|
|
|
// Presumably an error here is an account connection failure, so there is
|
|
|
|
// no point in continuing through the rest of the pending updates.
|
|
|
|
if (Email.DEBUG) {
|
2011-02-11 23:05:17 +00:00
|
|
|
Log.d(Logging.LOG_TAG, "Unable to process pending upsync for id="
|
2009-09-25 21:54:32 +00:00
|
|
|
+ lastMessageId + ": " + me);
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
if (mailboxes != null) {
|
|
|
|
mailboxes.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-09-10 23:01:48 +00:00
|
|
|
/**
|
|
|
|
* Scan for messages that are in the Message_Updates table, look for differences that
|
|
|
|
* we can deal with, and do the work.
|
|
|
|
*
|
|
|
|
* @param account
|
|
|
|
* @param resolver
|
|
|
|
* @param accountIdArgs
|
|
|
|
*/
|
2011-06-13 22:32:27 +00:00
|
|
|
private void processPendingUpdatesSynchronous(Account account,
|
2009-09-10 23:01:48 +00:00
|
|
|
ContentResolver resolver, String[] accountIdArgs) {
|
2009-08-27 06:12:02 +00:00
|
|
|
Cursor updates = resolver.query(EmailContent.Message.UPDATED_CONTENT_URI,
|
|
|
|
EmailContent.Message.CONTENT_PROJECTION,
|
|
|
|
EmailContent.MessageColumns.ACCOUNT_KEY + "=?", accountIdArgs,
|
|
|
|
EmailContent.MessageColumns.MAILBOX_KEY);
|
|
|
|
long lastMessageId = -1;
|
|
|
|
try {
|
|
|
|
// Defer setting up the store until we know we need to access it
|
|
|
|
Store remoteStore = null;
|
2009-09-10 18:52:36 +00:00
|
|
|
// Demand load mailbox (note order-by to reduce thrashing here)
|
2009-08-27 06:12:02 +00:00
|
|
|
Mailbox mailbox = null;
|
|
|
|
// loop through messages marked as needing updates
|
|
|
|
while (updates.moveToNext()) {
|
2009-09-10 18:52:36 +00:00
|
|
|
boolean changeMoveToTrash = false;
|
2009-08-27 06:12:02 +00:00
|
|
|
boolean changeRead = false;
|
|
|
|
boolean changeFlagged = false;
|
2010-08-24 19:37:00 +00:00
|
|
|
boolean changeMailbox = false;
|
2009-08-27 06:12:02 +00:00
|
|
|
|
|
|
|
EmailContent.Message oldMessage =
|
|
|
|
EmailContent.getContent(updates, EmailContent.Message.class);
|
|
|
|
lastMessageId = oldMessage.mId;
|
|
|
|
EmailContent.Message newMessage =
|
|
|
|
EmailContent.Message.restoreMessageWithId(mContext, oldMessage.mId);
|
|
|
|
if (newMessage != null) {
|
2011-06-23 17:52:21 +00:00
|
|
|
mailbox = Mailbox.restoreMailboxWithId(mContext, newMessage.mMailboxKey);
|
|
|
|
if (mailbox == null) {
|
|
|
|
continue; // Mailbox removed. Move to the next message.
|
2009-09-10 18:52:36 +00:00
|
|
|
}
|
2010-08-24 19:37:00 +00:00
|
|
|
if (oldMessage.mMailboxKey != newMessage.mMailboxKey) {
|
|
|
|
if (mailbox.mType == Mailbox.TYPE_TRASH) {
|
|
|
|
changeMoveToTrash = true;
|
|
|
|
} else {
|
|
|
|
changeMailbox = true;
|
|
|
|
}
|
|
|
|
}
|
2009-08-27 06:12:02 +00:00
|
|
|
changeRead = oldMessage.mFlagRead != newMessage.mFlagRead;
|
|
|
|
changeFlagged = oldMessage.mFlagFavorite != newMessage.mFlagFavorite;
|
2010-08-24 19:37:00 +00:00
|
|
|
}
|
2009-08-27 06:12:02 +00:00
|
|
|
|
2009-09-10 18:52:36 +00:00
|
|
|
// Load the remote store if it will be needed
|
2010-08-24 19:37:00 +00:00
|
|
|
if (remoteStore == null &&
|
|
|
|
(changeMoveToTrash || changeRead || changeFlagged || changeMailbox)) {
|
2011-06-29 19:47:56 +00:00
|
|
|
remoteStore = Store.getInstance(account, mContext);
|
2009-09-10 18:52:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Dispatch here for specific change types
|
|
|
|
if (changeMoveToTrash) {
|
|
|
|
// Move message to trash
|
2009-09-10 23:01:48 +00:00
|
|
|
processPendingMoveToTrash(remoteStore, account, mailbox, oldMessage,
|
|
|
|
newMessage);
|
2010-08-24 19:37:00 +00:00
|
|
|
} else if (changeRead || changeFlagged || changeMailbox) {
|
|
|
|
processPendingDataChange(remoteStore, mailbox, changeRead, changeFlagged,
|
|
|
|
changeMailbox, oldMessage, newMessage);
|
2009-08-27 06:12:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Finally, delete the update
|
|
|
|
Uri uri = ContentUris.withAppendedId(EmailContent.Message.UPDATED_CONTENT_URI,
|
|
|
|
oldMessage.mId);
|
|
|
|
resolver.delete(uri, null, null);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
2009-08-27 06:12:02 +00:00
|
|
|
|
|
|
|
} catch (MessagingException me) {
|
|
|
|
// Presumably an error here is an account connection failure, so there is
|
|
|
|
// no point in continuing through the rest of the pending updates.
|
|
|
|
if (Email.DEBUG) {
|
2011-02-11 23:05:17 +00:00
|
|
|
Log.d(Logging.LOG_TAG, "Unable to process pending update for id="
|
2009-08-27 06:12:02 +00:00
|
|
|
+ lastMessageId + ": " + me);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
2009-08-27 06:12:02 +00:00
|
|
|
} finally {
|
|
|
|
updates.close();
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-09-25 21:54:32 +00:00
|
|
|
/**
|
|
|
|
* Upsync an entire message. This must also unwind whatever triggered it (either by
|
|
|
|
* updating the serverId, or by deleting the update record, or it's going to keep happening
|
|
|
|
* over and over again.
|
|
|
|
*
|
|
|
|
* Note: If the message is being uploaded into an unexpected mailbox, we *do not* upload.
|
|
|
|
* This is to avoid unnecessary uploads into the trash. Although the caller attempts to select
|
|
|
|
* only the Drafts and Sent folders, this can happen when the update record and the current
|
|
|
|
* record mismatch. In this case, we let the update record remain, because the filters
|
|
|
|
* in processPendingUpdatesSynchronous() will pick it up as a move and handle it (or drop it)
|
|
|
|
* appropriately.
|
|
|
|
*
|
|
|
|
* @param resolver
|
|
|
|
* @param remoteStore
|
|
|
|
* @param account
|
|
|
|
* @param mailbox the actual mailbox
|
|
|
|
* @param messageId
|
|
|
|
*/
|
|
|
|
private void processUploadMessage(ContentResolver resolver, Store remoteStore,
|
2011-06-13 22:32:27 +00:00
|
|
|
Account account, Mailbox mailbox, long messageId)
|
2009-09-25 21:54:32 +00:00
|
|
|
throws MessagingException {
|
2011-03-21 23:13:15 +00:00
|
|
|
EmailContent.Message newMessage =
|
2009-09-25 21:54:32 +00:00
|
|
|
EmailContent.Message.restoreMessageWithId(mContext, messageId);
|
|
|
|
boolean deleteUpdate = false;
|
2011-03-21 23:13:15 +00:00
|
|
|
if (newMessage == null) {
|
2009-09-25 21:54:32 +00:00
|
|
|
deleteUpdate = true;
|
2011-02-11 23:05:17 +00:00
|
|
|
Log.d(Logging.LOG_TAG, "Upsync failed for null message, id=" + messageId);
|
2009-09-25 21:54:32 +00:00
|
|
|
} else if (mailbox.mType == Mailbox.TYPE_DRAFTS) {
|
|
|
|
deleteUpdate = false;
|
2011-02-11 23:05:17 +00:00
|
|
|
Log.d(Logging.LOG_TAG, "Upsync skipped for mailbox=drafts, id=" + messageId);
|
2009-09-25 21:54:32 +00:00
|
|
|
} else if (mailbox.mType == Mailbox.TYPE_OUTBOX) {
|
|
|
|
deleteUpdate = false;
|
2011-02-11 23:05:17 +00:00
|
|
|
Log.d(Logging.LOG_TAG, "Upsync skipped for mailbox=outbox, id=" + messageId);
|
2009-09-25 21:54:32 +00:00
|
|
|
} else if (mailbox.mType == Mailbox.TYPE_TRASH) {
|
|
|
|
deleteUpdate = false;
|
2011-02-11 23:05:17 +00:00
|
|
|
Log.d(Logging.LOG_TAG, "Upsync skipped for mailbox=trash, id=" + messageId);
|
2011-03-21 23:13:15 +00:00
|
|
|
} else if (newMessage != null && newMessage.mMailboxKey != mailbox.mId) {
|
|
|
|
deleteUpdate = false;
|
|
|
|
Log.d(Logging.LOG_TAG, "Upsync skipped; mailbox changed, id=" + messageId);
|
2009-09-25 21:54:32 +00:00
|
|
|
} else {
|
2011-02-11 23:05:17 +00:00
|
|
|
Log.d(Logging.LOG_TAG, "Upsyc triggered for message id=" + messageId);
|
2011-03-21 23:13:15 +00:00
|
|
|
deleteUpdate = processPendingAppend(remoteStore, account, mailbox, newMessage);
|
2009-09-25 21:54:32 +00:00
|
|
|
}
|
|
|
|
if (deleteUpdate) {
|
|
|
|
// Finally, delete the update (if any)
|
2011-04-08 20:59:21 +00:00
|
|
|
Uri uri = ContentUris.withAppendedId(
|
|
|
|
EmailContent.Message.UPDATED_CONTENT_URI, messageId);
|
2009-09-25 21:54:32 +00:00
|
|
|
resolver.delete(uri, null, null);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
/**
|
2010-08-24 19:37:00 +00:00
|
|
|
* Upsync changes to read, flagged, or mailbox
|
2009-03-04 03:32:22 +00:00
|
|
|
*
|
2010-08-24 19:37:00 +00:00
|
|
|
* @param remoteStore the remote store for this mailbox
|
|
|
|
* @param mailbox the mailbox the message is stored in
|
|
|
|
* @param changeRead whether the message's read state has changed
|
|
|
|
* @param changeFlagged whether the message's flagged state has changed
|
|
|
|
* @param changeMailbox whether the message's mailbox has changed
|
|
|
|
* @param oldMessage the message in it's pre-change state
|
|
|
|
* @param newMessage the current version of the message
|
2009-03-04 03:32:22 +00:00
|
|
|
*/
|
2010-08-24 19:37:00 +00:00
|
|
|
private void processPendingDataChange(Store remoteStore, Mailbox mailbox, boolean changeRead,
|
|
|
|
boolean changeFlagged, boolean changeMailbox, EmailContent.Message oldMessage,
|
2011-03-23 21:36:16 +00:00
|
|
|
final EmailContent.Message newMessage) throws MessagingException {
|
2011-06-23 17:52:21 +00:00
|
|
|
// New mailbox is the mailbox this message WILL be in (same as the one it WAS in if it isn't
|
|
|
|
// being moved
|
|
|
|
Mailbox newMailbox = mailbox;
|
|
|
|
// Mailbox is the original remote mailbox (the one we're acting on)
|
|
|
|
mailbox = getRemoteMailboxForMessage(oldMessage);
|
2010-05-13 21:28:08 +00:00
|
|
|
|
2009-09-25 21:54:32 +00:00
|
|
|
// 0. No remote update if the message is local-only
|
|
|
|
if (newMessage.mServerId == null || newMessage.mServerId.equals("")
|
2010-08-24 19:37:00 +00:00
|
|
|
|| newMessage.mServerId.startsWith(LOCAL_SERVERID_PREFIX) || (mailbox == null)) {
|
2009-09-25 21:54:32 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 1. No remote update for DRAFTS or OUTBOX
|
|
|
|
if (mailbox.mType == Mailbox.TYPE_DRAFTS || mailbox.mType == Mailbox.TYPE_OUTBOX) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 2. Open the remote store & folder
|
2011-04-22 22:45:11 +00:00
|
|
|
Folder remoteFolder = remoteStore.getFolder(mailbox.mServerId);
|
2009-03-04 03:32:22 +00:00
|
|
|
if (!remoteFolder.exists()) {
|
2009-09-10 23:01:48 +00:00
|
|
|
return;
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
2011-06-29 21:00:43 +00:00
|
|
|
remoteFolder.open(OpenMode.READ_WRITE);
|
2009-03-04 03:32:22 +00:00
|
|
|
if (remoteFolder.getMode() != OpenMode.READ_WRITE) {
|
|
|
|
return;
|
|
|
|
}
|
2009-09-25 21:54:32 +00:00
|
|
|
|
|
|
|
// 3. Finally, apply the changes to the message
|
2009-09-10 23:01:48 +00:00
|
|
|
Message remoteMessage = remoteFolder.getMessage(newMessage.mServerId);
|
2009-03-04 03:32:22 +00:00
|
|
|
if (remoteMessage == null) {
|
2009-09-10 23:01:48 +00:00
|
|
|
return;
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
2009-09-10 23:01:48 +00:00
|
|
|
if (Email.DEBUG) {
|
2011-02-11 23:05:17 +00:00
|
|
|
Log.d(Logging.LOG_TAG,
|
2010-08-24 19:37:00 +00:00
|
|
|
"Update for msg id=" + newMessage.mId
|
2009-09-10 23:01:48 +00:00
|
|
|
+ " read=" + newMessage.mFlagRead
|
2010-08-24 19:37:00 +00:00
|
|
|
+ " flagged=" + newMessage.mFlagFavorite
|
|
|
|
+ " new mailbox=" + newMessage.mMailboxKey);
|
2009-09-10 23:01:48 +00:00
|
|
|
}
|
|
|
|
Message[] messages = new Message[] { remoteMessage };
|
|
|
|
if (changeRead) {
|
|
|
|
remoteFolder.setFlags(messages, FLAG_LIST_SEEN, newMessage.mFlagRead);
|
|
|
|
}
|
|
|
|
if (changeFlagged) {
|
|
|
|
remoteFolder.setFlags(messages, FLAG_LIST_FLAGGED, newMessage.mFlagFavorite);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
2010-08-24 19:37:00 +00:00
|
|
|
if (changeMailbox) {
|
2011-04-22 22:45:11 +00:00
|
|
|
Folder toFolder = remoteStore.getFolder(newMailbox.mServerId);
|
2010-08-24 19:37:00 +00:00
|
|
|
if (!remoteFolder.exists()) {
|
|
|
|
return;
|
|
|
|
}
|
2011-03-21 23:34:44 +00:00
|
|
|
// We may need the message id to search for the message in the destination folder
|
|
|
|
remoteMessage.setMessageId(newMessage.mMessageId);
|
2010-08-24 19:37:00 +00:00
|
|
|
// Copy the message to its new folder
|
2011-03-23 21:36:16 +00:00
|
|
|
remoteFolder.copyMessages(messages, toFolder, new MessageUpdateCallbacks() {
|
|
|
|
@Override
|
|
|
|
public void onMessageUidChange(Message message, String newUid) {
|
|
|
|
ContentValues cv = new ContentValues();
|
|
|
|
cv.put(EmailContent.Message.SERVER_ID, newUid);
|
|
|
|
// We only have one message, so, any updates _must_ be for it. Otherwise,
|
|
|
|
// we'd have to cycle through to find the one with the same server ID.
|
|
|
|
mContext.getContentResolver().update(ContentUris.withAppendedId(
|
|
|
|
EmailContent.Message.CONTENT_URI, newMessage.mId), cv, null, null);
|
|
|
|
}
|
|
|
|
@Override
|
|
|
|
public void onMessageNotFound(Message message) {
|
|
|
|
}
|
|
|
|
});
|
2010-08-24 19:37:00 +00:00
|
|
|
// Delete the message from the remote source folder
|
|
|
|
remoteMessage.setFlag(Flag.DELETED, true);
|
|
|
|
remoteFolder.expunge();
|
|
|
|
}
|
|
|
|
remoteFolder.close(false);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Process a pending trash message command.
|
|
|
|
*
|
2009-09-10 18:52:36 +00:00
|
|
|
* @param remoteStore the remote store we're working in
|
|
|
|
* @param account The account in which we are working
|
|
|
|
* @param newMailbox The local trash mailbox
|
|
|
|
* @param oldMessage The message copy that was saved in the updates shadow table
|
|
|
|
* @param newMessage The message that was moved to the mailbox
|
2009-03-04 03:32:22 +00:00
|
|
|
*/
|
2009-09-10 18:52:36 +00:00
|
|
|
private void processPendingMoveToTrash(Store remoteStore,
|
2011-06-13 22:32:27 +00:00
|
|
|
Account account, Mailbox newMailbox, EmailContent.Message oldMessage,
|
2009-09-10 18:52:36 +00:00
|
|
|
final EmailContent.Message newMessage) throws MessagingException {
|
|
|
|
|
2009-09-25 21:54:32 +00:00
|
|
|
// 0. No remote move if the message is local-only
|
|
|
|
if (newMessage.mServerId == null || newMessage.mServerId.equals("")
|
|
|
|
|| newMessage.mServerId.startsWith(LOCAL_SERVERID_PREFIX)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2009-09-10 18:52:36 +00:00
|
|
|
// 1. Escape early if we can't find the local mailbox
|
|
|
|
// TODO smaller projection here
|
2011-06-23 17:52:21 +00:00
|
|
|
Mailbox oldMailbox = getRemoteMailboxForMessage(oldMessage);
|
2009-09-10 18:52:36 +00:00
|
|
|
if (oldMailbox == null) {
|
|
|
|
// can't find old mailbox, it may have been deleted. just return.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// 2. We don't support delete-from-trash here
|
|
|
|
if (oldMailbox.mType == Mailbox.TYPE_TRASH) {
|
|
|
|
return;
|
|
|
|
}
|
2009-03-04 03:32:22 +00:00
|
|
|
|
2009-09-10 18:52:36 +00:00
|
|
|
// 3. If DELETE_POLICY_NEVER, simply write back the deleted sentinel and return
|
|
|
|
//
|
|
|
|
// This sentinel takes the place of the server-side message, and locally "deletes" it
|
|
|
|
// by inhibiting future sync or display of the message. It will eventually go out of
|
|
|
|
// scope when it becomes old, or is deleted on the server, and the regular sync code
|
|
|
|
// will clean it up for us.
|
|
|
|
if (account.getDeletePolicy() == Account.DELETE_POLICY_NEVER) {
|
|
|
|
EmailContent.Message sentinel = new EmailContent.Message();
|
|
|
|
sentinel.mAccountKey = oldMessage.mAccountKey;
|
|
|
|
sentinel.mMailboxKey = oldMessage.mMailboxKey;
|
|
|
|
sentinel.mFlagLoaded = EmailContent.Message.FLAG_LOADED_DELETED;
|
2009-10-01 08:45:10 +00:00
|
|
|
sentinel.mFlagRead = true;
|
2009-09-10 18:52:36 +00:00
|
|
|
sentinel.mServerId = oldMessage.mServerId;
|
|
|
|
sentinel.save(mContext);
|
AI 146134: Add persistence API for remote stores & folders to use while
syncing. This provides a key-value store, per folder, that
can be used by network Stores to record persistent data such
as sync status, server keys, etc.
Note that, by definition, this only applies to remote folders
(e.g. IMAP, POP3). You'll see everywhere that LocalFolder is
passed null, and this is correct - LocalFolder *is* persistent
storage and does not need external help.
Note to reviewers: The core changes are Folder.java,
LocalStore.java, and LocalStoreUnitTests.java, so please give
them the bulk of your reviewer attention. The other files
are just following along with minor API changes. Of those,
the one worth close examination is MessagingController.java,
which is the only place in the system where remote Folders
are bonded with Local Folders and thus where this new API
comes into play.
Note to jham: Can you please take a look at
LocalStore.LocalFolder.setPersistentString() and recommend
better SQL foo than my primitive test-then-update-or-insert
logic, which is not transactional or threadsafe.
BUG=1786939
Automated import of CL 146134
2009-04-14 17:15:07 +00:00
|
|
|
|
2009-09-10 18:52:36 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// The rest of this method handles server-side deletion
|
|
|
|
|
|
|
|
// 4. Find the remote mailbox (that we deleted from), and open it
|
2011-04-22 22:45:11 +00:00
|
|
|
Folder remoteFolder = remoteStore.getFolder(oldMailbox.mServerId);
|
2009-03-04 03:32:22 +00:00
|
|
|
if (!remoteFolder.exists()) {
|
|
|
|
return;
|
|
|
|
}
|
2009-09-10 18:52:36 +00:00
|
|
|
|
2011-06-29 21:00:43 +00:00
|
|
|
remoteFolder.open(OpenMode.READ_WRITE);
|
2009-03-04 03:32:22 +00:00
|
|
|
if (remoteFolder.getMode() != OpenMode.READ_WRITE) {
|
2009-06-25 06:53:32 +00:00
|
|
|
remoteFolder.close(false);
|
2009-03-04 03:32:22 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2009-09-10 18:52:36 +00:00
|
|
|
// 5. Find the remote original message
|
|
|
|
Message remoteMessage = remoteFolder.getMessage(oldMessage.mServerId);
|
2009-03-04 03:32:22 +00:00
|
|
|
if (remoteMessage == null) {
|
2009-06-25 06:53:32 +00:00
|
|
|
remoteFolder.close(false);
|
2009-03-04 03:32:22 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2009-09-10 18:52:36 +00:00
|
|
|
// 6. Find the remote trash folder, and create it if not found
|
2011-04-22 22:45:11 +00:00
|
|
|
Folder remoteTrashFolder = remoteStore.getFolder(newMailbox.mServerId);
|
2009-03-04 03:32:22 +00:00
|
|
|
if (!remoteTrashFolder.exists()) {
|
|
|
|
/*
|
|
|
|
* If the remote trash folder doesn't exist we try to create it.
|
|
|
|
*/
|
|
|
|
remoteTrashFolder.create(FolderType.HOLDS_MESSAGES);
|
|
|
|
}
|
|
|
|
|
2009-09-10 18:52:36 +00:00
|
|
|
// 7. Try to copy the message into the remote trash folder
|
|
|
|
// Note, this entire section will be skipped for POP3 because there's no remote trash
|
2009-03-04 03:32:22 +00:00
|
|
|
if (remoteTrashFolder.exists()) {
|
2009-04-28 00:17:22 +00:00
|
|
|
/*
|
|
|
|
* Because remoteTrashFolder may be new, we need to explicitly open it
|
|
|
|
*/
|
2011-06-29 21:00:43 +00:00
|
|
|
remoteTrashFolder.open(OpenMode.READ_WRITE);
|
2009-04-28 00:17:22 +00:00
|
|
|
if (remoteTrashFolder.getMode() != OpenMode.READ_WRITE) {
|
2009-06-25 06:53:32 +00:00
|
|
|
remoteFolder.close(false);
|
|
|
|
remoteTrashFolder.close(false);
|
2009-04-28 00:17:22 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2009-04-24 18:54:42 +00:00
|
|
|
remoteFolder.copyMessages(new Message[] { remoteMessage }, remoteTrashFolder,
|
|
|
|
new Folder.MessageUpdateCallbacks() {
|
2009-09-10 18:52:36 +00:00
|
|
|
public void onMessageUidChange(Message message, String newUid) {
|
2009-04-28 00:17:22 +00:00
|
|
|
// update the UID in the local trash folder, because some stores will
|
|
|
|
// have to change it when copying to remoteTrashFolder
|
2009-09-10 18:52:36 +00:00
|
|
|
ContentValues cv = new ContentValues();
|
|
|
|
cv.put(EmailContent.Message.SERVER_ID, newUid);
|
|
|
|
mContext.getContentResolver().update(newMessage.getUri(), cv, null, null);
|
2009-04-28 00:17:22 +00:00
|
|
|
}
|
2009-06-22 23:13:03 +00:00
|
|
|
|
2009-05-05 23:36:31 +00:00
|
|
|
/**
|
|
|
|
* This will be called if the deleted message doesn't exist and can't be
|
|
|
|
* deleted (e.g. it was already deleted from the server.) In this case,
|
|
|
|
* attempt to delete the local copy as well.
|
|
|
|
*/
|
2009-09-10 18:52:36 +00:00
|
|
|
public void onMessageNotFound(Message message) {
|
|
|
|
mContext.getContentResolver().delete(newMessage.getUri(), null, null);
|
2009-05-05 23:36:31 +00:00
|
|
|
}
|
2010-08-24 19:37:00 +00:00
|
|
|
});
|
2009-06-25 06:53:32 +00:00
|
|
|
remoteTrashFolder.close(false);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
|
2009-09-10 18:52:36 +00:00
|
|
|
// 8. Delete the message from the remote source folder
|
2009-03-04 03:32:22 +00:00
|
|
|
remoteMessage.setFlag(Flag.DELETED, true);
|
|
|
|
remoteFolder.expunge();
|
2009-06-25 06:53:32 +00:00
|
|
|
remoteFolder.close(false);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
|
2009-09-10 23:01:48 +00:00
|
|
|
/**
|
|
|
|
* Process a pending trash message command.
|
|
|
|
*
|
|
|
|
* @param remoteStore the remote store we're working in
|
|
|
|
* @param account The account in which we are working
|
|
|
|
* @param oldMailbox The local trash mailbox
|
|
|
|
* @param oldMessage The message that was deleted from the trash
|
|
|
|
*/
|
|
|
|
private void processPendingDeleteFromTrash(Store remoteStore,
|
2011-06-13 22:32:27 +00:00
|
|
|
Account account, Mailbox oldMailbox, EmailContent.Message oldMessage)
|
2009-09-10 23:01:48 +00:00
|
|
|
throws MessagingException {
|
|
|
|
|
|
|
|
// 1. We only support delete-from-trash here
|
|
|
|
if (oldMailbox.mType != Mailbox.TYPE_TRASH) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 2. Find the remote trash folder (that we are deleting from), and open it
|
2011-04-22 22:45:11 +00:00
|
|
|
Folder remoteTrashFolder = remoteStore.getFolder(oldMailbox.mServerId);
|
2009-09-10 23:01:48 +00:00
|
|
|
if (!remoteTrashFolder.exists()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2011-06-29 21:00:43 +00:00
|
|
|
remoteTrashFolder.open(OpenMode.READ_WRITE);
|
2009-09-10 23:01:48 +00:00
|
|
|
if (remoteTrashFolder.getMode() != OpenMode.READ_WRITE) {
|
|
|
|
remoteTrashFolder.close(false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 3. Find the remote original message
|
|
|
|
Message remoteMessage = remoteTrashFolder.getMessage(oldMessage.mServerId);
|
|
|
|
if (remoteMessage == null) {
|
|
|
|
remoteTrashFolder.close(false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 4. Delete the message from the remote trash folder
|
|
|
|
remoteMessage.setFlag(Flag.DELETED, true);
|
|
|
|
remoteTrashFolder.expunge();
|
|
|
|
remoteTrashFolder.close(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Process a pending append message command. This command uploads a local message to the
|
|
|
|
* server, first checking to be sure that the server message is not newer than
|
2009-09-25 21:54:32 +00:00
|
|
|
* the local message.
|
2009-09-10 23:01:48 +00:00
|
|
|
*
|
2009-09-25 21:54:32 +00:00
|
|
|
* @param remoteStore the remote store we're working in
|
|
|
|
* @param account The account in which we are working
|
|
|
|
* @param newMailbox The mailbox we're appending to
|
|
|
|
* @param message The message we're appending
|
|
|
|
* @return true if successfully uploaded
|
2009-09-10 23:01:48 +00:00
|
|
|
*/
|
2011-06-13 22:32:27 +00:00
|
|
|
private boolean processPendingAppend(Store remoteStore, Account account,
|
2009-09-25 21:54:32 +00:00
|
|
|
Mailbox newMailbox, EmailContent.Message message)
|
2009-09-10 23:01:48 +00:00
|
|
|
throws MessagingException {
|
|
|
|
|
2009-09-25 21:54:32 +00:00
|
|
|
boolean updateInternalDate = false;
|
|
|
|
boolean updateMessage = false;
|
|
|
|
boolean deleteMessage = false;
|
2009-09-10 23:01:48 +00:00
|
|
|
|
2009-09-25 21:54:32 +00:00
|
|
|
// 1. Find the remote folder that we're appending to and create and/or open it
|
2011-04-22 22:45:11 +00:00
|
|
|
Folder remoteFolder = remoteStore.getFolder(newMailbox.mServerId);
|
2009-09-10 23:01:48 +00:00
|
|
|
if (!remoteFolder.exists()) {
|
2009-09-25 21:54:32 +00:00
|
|
|
if (!remoteFolder.canCreate(FolderType.HOLDS_MESSAGES)) {
|
|
|
|
// This is POP3, we cannot actually upload. Instead, we'll update the message
|
|
|
|
// locally with a fake serverId (so we don't keep trying here) and return.
|
|
|
|
if (message.mServerId == null || message.mServerId.length() == 0) {
|
|
|
|
message.mServerId = LOCAL_SERVERID_PREFIX + message.mId;
|
|
|
|
Uri uri =
|
|
|
|
ContentUris.withAppendedId(EmailContent.Message.CONTENT_URI, message.mId);
|
|
|
|
ContentValues cv = new ContentValues();
|
|
|
|
cv.put(EmailContent.Message.SERVER_ID, message.mServerId);
|
|
|
|
mContext.getContentResolver().update(uri, cv, null, null);
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
2009-09-10 23:01:48 +00:00
|
|
|
if (!remoteFolder.create(FolderType.HOLDS_MESSAGES)) {
|
2009-09-25 21:54:32 +00:00
|
|
|
// This is a (hopefully) transient error and we return false to try again later
|
|
|
|
return false;
|
2009-09-10 23:01:48 +00:00
|
|
|
}
|
|
|
|
}
|
2011-06-29 21:00:43 +00:00
|
|
|
remoteFolder.open(OpenMode.READ_WRITE);
|
2009-09-10 23:01:48 +00:00
|
|
|
if (remoteFolder.getMode() != OpenMode.READ_WRITE) {
|
2009-09-25 21:54:32 +00:00
|
|
|
return false;
|
2009-09-10 23:01:48 +00:00
|
|
|
}
|
|
|
|
|
2009-09-25 21:54:32 +00:00
|
|
|
// 2. If possible, load a remote message with the matching UID
|
2009-09-10 23:01:48 +00:00
|
|
|
Message remoteMessage = null;
|
2009-09-25 21:54:32 +00:00
|
|
|
if (message.mServerId != null && message.mServerId.length() > 0) {
|
|
|
|
remoteMessage = remoteFolder.getMessage(message.mServerId);
|
2009-09-10 23:01:48 +00:00
|
|
|
}
|
|
|
|
|
2009-09-25 21:54:32 +00:00
|
|
|
// 3. If a remote message could not be found, upload our local message
|
2009-09-10 23:01:48 +00:00
|
|
|
if (remoteMessage == null) {
|
2009-09-25 21:54:32 +00:00
|
|
|
// 3a. Create a legacy message to upload
|
|
|
|
Message localMessage = LegacyConversions.makeMessage(mContext, message);
|
|
|
|
|
|
|
|
// 3b. Upload it
|
2009-09-10 23:01:48 +00:00
|
|
|
FetchProfile fp = new FetchProfile();
|
|
|
|
fp.add(FetchProfile.Item.BODY);
|
|
|
|
remoteFolder.appendMessages(new Message[] { localMessage });
|
2009-09-25 21:54:32 +00:00
|
|
|
|
|
|
|
// 3b. And record the UID from the server
|
|
|
|
message.mServerId = localMessage.getUid();
|
|
|
|
updateInternalDate = true;
|
|
|
|
updateMessage = true;
|
|
|
|
} else {
|
|
|
|
// 4. If the remote message exists we need to determine which copy to keep.
|
2009-09-10 23:01:48 +00:00
|
|
|
FetchProfile fp = new FetchProfile();
|
|
|
|
fp.add(FetchProfile.Item.ENVELOPE);
|
|
|
|
remoteFolder.fetch(new Message[] { remoteMessage }, fp, null);
|
2009-09-25 21:54:32 +00:00
|
|
|
Date localDate = new Date(message.mServerTimeStamp);
|
2009-09-10 23:01:48 +00:00
|
|
|
Date remoteDate = remoteMessage.getInternalDate();
|
2010-05-20 00:23:23 +00:00
|
|
|
if (remoteDate != null && remoteDate.compareTo(localDate) > 0) {
|
2009-09-25 21:54:32 +00:00
|
|
|
// 4a. If the remote message is newer than ours we'll just
|
|
|
|
// delete ours and move on. A sync will get the server message
|
|
|
|
// if we need to be able to see it.
|
|
|
|
deleteMessage = true;
|
|
|
|
} else {
|
|
|
|
// 4b. Otherwise we'll upload our message and then delete the remote message.
|
|
|
|
|
|
|
|
// Create a legacy message to upload
|
|
|
|
Message localMessage = LegacyConversions.makeMessage(mContext, message);
|
|
|
|
|
|
|
|
// 4c. Upload it
|
2009-09-10 23:01:48 +00:00
|
|
|
fp.clear();
|
|
|
|
fp = new FetchProfile();
|
|
|
|
fp.add(FetchProfile.Item.BODY);
|
|
|
|
remoteFolder.appendMessages(new Message[] { localMessage });
|
2009-09-25 21:54:32 +00:00
|
|
|
|
|
|
|
// 4d. Record the UID and new internalDate from the server
|
|
|
|
message.mServerId = localMessage.getUid();
|
|
|
|
updateInternalDate = true;
|
|
|
|
updateMessage = true;
|
|
|
|
|
|
|
|
// 4e. And delete the old copy of the message from the server
|
2009-09-10 23:01:48 +00:00
|
|
|
remoteMessage.setFlag(Flag.DELETED, true);
|
|
|
|
}
|
|
|
|
}
|
2009-09-25 21:54:32 +00:00
|
|
|
|
|
|
|
// 5. If requested, Best-effort to capture new "internaldate" from the server
|
Watch for null results when updating internaldate.
Fixes bug # 2148971
Note, this condition happens when we APPEND a message to the server
but then we turn around and query for it, and the server reports that
it's not there:
>>> 7 APPEND "Sent" {490}
<<< #null# [Ready, for, argument]
<<< #null# [194, EXISTS]
<<< #7# [OK, [APPENDUID, 1233339552, 1191], APPEND, completed]
>>> 8 UID SEARCH (HEADER MESSAGE-ID \
<pwyc0i5yviwvu5nwio2lwdo5.1254073239445@email.android.com>)
<<< #null# [SEARCH]
<<< #8# [OK, UID, SEARCH, completed]
>>> 9 UID SEARCH UID NULL
<<< #9# [BAD, UID, SEARCH, error;, bogus, or, incorrect, syntax, \
of, search, critera]
I don't know why the Yahoo! server did this, but now we'll at least not
crash if they do it again.
2009-09-27 21:35:11 +00:00
|
|
|
if (updateInternalDate && message.mServerId != null) {
|
2009-09-25 21:54:32 +00:00
|
|
|
try {
|
|
|
|
Message remoteMessage2 = remoteFolder.getMessage(message.mServerId);
|
Watch for null results when updating internaldate.
Fixes bug # 2148971
Note, this condition happens when we APPEND a message to the server
but then we turn around and query for it, and the server reports that
it's not there:
>>> 7 APPEND "Sent" {490}
<<< #null# [Ready, for, argument]
<<< #null# [194, EXISTS]
<<< #7# [OK, [APPENDUID, 1233339552, 1191], APPEND, completed]
>>> 8 UID SEARCH (HEADER MESSAGE-ID \
<pwyc0i5yviwvu5nwio2lwdo5.1254073239445@email.android.com>)
<<< #null# [SEARCH]
<<< #8# [OK, UID, SEARCH, completed]
>>> 9 UID SEARCH UID NULL
<<< #9# [BAD, UID, SEARCH, error;, bogus, or, incorrect, syntax, \
of, search, critera]
I don't know why the Yahoo! server did this, but now we'll at least not
crash if they do it again.
2009-09-27 21:35:11 +00:00
|
|
|
if (remoteMessage2 != null) {
|
|
|
|
FetchProfile fp2 = new FetchProfile();
|
|
|
|
fp2.add(FetchProfile.Item.ENVELOPE);
|
|
|
|
remoteFolder.fetch(new Message[] { remoteMessage2 }, fp2, null);
|
|
|
|
message.mServerTimeStamp = remoteMessage2.getInternalDate().getTime();
|
|
|
|
updateMessage = true;
|
|
|
|
}
|
2009-09-25 21:54:32 +00:00
|
|
|
} catch (MessagingException me) {
|
|
|
|
// skip it - we can live without this
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 6. Perform required edits to local copy of message
|
|
|
|
if (deleteMessage || updateMessage) {
|
|
|
|
Uri uri = ContentUris.withAppendedId(EmailContent.Message.CONTENT_URI, message.mId);
|
|
|
|
ContentResolver resolver = mContext.getContentResolver();
|
|
|
|
if (deleteMessage) {
|
|
|
|
resolver.delete(uri, null, null);
|
|
|
|
} else if (updateMessage) {
|
|
|
|
ContentValues cv = new ContentValues();
|
|
|
|
cv.put(EmailContent.Message.SERVER_ID, message.mServerId);
|
|
|
|
cv.put(EmailContent.Message.SERVER_TIMESTAMP, message.mServerTimeStamp);
|
|
|
|
resolver.update(uri, cv, null, null);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
2009-09-10 23:01:48 +00:00
|
|
|
}
|
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
/**
|
2009-08-27 06:12:02 +00:00
|
|
|
* Finish loading a message that have been partially downloaded.
|
2009-08-25 03:23:39 +00:00
|
|
|
*
|
|
|
|
* @param messageId the message to load
|
|
|
|
* @param listener the callback by which results will be reported
|
|
|
|
*/
|
|
|
|
public void loadMessageForView(final long messageId, MessagingListener listener) {
|
|
|
|
mListeners.loadMessageForViewStarted(messageId);
|
2009-03-04 03:32:22 +00:00
|
|
|
put("loadMessageForViewRemote", listener, new Runnable() {
|
|
|
|
public void run() {
|
|
|
|
try {
|
2009-08-25 03:23:39 +00:00
|
|
|
// 1. Resample the message, in case it disappeared or synced while
|
|
|
|
// this command was in queue
|
|
|
|
EmailContent.Message message =
|
|
|
|
EmailContent.Message.restoreMessageWithId(mContext, messageId);
|
|
|
|
if (message == null) {
|
|
|
|
mListeners.loadMessageForViewFailed(messageId, "Unknown message");
|
2009-03-04 03:32:22 +00:00
|
|
|
return;
|
|
|
|
}
|
2009-09-10 18:52:36 +00:00
|
|
|
if (message.mFlagLoaded == EmailContent.Message.FLAG_LOADED_COMPLETE) {
|
2009-08-25 03:23:39 +00:00
|
|
|
mListeners.loadMessageForViewFinished(messageId);
|
|
|
|
return;
|
2009-05-01 21:40:58 +00:00
|
|
|
}
|
2009-06-22 23:13:03 +00:00
|
|
|
|
2009-08-25 03:23:39 +00:00
|
|
|
// 2. Open the remote folder.
|
|
|
|
// TODO combine with common code in loadAttachment
|
2011-06-29 19:47:56 +00:00
|
|
|
Account account = Account.restoreAccountWithId(mContext, message.mAccountKey);
|
|
|
|
Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, message.mMailboxKey);
|
2009-09-16 17:58:54 +00:00
|
|
|
if (account == null || mailbox == null) {
|
|
|
|
mListeners.loadMessageForViewFailed(messageId, "null account or mailbox");
|
|
|
|
return;
|
|
|
|
}
|
2009-08-25 03:23:39 +00:00
|
|
|
|
2011-06-29 19:47:56 +00:00
|
|
|
Store remoteStore = Store.getInstance(account, mContext);
|
2011-06-23 17:52:21 +00:00
|
|
|
String remoteServerId = mailbox.mServerId;
|
|
|
|
// If this is a search result, use the protocolSearchInfo field to get the
|
|
|
|
// correct remote location
|
|
|
|
if (!TextUtils.isEmpty(message.mProtocolSearchInfo)) {
|
|
|
|
remoteServerId = message.mProtocolSearchInfo;
|
|
|
|
}
|
|
|
|
Folder remoteFolder = remoteStore.getFolder(remoteServerId);
|
2011-06-29 21:00:43 +00:00
|
|
|
remoteFolder.open(OpenMode.READ_WRITE);
|
2009-08-25 03:23:39 +00:00
|
|
|
|
2011-06-23 17:52:21 +00:00
|
|
|
// 3. Set up to download the entire message
|
|
|
|
Message remoteMessage = remoteFolder.getMessage(remoteServerId);
|
2009-05-01 21:40:58 +00:00
|
|
|
FetchProfile fp = new FetchProfile();
|
|
|
|
fp.add(FetchProfile.Item.BODY);
|
2009-08-25 03:23:39 +00:00
|
|
|
remoteFolder.fetch(new Message[] { remoteMessage }, fp, null);
|
2009-03-04 03:32:22 +00:00
|
|
|
|
2011-06-23 17:52:21 +00:00
|
|
|
// 4. Write to provider
|
2009-08-25 03:23:39 +00:00
|
|
|
copyOneMessageToProvider(remoteMessage, account, mailbox,
|
2009-09-10 18:52:36 +00:00
|
|
|
EmailContent.Message.FLAG_LOADED_COMPLETE);
|
2009-03-04 03:32:22 +00:00
|
|
|
|
2011-06-23 17:52:21 +00:00
|
|
|
// 5. Notify UI
|
2009-08-25 03:23:39 +00:00
|
|
|
mListeners.loadMessageForViewFinished(messageId);
|
2009-03-04 03:32:22 +00:00
|
|
|
|
2009-08-25 03:23:39 +00:00
|
|
|
} catch (MessagingException me) {
|
2011-05-13 18:20:04 +00:00
|
|
|
if (Logging.LOGD) Log.v(Logging.LOG_TAG, "", me);
|
2009-08-25 03:23:39 +00:00
|
|
|
mListeners.loadMessageForViewFailed(messageId, me.getMessage());
|
|
|
|
} catch (RuntimeException rte) {
|
|
|
|
mListeners.loadMessageForViewFailed(messageId, rte.getMessage());
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2009-08-19 20:15:13 +00:00
|
|
|
* Attempts to load the attachment specified by id from the given account and message.
|
2009-03-04 03:32:22 +00:00
|
|
|
*/
|
2009-08-19 20:15:13 +00:00
|
|
|
public void loadAttachment(final long accountId, final long messageId, final long mailboxId,
|
2011-01-21 19:58:39 +00:00
|
|
|
final long attachmentId, MessagingListener listener, final boolean background) {
|
2009-08-19 20:15:13 +00:00
|
|
|
mListeners.loadAttachmentStarted(accountId, messageId, attachmentId, true);
|
2009-03-04 03:32:22 +00:00
|
|
|
|
|
|
|
put("loadAttachment", listener, new Runnable() {
|
|
|
|
public void run() {
|
|
|
|
try {
|
2009-10-17 15:37:39 +00:00
|
|
|
//1. Check if the attachment is already here and return early in that case
|
|
|
|
Attachment attachment =
|
|
|
|
Attachment.restoreAttachmentWithId(mContext, attachmentId);
|
|
|
|
if (attachment == null) {
|
|
|
|
mListeners.loadAttachmentFailed(accountId, messageId, attachmentId,
|
2011-01-21 19:58:39 +00:00
|
|
|
new MessagingException("The attachment is null"),
|
|
|
|
background);
|
2009-10-17 15:37:39 +00:00
|
|
|
return;
|
|
|
|
}
|
2010-08-25 00:17:54 +00:00
|
|
|
if (Utility.attachmentExists(mContext, attachment)) {
|
2009-10-17 15:37:39 +00:00
|
|
|
mListeners.loadAttachmentFinished(accountId, messageId, attachmentId);
|
|
|
|
return;
|
|
|
|
}
|
2009-08-19 20:15:13 +00:00
|
|
|
|
|
|
|
// 2. Open the remote folder.
|
|
|
|
// TODO all of these could be narrower projections
|
2011-06-29 19:47:56 +00:00
|
|
|
Account account = Account.restoreAccountWithId(mContext, accountId);
|
|
|
|
Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, mailboxId);
|
2009-08-19 20:15:13 +00:00
|
|
|
EmailContent.Message message =
|
|
|
|
EmailContent.Message.restoreMessageWithId(mContext, messageId);
|
2009-10-17 15:37:39 +00:00
|
|
|
|
|
|
|
if (account == null || mailbox == null || message == null) {
|
2009-09-16 17:58:54 +00:00
|
|
|
mListeners.loadAttachmentFailed(accountId, messageId, attachmentId,
|
2010-08-18 15:50:45 +00:00
|
|
|
new MessagingException(
|
2011-01-21 19:58:39 +00:00
|
|
|
"Account, mailbox, message or attachment are null"),
|
|
|
|
background);
|
2009-09-16 17:58:54 +00:00
|
|
|
return;
|
|
|
|
}
|
2009-08-19 20:15:13 +00:00
|
|
|
|
2011-06-29 19:47:56 +00:00
|
|
|
Store remoteStore = Store.getInstance(account, mContext);
|
2011-04-22 22:45:11 +00:00
|
|
|
Folder remoteFolder = remoteStore.getFolder(mailbox.mServerId);
|
2011-06-29 21:00:43 +00:00
|
|
|
remoteFolder.open(OpenMode.READ_WRITE);
|
2009-08-19 20:15:13 +00:00
|
|
|
|
|
|
|
// 3. Generate a shell message in which to retrieve the attachment,
|
|
|
|
// and a shell BodyPart for the attachment. Then glue them together.
|
|
|
|
Message storeMessage = remoteFolder.createMessage(message.mServerId);
|
2009-10-07 18:42:27 +00:00
|
|
|
MimeBodyPart storePart = new MimeBodyPart();
|
|
|
|
storePart.setSize((int)attachment.mSize);
|
2009-08-19 20:15:13 +00:00
|
|
|
storePart.setHeader(MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA,
|
|
|
|
attachment.mLocation);
|
|
|
|
storePart.setHeader(MimeHeader.HEADER_CONTENT_TYPE,
|
|
|
|
String.format("%s;\n name=\"%s\"",
|
|
|
|
attachment.mMimeType,
|
|
|
|
attachment.mFileName));
|
|
|
|
// TODO is this always true for attachments? I think we dropped the
|
|
|
|
// true encoding along the way
|
|
|
|
storePart.setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, "base64");
|
|
|
|
|
|
|
|
MimeMultipart multipart = new MimeMultipart();
|
|
|
|
multipart.setSubType("mixed");
|
|
|
|
multipart.addBodyPart(storePart);
|
|
|
|
|
|
|
|
storeMessage.setHeader(MimeHeader.HEADER_CONTENT_TYPE, "multipart/mixed");
|
|
|
|
storeMessage.setBody(multipart);
|
|
|
|
|
|
|
|
// 4. Now ask for the attachment to be fetched
|
2009-03-04 03:32:22 +00:00
|
|
|
FetchProfile fp = new FetchProfile();
|
2009-08-19 20:15:13 +00:00
|
|
|
fp.add(storePart);
|
2010-08-18 15:50:45 +00:00
|
|
|
remoteFolder.fetch(new Message[] { storeMessage }, fp,
|
2010-12-10 21:36:18 +00:00
|
|
|
mController.new MessageRetrievalListenerBridge(
|
|
|
|
messageId, attachmentId));
|
2010-08-18 15:50:45 +00:00
|
|
|
|
|
|
|
// If we failed to load the attachment, throw an Exception here, so that
|
|
|
|
// AttachmentDownloadService knows that we failed
|
|
|
|
if (storePart.getBody() == null) {
|
|
|
|
throw new MessagingException("Attachment not loaded.");
|
|
|
|
}
|
2009-08-19 20:15:13 +00:00
|
|
|
|
|
|
|
// 5. Save the downloaded file and update the attachment as necessary
|
|
|
|
LegacyConversions.saveAttachmentBody(mContext, storePart, attachment,
|
|
|
|
accountId);
|
|
|
|
|
|
|
|
// 6. Report success
|
|
|
|
mListeners.loadAttachmentFinished(accountId, messageId, attachmentId);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
catch (MessagingException me) {
|
2011-05-13 18:20:04 +00:00
|
|
|
if (Logging.LOGD) Log.v(Logging.LOG_TAG, "", me);
|
2011-01-21 19:58:39 +00:00
|
|
|
mListeners.loadAttachmentFailed(
|
|
|
|
accountId, messageId, attachmentId, me, background);
|
2009-08-19 20:15:13 +00:00
|
|
|
} catch (IOException ioe) {
|
2011-02-11 23:05:17 +00:00
|
|
|
Log.e(Logging.LOG_TAG, "Error while storing attachment." + ioe.toString());
|
2009-08-19 20:15:13 +00:00
|
|
|
}
|
|
|
|
}});
|
|
|
|
}
|
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
/**
|
|
|
|
* Attempt to send any messages that are sitting in the Outbox.
|
|
|
|
* @param account
|
|
|
|
* @param listener
|
|
|
|
*/
|
2011-06-13 22:32:27 +00:00
|
|
|
public void sendPendingMessages(final Account account, final long sentFolderId,
|
2009-08-20 18:09:39 +00:00
|
|
|
MessagingListener listener) {
|
2009-03-04 03:32:22 +00:00
|
|
|
put("sendPendingMessages", listener, new Runnable() {
|
|
|
|
public void run() {
|
2009-08-20 18:09:39 +00:00
|
|
|
sendPendingMessagesSynchronous(account, sentFolderId);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2011-04-20 15:04:46 +00:00
|
|
|
* Attempt to send all messages sitting in the given account's outbox. Optionally,
|
|
|
|
* if the server requires it, the message will be moved to the given sent folder.
|
2009-03-04 03:32:22 +00:00
|
|
|
*/
|
2011-06-13 22:32:27 +00:00
|
|
|
public void sendPendingMessagesSynchronous(final Account account,
|
2009-08-20 18:09:39 +00:00
|
|
|
long sentFolderId) {
|
2010-10-18 20:14:20 +00:00
|
|
|
NotificationController nc = NotificationController.getInstance(mContext);
|
2009-08-20 18:09:39 +00:00
|
|
|
// 1. Loop through all messages in the account's outbox
|
|
|
|
long outboxId = Mailbox.findMailboxOfType(mContext, account.mId, Mailbox.TYPE_OUTBOX);
|
|
|
|
if (outboxId == Mailbox.NO_MAILBOX) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
ContentResolver resolver = mContext.getContentResolver();
|
|
|
|
Cursor c = resolver.query(EmailContent.Message.CONTENT_URI,
|
|
|
|
EmailContent.Message.ID_COLUMN_PROJECTION,
|
|
|
|
EmailContent.Message.MAILBOX_KEY + "=?", new String[] { Long.toString(outboxId) },
|
|
|
|
null);
|
2009-03-04 03:32:22 +00:00
|
|
|
try {
|
2009-08-20 18:09:39 +00:00
|
|
|
// 2. exit early
|
|
|
|
if (c.getCount() <= 0) {
|
2009-03-04 03:32:22 +00:00
|
|
|
return;
|
|
|
|
}
|
2009-08-20 18:09:39 +00:00
|
|
|
// 3. do one-time setup of the Sender & other stuff
|
2009-09-18 22:31:37 +00:00
|
|
|
mListeners.sendPendingMessagesStarted(account.mId, -1);
|
|
|
|
|
2011-04-20 15:04:46 +00:00
|
|
|
Sender sender = Sender.getInstance(mContext, account);
|
2011-06-29 19:47:56 +00:00
|
|
|
Store remoteStore = Store.getInstance(account, mContext);
|
2009-08-20 18:09:39 +00:00
|
|
|
boolean requireMoveMessageToSentFolder = remoteStore.requireCopyMessageToSentFolder();
|
|
|
|
ContentValues moveToSentValues = null;
|
|
|
|
if (requireMoveMessageToSentFolder) {
|
|
|
|
moveToSentValues = new ContentValues();
|
|
|
|
moveToSentValues.put(MessageColumns.MAILBOX_KEY, sentFolderId);
|
|
|
|
}
|
2009-06-22 23:13:03 +00:00
|
|
|
|
2009-08-20 18:09:39 +00:00
|
|
|
// 4. loop through the available messages and send them
|
|
|
|
while (c.moveToNext()) {
|
|
|
|
long messageId = -1;
|
|
|
|
try {
|
|
|
|
messageId = c.getLong(0);
|
|
|
|
mListeners.sendPendingMessagesStarted(account.mId, messageId);
|
2010-08-18 15:50:45 +00:00
|
|
|
// Don't send messages with unloaded attachments
|
|
|
|
if (Utility.hasUnloadedAttachments(mContext, messageId)) {
|
|
|
|
if (Email.DEBUG) {
|
2011-02-11 23:05:17 +00:00
|
|
|
Log.d(Logging.LOG_TAG, "Can't send #" + messageId +
|
2010-08-18 15:50:45 +00:00
|
|
|
"; unloaded attachments");
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
2009-08-20 18:09:39 +00:00
|
|
|
sender.sendMessage(messageId);
|
|
|
|
} catch (MessagingException me) {
|
|
|
|
// report error for this message, but keep trying others
|
2010-10-18 20:14:20 +00:00
|
|
|
if (me instanceof AuthenticationFailedException) {
|
|
|
|
nc.showLoginFailedNotification(account.mId);
|
|
|
|
}
|
2009-08-20 18:09:39 +00:00
|
|
|
mListeners.sendPendingMessagesFailed(account.mId, messageId, me);
|
|
|
|
continue;
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
2009-08-20 18:09:39 +00:00
|
|
|
// 5. move to sent, or delete
|
2009-09-25 21:54:32 +00:00
|
|
|
Uri syncedUri =
|
|
|
|
ContentUris.withAppendedId(EmailContent.Message.SYNCED_CONTENT_URI, messageId);
|
2009-08-20 18:09:39 +00:00
|
|
|
if (requireMoveMessageToSentFolder) {
|
2010-08-18 15:50:45 +00:00
|
|
|
// If this is a forwarded message and it has attachments, delete them, as they
|
|
|
|
// duplicate information found elsewhere (on the server). This saves storage.
|
|
|
|
EmailContent.Message msg =
|
|
|
|
EmailContent.Message.restoreMessageWithId(mContext, messageId);
|
|
|
|
if (msg != null &&
|
|
|
|
((msg.mFlags & EmailContent.Message.FLAG_TYPE_FORWARD) != 0)) {
|
2011-02-09 01:50:30 +00:00
|
|
|
AttachmentUtilities.deleteAllAttachmentFiles(mContext, account.mId,
|
2010-08-18 15:50:45 +00:00
|
|
|
messageId);
|
|
|
|
}
|
2009-09-25 21:54:32 +00:00
|
|
|
resolver.update(syncedUri, moveToSentValues, null, null);
|
2009-08-20 18:09:39 +00:00
|
|
|
} else {
|
2011-02-09 01:50:30 +00:00
|
|
|
AttachmentUtilities.deleteAllAttachmentFiles(mContext, account.mId,
|
|
|
|
messageId);
|
2009-09-25 21:54:32 +00:00
|
|
|
Uri uri =
|
|
|
|
ContentUris.withAppendedId(EmailContent.Message.CONTENT_URI, messageId);
|
2009-08-20 18:09:39 +00:00
|
|
|
resolver.delete(uri, null, null);
|
2009-09-25 21:54:32 +00:00
|
|
|
resolver.delete(syncedUri, null, null);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
}
|
2009-08-20 18:09:39 +00:00
|
|
|
// 6. report completion/success
|
|
|
|
mListeners.sendPendingMessagesCompleted(account.mId);
|
2010-10-18 20:14:20 +00:00
|
|
|
nc.cancelLoginFailedNotification(account.mId);
|
2009-08-20 18:09:39 +00:00
|
|
|
} catch (MessagingException me) {
|
2010-10-18 20:14:20 +00:00
|
|
|
if (me instanceof AuthenticationFailedException) {
|
|
|
|
nc.showLoginFailedNotification(account.mId);
|
|
|
|
}
|
2009-08-20 18:09:39 +00:00
|
|
|
mListeners.sendPendingMessagesFailed(account.mId, -1, me);
|
|
|
|
} finally {
|
|
|
|
c.close();
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2010-08-16 21:14:40 +00:00
|
|
|
* Checks mail for an account.
|
|
|
|
* This entry point is for use by the mail checking service only, because it
|
2009-08-18 07:54:34 +00:00
|
|
|
* gives slightly different callbacks (so the service doesn't get confused by callbacks
|
|
|
|
* triggered by/for the foreground UI.
|
2009-06-22 23:13:03 +00:00
|
|
|
*
|
2009-08-18 07:54:34 +00:00
|
|
|
* TODO clean up the execution model which is unnecessarily threaded due to legacy code
|
2009-03-04 03:32:22 +00:00
|
|
|
*
|
2009-08-18 07:54:34 +00:00
|
|
|
* @param accountId the account to check
|
2009-03-04 03:32:22 +00:00
|
|
|
* @param listener
|
|
|
|
*/
|
2009-08-18 07:54:34 +00:00
|
|
|
public void checkMail(final long accountId, final long tag, final MessagingListener listener) {
|
|
|
|
mListeners.checkMailStarted(mContext, accountId, tag);
|
|
|
|
|
|
|
|
// This puts the command on the queue (not synchronous)
|
|
|
|
listFolders(accountId, null);
|
|
|
|
|
|
|
|
// Put this on the queue as well so it follows listFolders
|
2009-08-20 18:09:39 +00:00
|
|
|
put("checkMail", listener, new Runnable() {
|
2009-04-27 16:18:01 +00:00
|
|
|
public void run() {
|
2009-08-20 18:09:39 +00:00
|
|
|
// send any pending outbound messages. note, there is a slight race condition
|
|
|
|
// here if we somehow don't have a sent folder, but this should never happen
|
|
|
|
// because the call to sendMessage() would have built one previously.
|
2009-09-16 17:58:54 +00:00
|
|
|
long inboxId = -1;
|
2011-06-29 19:47:56 +00:00
|
|
|
Account account = Account.restoreAccountWithId(mContext, accountId);
|
2009-09-16 17:58:54 +00:00
|
|
|
if (account != null) {
|
|
|
|
long sentboxId = Mailbox.findMailboxOfType(mContext, accountId,
|
|
|
|
Mailbox.TYPE_SENT);
|
|
|
|
if (sentboxId != Mailbox.NO_MAILBOX) {
|
|
|
|
sendPendingMessagesSynchronous(account, sentboxId);
|
|
|
|
}
|
|
|
|
// find mailbox # for inbox and sync it.
|
|
|
|
// TODO we already know this in Controller, can we pass it in?
|
|
|
|
inboxId = Mailbox.findMailboxOfType(mContext, accountId, Mailbox.TYPE_INBOX);
|
|
|
|
if (inboxId != Mailbox.NO_MAILBOX) {
|
2011-05-14 00:26:27 +00:00
|
|
|
Mailbox mailbox =
|
|
|
|
Mailbox.restoreMailboxWithId(mContext, inboxId);
|
2009-09-16 17:58:54 +00:00
|
|
|
if (mailbox != null) {
|
|
|
|
synchronizeMailboxSynchronous(account, mailbox);
|
|
|
|
}
|
|
|
|
}
|
2009-08-20 18:09:39 +00:00
|
|
|
}
|
2010-04-30 21:41:47 +00:00
|
|
|
mListeners.checkMailFinished(mContext, accountId, inboxId, tag);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2009-06-08 22:27:17 +00:00
|
|
|
private static class Command {
|
2009-03-04 03:32:22 +00:00
|
|
|
public Runnable runnable;
|
|
|
|
|
|
|
|
public MessagingListener listener;
|
|
|
|
|
|
|
|
public String description;
|
2009-06-22 23:13:03 +00:00
|
|
|
|
2009-04-24 23:17:04 +00:00
|
|
|
@Override
|
|
|
|
public String toString() {
|
|
|
|
return description;
|
|
|
|
}
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
2011-05-03 21:42:26 +00:00
|
|
|
|
|
|
|
/** Results of the latest synchronization. */
|
|
|
|
private static class SyncResults {
|
|
|
|
/** The total # of messages in the folder */
|
|
|
|
public final int mTotalMessages;
|
|
|
|
/** A list of new message IDs; must not be {@code null} */
|
|
|
|
public final ArrayList<Long> mAddedMessages;
|
|
|
|
|
|
|
|
public SyncResults(int totalMessages, ArrayList<Long> addedMessages) {
|
|
|
|
if (addedMessages == null) {
|
|
|
|
throw new IllegalArgumentException("addedMessages must not be null");
|
|
|
|
}
|
|
|
|
mTotalMessages = totalMessages;
|
|
|
|
mAddedMessages = addedMessages;
|
|
|
|
}
|
|
|
|
}
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|