Merge change 22840 into eclair

* changes:
  Sync "read" and "favorite" back to server.
This commit is contained in:
Android (Google) Code Review 2009-08-26 23:14:28 -07:00
commit 2ded414e99
5 changed files with 223 additions and 175 deletions

View File

@ -588,36 +588,57 @@ public class Controller {
/**
* Set/clear the unread status of a message
*
* TODO db ops should not be in this thread. queue it up.
*
* @param messageId the message to update
* @param isRead the new value for the isRead flag
*/
public void setMessageRead(long messageId, boolean isRead) {
// TODO this should not be in this thread. queue it up.
// TODO Also, it needs to update the read/unread count in the mailbox
// TODO kick off service/messagingcontroller actions
public void setMessageRead(final long messageId, boolean isRead) {
ContentValues cv = new ContentValues();
cv.put(EmailContent.MessageColumns.FLAG_READ, isRead);
Uri uri = ContentUris.withAppendedId(
EmailContent.Message.SYNCED_CONTENT_URI, messageId);
mProviderContext.getContentResolver().update(uri, cv, null, null);
// Service runs automatically, MessagingController needs a kick
final Message message = Message.restoreMessageWithId(mContext, messageId);
Account account = Account.restoreAccountWithId(mContext, message.mAccountKey);
if (isMessagingController(account)) {
new Thread() {
@Override
public void run() {
mLegacyController.processPendingCommands(message.mAccountKey);
}
}.start();
}
}
/**
* Set/clear the favorite status of a message
*
* TODO db ops should not be in this thread. queue it up.
*
* @param messageId the message to update
* @param isFavorite the new value for the isFavorite flag
*/
public void setMessageFavorite(long messageId, boolean isFavorite) {
// TODO this should not be in this thread. queue it up.
// TODO kick off service/messagingcontroller actions
public void setMessageFavorite(final long messageId, boolean isFavorite) {
ContentValues cv = new ContentValues();
cv.put(EmailContent.MessageColumns.FLAG_FAVORITE, isFavorite);
Uri uri = ContentUris.withAppendedId(
EmailContent.Message.SYNCED_CONTENT_URI, messageId);
mProviderContext.getContentResolver().update(uri, cv, null, null);
// Service runs automatically, MessagingController needs a kick
final Message message = Message.restoreMessageWithId(mContext, messageId);
Account account = Account.restoreAccountWithId(mContext, message.mAccountKey);
if (isMessagingController(account)) {
new Thread() {
@Override
public void run() {
mLegacyController.processPendingCommands(message.mAccountKey);
}
}.start();
}
}
/**
@ -689,6 +710,8 @@ public class Controller {
/**
* For a given account id, return a service proxy if applicable, or null.
*
* TODO this should use a cache because we'll be doing this a lot
*
* @param accountId the message of interest
* @result service proxy, or null if n/a
*/

View File

@ -97,12 +97,8 @@ public class MessagingController implements Runnable {
*/
private static final int MAX_SMALL_MESSAGE_SIZE = (25 * 1024);
private static final String PENDING_COMMAND_TRASH =
"com.android.email.MessagingController.trash";
private static final String PENDING_COMMAND_MARK_READ =
"com.android.email.MessagingController.markRead";
private static final String PENDING_COMMAND_APPEND =
"com.android.email.MessagingController.append";
private static Flag[] FLAG_LIST_SEEN = new Flag[] { Flag.SEEN };
private static Flag[] FLAG_LIST_FLAGGED = new Flag[] { Flag.FLAGGED };
/**
* Projections & CVs used by pruneCachedAttachments
@ -1092,16 +1088,26 @@ public class MessagingController implements Runnable {
Log.e(Email.LOG_TAG, "Error while storing attachment." + ioe.toString());
}
}
private void queuePendingCommand(EmailContent.Account account, PendingCommand command) {
try {
LocalStore localStore = (LocalStore) Store.getInstance(
account.getLocalStoreUri(mContext), mContext, null);
localStore.addPendingCommand(command);
}
catch (Exception e) {
throw new RuntimeException("Unable to enqueue pending command", e);
}
public void processPendingCommands(final long accountId) {
put("processPendingCommands", null, new Runnable() {
public void run() {
try {
EmailContent.Account account =
EmailContent.Account.restoreAccountWithId(mContext, accountId);
processPendingCommandsSynchronous(account);
}
catch (MessagingException me) {
if (Email.LOGD) {
Log.v(Email.LOG_TAG, "processPendingCommands", me);
}
/*
* Ignore any exceptions from the commands. Commands will be processed
* on the next round.
*/
}
}
});
}
private void processPendingCommands(final EmailContent.Account account) {
@ -1123,27 +1129,107 @@ public class MessagingController implements Runnable {
});
}
/**
* Find messages in the updated table that need to be written back to server.
*
* Handles:
* Read/Unread
* Flagged
* TODO:
* Delete
* Append
* Move
*
* TODO: tighter projections
*
* @param account the account to update
* @throws MessagingException
*/
private void processPendingCommandsSynchronous(EmailContent.Account account)
throws MessagingException {
LocalStore localStore = (LocalStore) Store.getInstance(
account.getLocalStoreUri(mContext), mContext, null);
ArrayList<PendingCommand> commands = localStore.getPendingCommands();
for (PendingCommand command : commands) {
/*
* We specifically do not catch any exceptions here. If a command fails it is
* most likely due to a server or IO error and it must be retried before any
* other command processes. This maintains the order of the commands.
*/
if (PENDING_COMMAND_APPEND.equals(command.command)) {
processPendingAppend(command, account);
throws MessagingException {
ContentResolver resolver = mContext.getContentResolver();
String[] accountIdArgs = new String[] { Long.toString(account.mId) };
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;
// Demand load mailbox (note order-by to reduce thrashing here
Mailbox mailbox = null;
// loop through messages marked as needing updates
while (updates.moveToNext()) {
boolean changeRead = false;
boolean changeFlagged = false;
EmailContent.Message oldMessage =
EmailContent.getContent(updates, EmailContent.Message.class);
lastMessageId = oldMessage.mId;
EmailContent.Message newMessage =
EmailContent.Message.restoreMessageWithId(mContext, oldMessage.mId);
if (newMessage != null) {
changeRead = oldMessage.mFlagRead != newMessage.mFlagRead;
changeFlagged = oldMessage.mFlagFavorite != newMessage.mFlagFavorite;
}
// Handle simple flag changes
if (changeRead || changeFlagged) {
if (remoteStore == null) {
remoteStore = Store.getInstance(account.getStoreUri(mContext), mContext,
null);
}
if (mailbox == null || mailbox.mId != newMessage.mMailboxKey) {
mailbox =
Mailbox.restoreMailboxWithId(mContext, newMessage.mMailboxKey);
}
Folder remoteFolder = remoteStore.getFolder(mailbox.mDisplayName);
if (remoteFolder.exists()) {
remoteFolder.open(OpenMode.READ_WRITE, null);
if (remoteFolder.getMode() == OpenMode.READ_WRITE) {
// Finally, apply the changes to the message
Message remoteMessage =
remoteFolder.getMessage(newMessage.mServerId);
if (remoteMessage != null) {
if (Email.DEBUG) {
Log.d(Email.LOG_TAG,
"Update flags for msg id=" + newMessage.mId
+ " read=" + newMessage.mFlagRead
+ " flagged=" + newMessage.mFlagFavorite);
}
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);
}
}
}
}
}
// TODO other changes!
// Finally, delete the update
Uri uri = ContentUris.withAppendedId(EmailContent.Message.UPDATED_CONTENT_URI,
oldMessage.mId);
resolver.delete(uri, null, null);
}
else if (PENDING_COMMAND_MARK_READ.equals(command.command)) {
processPendingMarkRead(command, account);
} 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) {
Log.d(Email.LOG_TAG, "Unable to process pending update for id="
+ lastMessageId + ": " + me);
}
else if (PENDING_COMMAND_TRASH.equals(command.command)) {
processPendingTrash(command, account);
}
localStore.removePendingCommand(command);
} finally {
updates.close();
}
}
@ -1342,75 +1428,7 @@ public class MessagingController implements Runnable {
}
/**
* Processes a pending mark read or unread command.
*
* @param command arguments = (String folder, String uid, boolean read)
* @param account
*/
private void processPendingMarkRead(PendingCommand command, EmailContent.Account account)
throws MessagingException {
String folder = command.arguments[0];
String uid = command.arguments[1];
boolean read = Boolean.parseBoolean(command.arguments[2]);
LocalStore localStore = (LocalStore) Store.getInstance(
account.getLocalStoreUri(mContext), mContext, null);
LocalFolder localFolder = (LocalFolder) localStore.getFolder(folder);
Store remoteStore = Store.getInstance(account.getStoreUri(mContext), mContext,
localStore.getPersistentCallbacks());
Folder remoteFolder = remoteStore.getFolder(folder);
if (!remoteFolder.exists()) {
return;
}
remoteFolder.open(OpenMode.READ_WRITE, localFolder.getPersistentCallbacks());
if (remoteFolder.getMode() != OpenMode.READ_WRITE) {
return;
}
Message remoteMessage = null;
if (!uid.startsWith("Local")
&& !uid.contains("-")) {
remoteMessage = remoteFolder.getMessage(uid);
}
if (remoteMessage == null) {
return;
}
remoteMessage.setFlag(Flag.SEEN, read);
}
/**
* Mark the message with the given account, folder and uid either Seen or not Seen.
* @param account
* @param folder
* @param uid
* @param seen
*/
public void markMessageRead(
final EmailContent.Account account,
final String folder,
final String uid,
final boolean seen) {
try {
Store localStore = Store.getInstance(account.getLocalStoreUri(mContext), mContext,
null);
Folder localFolder = localStore.getFolder(folder);
localFolder.open(OpenMode.READ_WRITE, null);
Message message = localFolder.getMessage(uid);
message.setFlag(Flag.SEEN, seen);
PendingCommand command = new PendingCommand();
command.command = PENDING_COMMAND_MARK_READ;
command.arguments = new String[] { folder, uid, Boolean.toString(seen) };
queuePendingCommand(account, command);
processPendingCommands(account);
}
catch (MessagingException me) {
throw new RuntimeException(me);
}
}
/**
* Finiah loading a message that have been partially downloaded.
* Finish loading a message that have been partially downloaded.
*
* @param messageId the message to load
* @param listener the callback by which results will be reported
@ -1695,29 +1713,31 @@ public class MessagingController implements Runnable {
*/
public void deleteMessage(final EmailContent.Account account, final String folder,
final Message message, MessagingListener listener) {
if (folder.equals(account.getTrashFolderName(mContext))) {
return;
}
try {
Store localStore = Store.getInstance(account.getLocalStoreUri(mContext), mContext,
null);
Folder localFolder = localStore.getFolder(folder);
Folder localTrashFolder = localStore.getFolder(account.getTrashFolderName(mContext));
// TODO rewrite using provider updates
localFolder.copyMessages(new Message[] { message }, localTrashFolder, null);
message.setFlag(Flag.DELETED, true);
if (account.getDeletePolicy() == Account.DELETE_POLICY_ON_DELETE) {
PendingCommand command = new PendingCommand();
command.command = PENDING_COMMAND_TRASH;
command.arguments = new String[] { folder, message.getUid() };
queuePendingCommand(account, command);
processPendingCommands(account);
}
}
catch (MessagingException me) {
throw new RuntimeException("Error deleting message from local store.", me);
}
// if (folder.equals(account.getTrashFolderName(mContext))) {
// return;
// }
// try {
// Store localStore = Store.getInstance(account.getLocalStoreUri(mContext), mContext,
// null);
// Folder localFolder = localStore.getFolder(folder);
// Folder localTrashFolder = localStore.getFolder(account.getTrashFolderName(mContext));
//
// localFolder.copyMessages(new Message[] { message }, localTrashFolder, null);
// message.setFlag(Flag.DELETED, true);
//
// if (account.getDeletePolicy() == Account.DELETE_POLICY_ON_DELETE) {
// PendingCommand command = new PendingCommand();
// command.command = PENDING_COMMAND_TRASH;
// command.arguments = new String[] { folder, message.getUid() };
// queuePendingCommand(account, command);
// processPendingCommands(account);
// }
// }
// catch (MessagingException me) {
// throw new RuntimeException("Error deleting message from local store.", me);
// }
}
public void emptyTrash(final EmailContent.Account account, MessagingListener listener) {
@ -1789,29 +1809,31 @@ public class MessagingController implements Runnable {
}
public void saveDraft(final EmailContent.Account account, final Message message) {
try {
Store localStore = Store.getInstance(account.getLocalStoreUri(mContext), mContext,
null);
LocalFolder localFolder =
(LocalFolder) localStore.getFolder(account.getDraftsFolderName(mContext));
localFolder.open(OpenMode.READ_WRITE, null);
localFolder.appendMessages(new Message[] {
message
});
Message localMessage = localFolder.getMessage(message.getUid());
localMessage.setFlag(Flag.X_DOWNLOADED_FULL, true);
// TODO rewrite using provider upates
PendingCommand command = new PendingCommand();
command.command = PENDING_COMMAND_APPEND;
command.arguments = new String[] {
localFolder.getName(),
localMessage.getUid() };
queuePendingCommand(account, command);
processPendingCommands(account);
}
catch (MessagingException e) {
Log.e(Email.LOG_TAG, "Unable to save message as draft.", e);
}
// try {
// Store localStore = Store.getInstance(account.getLocalStoreUri(mContext), mContext,
// null);
// LocalFolder localFolder =
// (LocalFolder) localStore.getFolder(account.getDraftsFolderName(mContext));
// localFolder.open(OpenMode.READ_WRITE, null);
// localFolder.appendMessages(new Message[] {
// message
// });
// Message localMessage = localFolder.getMessage(message.getUid());
// localMessage.setFlag(Flag.X_DOWNLOADED_FULL, true);
//
// PendingCommand command = new PendingCommand();
// command.command = PENDING_COMMAND_APPEND;
// command.arguments = new String[] {
// localFolder.getName(),
// localMessage.getUid() };
// queuePendingCommand(account, command);
// processPendingCommands(account);
// }
// catch (MessagingException e) {
// Log.e(Email.LOG_TAG, "Unable to save message as draft.", e);
// }
}
private static class Command {

View File

@ -893,13 +893,13 @@ public class FolderMessageList extends ExpandableListActivity {
}
private void onToggleRead(MessageInfoHolder holder) {
MessagingController.getInstance(getApplication()).markMessageRead(
mAccount,
holder.message.getFolder().getName(),
holder.uid,
!holder.read);
holder.read = !holder.read;
onRefresh(false);
// MessagingController.getInstance(getApplication()).markMessageRead(
// mAccount,
// holder.message.getFolder().getName(),
// holder.uid,
// !holder.read);
// holder.read = !holder.read;
// onRefresh(false);
}
@Override

View File

@ -84,7 +84,6 @@ public class ImapStore extends Store {
public static final int CONNECTION_SECURITY_SSL_REQUIRED = 3;
public static final int CONNECTION_SECURITY_SSL_OPTIONAL = 4;
// TODO: Flag.FLAGGED is only partially permanent - we can read, but we can't write back
private static final Flag[] PERMANENT_FLAGS = { Flag.DELETED, Flag.SEEN, Flag.FLAGGED };
private Transport mRootTransport;
@ -1046,25 +1045,28 @@ public class ImapStore extends Store {
public void setFlags(Message[] messages, Flag[] flags, boolean value)
throws MessagingException {
checkOpen();
String[] uids = new String[messages.length];
StringBuilder uidList = new StringBuilder();
for (int i = 0, count = messages.length; i < count; i++) {
uids[i] = messages[i].getUid();
if (i > 0) uidList.append(',');
uidList.append(messages[i].getUid());
}
ArrayList<String> flagNames = new ArrayList<String>();
StringBuilder flagList = new StringBuilder();
for (int i = 0, count = flags.length; i < count; i++) {
Flag flag = flags[i];
if (flag == Flag.SEEN) {
flagNames.add("\\Seen");
}
else if (flag == Flag.DELETED) {
flagNames.add("\\Deleted");
flagList.append(" \\Seen");
} else if (flag == Flag.DELETED) {
flagList.append(" \\Deleted");
} else if (flag == Flag.FLAGGED) {
flagList.append(" \\Flagged");
}
}
try {
mConnection.executeSimpleCommand(String.format("UID STORE %s %sFLAGS.SILENT (%s)",
Utility.combine(uids, ','),
uidList,
value ? "+" : "-",
Utility.combine(flagNames.toArray(new String[flagNames.size()]), ' ')));
flagList.substring(1))); // Remove the first space
}
catch (IOException ioe) {
throw ioExceptionHandler(mConnection, ioe);

View File

@ -121,12 +121,13 @@ public class ImapStoreUnitTests extends AndroidTestCase {
* TODO: Test all of the small Folder functions.
*/
public void testSmallFolderFunctions() throws MessagingException {
// getPermanentFlags() returns { Flag.DELETED, Flag.SEEN }
// getPermanentFlags() returns { Flag.DELETED, Flag.SEEN, Flag.FLAGGED }
Flag[] flags = mFolder.getPermanentFlags();
assertEquals(2, flags.length);
assertEquals(3, flags.length);
// TODO: Write flags into hashset and compare them to a hashset and compare them
assertEquals(Flag.DELETED, flags[0]);
assertEquals(Flag.SEEN, flags[1]);
assertEquals(Flag.FLAGGED, flags[2]);
}
/**