Fix MessageStateChange to include mailbox id.
Also ignore messages without server ids for moves and state changes. Also cleanup to match needs of EAS upsync. Bug: 10678136 Change-Id: Id4d5229b8479e61bd718b707b0d2bc77a9e68046
This commit is contained in:
parent
ed610f749c
commit
e984940465
|
@ -687,6 +687,8 @@ public abstract class EmailContent {
|
|||
public static final String ACCOUNT_KEY_SELECTION =
|
||||
MessageColumns.ACCOUNT_KEY + "=?";
|
||||
|
||||
public static final String[] MAILBOX_KEY_PROJECTION = new String[] { MAILBOX_KEY };
|
||||
|
||||
/**
|
||||
* Selection for messages that are loaded
|
||||
*
|
||||
|
|
|
@ -141,6 +141,18 @@ public class Mailbox extends EmailContent implements MailboxColumns, Parcelable
|
|||
private static final String[] ACCOUNT_KEY_PROJECTION = { MailboxColumns.ACCOUNT_KEY };
|
||||
private static final int ACCOUNT_KEY_PROJECTION_ACCOUNT_KEY_COLUMN = 0;
|
||||
|
||||
/**
|
||||
* Projection for querying data needed during a sync.
|
||||
*/
|
||||
public interface ProjectionSyncData {
|
||||
public static final int COLUMN_SERVER_ID = 0;
|
||||
public static final int COLUMN_SYNC_KEY = 1;
|
||||
|
||||
public static final String[] PROJECTION = {
|
||||
MailboxColumns.SERVER_ID, MailboxColumns.SYNC_KEY
|
||||
};
|
||||
};
|
||||
|
||||
public static final long NO_MAILBOX = -1;
|
||||
|
||||
// Sentinel values for the mSyncInterval field of both Mailbox records
|
||||
|
|
|
@ -37,6 +37,7 @@ public abstract class MessageChangeLogTable {
|
|||
public static final String STATUS_PROCESSING_STRING = String.valueOf(STATUS_PROCESSING);
|
||||
/** Status value indicating this move failed to upsync. */
|
||||
public static final int STATUS_FAILED = 2;
|
||||
public static final String STATUS_FAILED_STRING = String.valueOf(STATUS_FAILED);
|
||||
|
||||
/** Selection string for querying this table. */
|
||||
private static final String SELECTION_BY_ACCOUNT_KEY_AND_STATUS =
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.android.emailcommon.provider;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
|
@ -37,10 +38,14 @@ public class MessageMove extends MessageChangeLogTable {
|
|||
/** Column name for the server-side id for dstFolderKey. */
|
||||
public static final String DST_FOLDER_SERVER_ID = "dstFolderServerId";
|
||||
|
||||
/** Selection to get the last synced folder for a message. */
|
||||
private static final String SELECTION_LAST_SYNCED_MAILBOX = MESSAGE_KEY + "=? and " + STATUS
|
||||
+ "!=" + STATUS_FAILED_STRING;
|
||||
|
||||
/**
|
||||
* Projection for a query to get all columns necessary for an actual move.
|
||||
*/
|
||||
private static final class ProjectionMoveQuery {
|
||||
private interface ProjectionMoveQuery {
|
||||
public static final int COLUMN_ID = 0;
|
||||
public static final int COLUMN_MESSAGE_KEY = 1;
|
||||
public static final int COLUMN_SERVER_ID = 2;
|
||||
|
@ -56,6 +61,16 @@ public class MessageMove extends MessageChangeLogTable {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Projection for a query to get the original folder id for a message.
|
||||
*/
|
||||
private interface ProjectionLastSyncedMailboxQuery {
|
||||
public static final int COLUMN_ID = 0;
|
||||
public static final int COLUMN_SRC_FOLDER_KEY = 1;
|
||||
|
||||
public static final String[] PROJECTION = new String[] { ID, SRC_FOLDER_KEY };
|
||||
}
|
||||
|
||||
// The actual fields.
|
||||
private final long mSrcFolderKey;
|
||||
private long mDstFolderKey;
|
||||
|
@ -151,7 +166,9 @@ public class MessageMove extends MessageChangeLogTable {
|
|||
final ArrayList<MessageMove> moves = new ArrayList(moveCount);
|
||||
for (int i = 0; i < movesMap.size(); ++i) {
|
||||
final MessageMove move = movesMap.valueAt(i);
|
||||
if (move.mSrcFolderKey == move.mDstFolderKey) {
|
||||
// We also treat changes without a server id as a no-op.
|
||||
if ((move.mServerId == null || move.mServerId.length() == 0) ||
|
||||
move.mSrcFolderKey == move.mDstFolderKey) {
|
||||
unmovedMessages[unmovedMessagesCount] = move.mMessageKey;
|
||||
++unmovedMessagesCount;
|
||||
} else {
|
||||
|
@ -199,4 +216,45 @@ public class MessageMove extends MessageChangeLogTable {
|
|||
final int count) {
|
||||
failMessages(cr, CONTENT_URI, messageKeys, count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the id for the mailbox this message is in (from the server's point of view).
|
||||
* @param cr A {@link ContentResolver}.
|
||||
* @param messageId The message we're interested in.
|
||||
* @return The id for the mailbox this message was in.
|
||||
*/
|
||||
public static long getLastSyncedMailboxForMessage(final ContentResolver cr,
|
||||
final long messageId) {
|
||||
// Check if there's a pending move and get the original mailbox id.
|
||||
final String[] selectionArgs = { String.valueOf(messageId) };
|
||||
final Cursor moveCursor = cr.query(CONTENT_URI, ProjectionLastSyncedMailboxQuery.PROJECTION,
|
||||
SELECTION_LAST_SYNCED_MAILBOX, selectionArgs, ID + " ASC");
|
||||
if (moveCursor != null) {
|
||||
try {
|
||||
if (moveCursor.moveToFirst()) {
|
||||
// We actually only care about the oldest one, i.e. the one we last got
|
||||
// from the server before we started mucking with it.
|
||||
return moveCursor.getLong(
|
||||
ProjectionLastSyncedMailboxQuery.COLUMN_SRC_FOLDER_KEY);
|
||||
}
|
||||
} finally {
|
||||
moveCursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
// There are no pending moves for this message, so use the one in the Message table.
|
||||
final Cursor messageCursor = cr.query(ContentUris.withAppendedId(
|
||||
EmailContent.Message.CONTENT_URI, messageId),
|
||||
EmailContent.Message.MAILBOX_KEY_PROJECTION, null, null, null);
|
||||
if (messageCursor != null) {
|
||||
try {
|
||||
if (messageCursor.moveToFirst()) {
|
||||
return messageCursor.getLong(0);
|
||||
}
|
||||
} finally {
|
||||
messageCursor.close();
|
||||
}
|
||||
}
|
||||
return Mailbox.NO_MAILBOX;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ public class MessageStateChange extends MessageChangeLogTable {
|
|||
/**
|
||||
* Projection for a query to get all columns necessary for an actual change.
|
||||
*/
|
||||
private static final class ProjectionChangeQuery {
|
||||
private interface ProjectionChangeQuery {
|
||||
public static final int COLUMN_ID = 0;
|
||||
public static final int COLUMN_MESSAGE_KEY = 1;
|
||||
public static final int COLUMN_SERVER_ID = 2;
|
||||
|
@ -64,15 +64,32 @@ public class MessageStateChange extends MessageChangeLogTable {
|
|||
private int mNewFlagRead;
|
||||
private final int mOldFlagFavorite;
|
||||
private int mNewFlagFavorite;
|
||||
private final long mMailboxId;
|
||||
|
||||
private MessageStateChange(final long messageKey,final String serverId, final long id,
|
||||
final int oldFlagRead, final int newFlagRead,
|
||||
final int oldFlagFavorite, final int newFlagFavorite) {
|
||||
final int oldFlagFavorite, final int newFlagFavorite,
|
||||
final long mailboxId) {
|
||||
super(messageKey, serverId, id);
|
||||
mOldFlagRead = oldFlagRead;
|
||||
mNewFlagRead = newFlagRead;
|
||||
mOldFlagFavorite = oldFlagFavorite;
|
||||
mNewFlagFavorite = newFlagFavorite;
|
||||
mMailboxId = mailboxId;
|
||||
}
|
||||
|
||||
public final int getNewFlagRead() {
|
||||
if (mOldFlagRead == mNewFlagRead) {
|
||||
return VALUE_UNCHANGED;
|
||||
}
|
||||
return mNewFlagRead;
|
||||
}
|
||||
|
||||
public final int getNewFlagFavorite() {
|
||||
if (mOldFlagFavorite == mNewFlagFavorite) {
|
||||
return VALUE_UNCHANGED;
|
||||
}
|
||||
return mNewFlagFavorite;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -82,7 +99,18 @@ public class MessageStateChange extends MessageChangeLogTable {
|
|||
CONTENT_URI = EmailContent.CONTENT_URI.buildUpon().appendEncodedPath(PATH).build();
|
||||
}
|
||||
|
||||
public static List<MessageStateChange> getChanges(final Context context, final long accountId) {
|
||||
/**
|
||||
* Gets final state changes to upsync to the server, setting the status in the DB for all rows
|
||||
* to {@link #STATUS_PROCESSING} that are being updated and to {@link #STATUS_FAILED} for any
|
||||
* old updates. Messages whose sequence of changes results in a no-op are cleared from the DB
|
||||
* without any upsync.
|
||||
* @param context A {@link Context}.
|
||||
* @param accountId The account we want to update.
|
||||
* @param ignoreFavorites Whether to ignore changes to the favorites flag.
|
||||
* @return The final chnages to send to the server, or null if there are none.
|
||||
*/
|
||||
public static List<MessageStateChange> getChanges(final Context context, final long accountId,
|
||||
final boolean ignoreFavorites) {
|
||||
final ContentResolver cr = context.getContentResolver();
|
||||
final Cursor c = getCursor(cr, CONTENT_URI, ProjectionChangeQuery.PROJECTION, accountId);
|
||||
if (c == null) {
|
||||
|
@ -105,8 +133,9 @@ public class MessageStateChange extends MessageChangeLogTable {
|
|||
c.getInt(ProjectionChangeQuery.COLUMN_OLD_FLAG_FAVORITE);
|
||||
final int newFlagFavoriteTable =
|
||||
c.getInt(ProjectionChangeQuery.COLUMN_NEW_FLAG_FAVORITE);
|
||||
final int newFlagFavorite = (newFlagFavoriteTable == VALUE_UNCHANGED) ?
|
||||
oldFlagFavorite : newFlagFavoriteTable;
|
||||
final int newFlagFavorite =
|
||||
(ignoreFavorites || newFlagFavoriteTable == VALUE_UNCHANGED) ?
|
||||
oldFlagFavorite : newFlagFavoriteTable;
|
||||
final MessageStateChange existingChange = changesMap.get(messageKey);
|
||||
if (existingChange != null) {
|
||||
if (existingChange.mLastId >= id) {
|
||||
|
@ -120,8 +149,15 @@ public class MessageStateChange extends MessageChangeLogTable {
|
|||
existingChange.mNewFlagFavorite = newFlagFavorite;
|
||||
existingChange.mLastId = id;
|
||||
} else {
|
||||
changesMap.put(messageKey, new MessageStateChange(messageKey, serverId, id,
|
||||
oldFlagRead, newFlagRead, oldFlagFavorite, newFlagFavorite));
|
||||
final long mailboxId = MessageMove.getLastSyncedMailboxForMessage(cr,
|
||||
messageKey);
|
||||
if (mailboxId == Mailbox.NO_MAILBOX) {
|
||||
LogUtils.e(LOG_TAG, "No mailbox id for message %d", messageKey);
|
||||
} else {
|
||||
changesMap.put(messageKey, new MessageStateChange(messageKey, serverId, id,
|
||||
oldFlagRead, newFlagRead, oldFlagFavorite, newFlagFavorite,
|
||||
mailboxId));
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
|
@ -136,8 +172,10 @@ public class MessageStateChange extends MessageChangeLogTable {
|
|||
final ArrayList<MessageStateChange> changes = new ArrayList(count);
|
||||
for (int i = 0; i < changesMap.size(); ++i) {
|
||||
final MessageStateChange change = changesMap.valueAt(i);
|
||||
if (change.mOldFlagRead == change.mNewFlagRead &&
|
||||
change.mOldFlagFavorite == change.mNewFlagFavorite) {
|
||||
// We also treat changes without a server id as a no-op.
|
||||
if ((change.mServerId == null || change.mServerId.length() == 0) ||
|
||||
(change.mOldFlagRead == change.mNewFlagRead &&
|
||||
change.mOldFlagFavorite == change.mNewFlagFavorite)) {
|
||||
unchangedMessages[unchangedMessagesCount] = change.mMessageKey;
|
||||
++unchangedMessagesCount;
|
||||
} else {
|
||||
|
@ -152,4 +190,51 @@ public class MessageStateChange extends MessageChangeLogTable {
|
|||
}
|
||||
return changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rearrange the changes list to a map by mailbox id.
|
||||
* @return The final changes to send to the server, or null if there are none.
|
||||
*/
|
||||
public static LongSparseArray<List<MessageStateChange>> convertToChangesMap(
|
||||
final List<MessageStateChange> changes) {
|
||||
if (changes == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final LongSparseArray<List<MessageStateChange>> changesMap = new LongSparseArray();
|
||||
for (final MessageStateChange change : changes) {
|
||||
List<MessageStateChange> list = changesMap.get(change.mMailboxId);
|
||||
if (list == null) {
|
||||
list = new ArrayList();
|
||||
changesMap.put(change.mMailboxId, list);
|
||||
}
|
||||
list.add(change);
|
||||
}
|
||||
if (changesMap.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
return changesMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up the table to reflect a successful set of upsyncs.
|
||||
* @param cr A {@link ContentResolver}
|
||||
* @param messageKeys The messages to update.
|
||||
* @param count The number of messages.
|
||||
*/
|
||||
public static void upsyncSuccessful(final ContentResolver cr, final long[] messageKeys,
|
||||
final int count) {
|
||||
deleteRowsForMessages(cr, CONTENT_URI, messageKeys, count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up the table to reflect upsyncs that need to be retried.
|
||||
* @param cr A {@link ContentResolver}
|
||||
* @param messageKeys The messages to update.
|
||||
* @param count The number of messages.
|
||||
*/
|
||||
public static void upsyncRetry(final ContentResolver cr, final long[] messageKeys,
|
||||
final int count) {
|
||||
retryMessages(cr, CONTENT_URI, messageKeys, count);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1464,6 +1464,16 @@ public class EmailProvider extends ContentProvider {
|
|||
"select count(*) from (select count(*) as dupes from " + Mailbox.TABLE_NAME +
|
||||
" where accountKey=? group by " + MailboxColumns.SERVER_ID + ") where dupes > 1";
|
||||
|
||||
|
||||
// Query to get the protocol for a message. Temporary to switch between new and old upsync
|
||||
// behavior; should go away when IMAP gets converted.
|
||||
private static final String GET_PROTOCOL_FOR_MESSAGE = "select h."
|
||||
+ EmailContent.HostAuthColumns.PROTOCOL + " from "
|
||||
+ Message.TABLE_NAME + " m inner join " + Account.TABLE_NAME + " a on m."
|
||||
+ MessageColumns.ACCOUNT_KEY + "=a." + AccountColumns.ID + " inner join "
|
||||
+ HostAuth.TABLE_NAME + " h on a." + AccountColumns.HOST_AUTH_KEY_RECV + "=h."
|
||||
+ EmailContent.HostAuthColumns.ID + " where m." + MessageColumns.ID + "=?";
|
||||
|
||||
@Override
|
||||
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
||||
// Handle this special case the fastest possible way
|
||||
|
@ -1555,24 +1565,43 @@ public class EmailProvider extends ContentProvider {
|
|||
case POLICY_ID:
|
||||
id = uri.getPathSegments().get(1);
|
||||
if (match == SYNCED_MESSAGE_ID) {
|
||||
// For synced messages, first copy the old message to the updated table
|
||||
// Note the insert or ignore semantics, guaranteeing that only the first
|
||||
// update will be reflected in the updated message table; therefore this
|
||||
// row will always have the "original" data
|
||||
db.execSQL(UPDATED_MESSAGE_INSERT + id);
|
||||
|
||||
Long dstFolderId = values.getAsLong(MessageColumns.MAILBOX_KEY);
|
||||
if (dstFolderId != null) {
|
||||
addToMessageMove(db, id, dstFolderId);
|
||||
// TODO: Migrate IMAP to use MessageMove/MessageStateChange as well.
|
||||
boolean isEas = false;
|
||||
final Cursor c = db.rawQuery(GET_PROTOCOL_FOR_MESSAGE, new String[] {id});
|
||||
if (c != null) {
|
||||
try {
|
||||
if (c.moveToFirst()) {
|
||||
final String protocol = c.getString(0);
|
||||
isEas = context.getString(R.string.protocol_eas)
|
||||
.equals(protocol);
|
||||
}
|
||||
} finally {
|
||||
c.close();
|
||||
}
|
||||
}
|
||||
Integer flagRead = values.getAsInteger(MessageColumns.FLAG_READ);
|
||||
Integer flagFavorite = values.getAsInteger(MessageColumns.FLAG_FAVORITE);
|
||||
int flagReadValue = (flagRead != null) ?
|
||||
flagRead : MessageStateChange.VALUE_UNCHANGED;
|
||||
int flagFavoriteValue = (flagFavorite != null) ?
|
||||
flagFavorite : MessageStateChange.VALUE_UNCHANGED;
|
||||
if (flagRead != null || flagFavorite != null) {
|
||||
addToMessageStateChange(db, id, flagReadValue, flagFavoriteValue);
|
||||
|
||||
if (isEas) {
|
||||
// EAS uses the new upsync classes.
|
||||
Long dstFolderId = values.getAsLong(MessageColumns.MAILBOX_KEY);
|
||||
if (dstFolderId != null) {
|
||||
addToMessageMove(db, id, dstFolderId);
|
||||
}
|
||||
Integer flagRead = values.getAsInteger(MessageColumns.FLAG_READ);
|
||||
Integer flagFavorite = values.getAsInteger(MessageColumns.FLAG_FAVORITE);
|
||||
int flagReadValue = (flagRead != null) ?
|
||||
flagRead : MessageStateChange.VALUE_UNCHANGED;
|
||||
int flagFavoriteValue = (flagFavorite != null) ?
|
||||
flagFavorite : MessageStateChange.VALUE_UNCHANGED;
|
||||
if (flagRead != null || flagFavorite != null) {
|
||||
addToMessageStateChange(db, id, flagReadValue, flagFavoriteValue);
|
||||
}
|
||||
} else {
|
||||
// Old way of doing upsync.
|
||||
// For synced messages, first copy the old message to the updated table
|
||||
// Note the insert or ignore semantics, guaranteeing that only the first
|
||||
// update will be reflected in the updated message table; therefore this
|
||||
// row will always have the "original" data
|
||||
db.execSQL(UPDATED_MESSAGE_INSERT + id);
|
||||
}
|
||||
} else if (match == MESSAGE_ID) {
|
||||
db.execSQL(UPDATED_MESSAGE_DELETE + id);
|
||||
|
|
Loading…
Reference in New Issue