261 lines
11 KiB
Java
261 lines
11 KiB
Java
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;
|
|
import android.support.v4.util.LongSparseArray;
|
|
|
|
import com.android.mail.utils.LogUtils;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
|
|
/**
|
|
* {@link EmailContent}-like class for the MessageMove table.
|
|
*/
|
|
public class MessageMove extends MessageChangeLogTable {
|
|
/** Logging tag. */
|
|
public static final String LOG_TAG = "MessageMove";
|
|
|
|
/** The name for this table in the database. */
|
|
public static final String TABLE_NAME = "MessageMove";
|
|
|
|
/** The path for the URI for interacting with message moves. */
|
|
public static final String PATH = "messageMove";
|
|
|
|
/** The URI for dealing with message move data. */
|
|
public static Uri CONTENT_URI;
|
|
|
|
// DB columns.
|
|
/** Column name for a foreign key into Mailbox for the folder the message is moving from. */
|
|
public static final String SRC_FOLDER_KEY = "srcFolderKey";
|
|
/** Column name for a foreign key into Mailbox for the folder the message is moving to. */
|
|
public static final String DST_FOLDER_KEY = "dstFolderKey";
|
|
/** Column name for the server-side id for srcFolderKey. */
|
|
public static final String SRC_FOLDER_SERVER_ID = "srcFolderServerId";
|
|
/** 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 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;
|
|
public static final int COLUMN_SRC_FOLDER_KEY = 3;
|
|
public static final int COLUMN_DST_FOLDER_KEY = 4;
|
|
public static final int COLUMN_SRC_FOLDER_SERVER_ID = 5;
|
|
public static final int COLUMN_DST_FOLDER_SERVER_ID = 6;
|
|
|
|
public static final String[] PROJECTION = new String[] {
|
|
ID, MESSAGE_KEY, SERVER_ID,
|
|
SRC_FOLDER_KEY, DST_FOLDER_KEY,
|
|
SRC_FOLDER_SERVER_ID, DST_FOLDER_SERVER_ID
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
private final String mSrcFolderServerId;
|
|
private String mDstFolderServerId;
|
|
|
|
private MessageMove(final long messageKey,final String serverId, final long id,
|
|
final long srcFolderKey, final long dstFolderKey,
|
|
final String srcFolderServerId, final String dstFolderServerId) {
|
|
super(messageKey, serverId, id);
|
|
mSrcFolderKey = srcFolderKey;
|
|
mDstFolderKey = dstFolderKey;
|
|
mSrcFolderServerId = srcFolderServerId;
|
|
mDstFolderServerId = dstFolderServerId;
|
|
}
|
|
|
|
public final long getSourceFolderKey() {
|
|
return mSrcFolderKey;
|
|
}
|
|
|
|
public final String getSourceFolderId() {
|
|
return mSrcFolderServerId;
|
|
}
|
|
|
|
public final String getDestFolderId() {
|
|
return mDstFolderServerId;
|
|
}
|
|
|
|
/**
|
|
* Initialize static state for this class.
|
|
*/
|
|
public static void init() {
|
|
CONTENT_URI = EmailContent.CONTENT_URI.buildUpon().appendEncodedPath(PATH).build();
|
|
}
|
|
|
|
/**
|
|
* Get the final moves that we want 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 pending moves results in a no-op (i.e. the message has been moved
|
|
* back to its original folder) have their moves cleared from the DB without any upsync.
|
|
* @param context A {@link Context}.
|
|
* @param accountId The account we want to update.
|
|
* @return The final moves to send to the server, or null if there are none.
|
|
*/
|
|
public static List<MessageMove> getMoves(final Context context, final long accountId) {
|
|
final ContentResolver cr = context.getContentResolver();
|
|
final Cursor c = getCursor(cr, CONTENT_URI, ProjectionMoveQuery.PROJECTION, accountId);
|
|
if (c == null) {
|
|
return null;
|
|
}
|
|
|
|
// Collapse any rows in the cursor that are acting on the same message. We know the cursor
|
|
// returned by getRowsToProcess is ordered from oldest to newest, and we use this fact to
|
|
// get the original and final folder for the message.
|
|
LongSparseArray<MessageMove> movesMap = new LongSparseArray();
|
|
try {
|
|
while (c.moveToNext()) {
|
|
final long id = c.getLong(ProjectionMoveQuery.COLUMN_ID);
|
|
final long messageKey = c.getLong(ProjectionMoveQuery.COLUMN_MESSAGE_KEY);
|
|
final String serverId = c.getString(ProjectionMoveQuery.COLUMN_SERVER_ID);
|
|
final long srcFolderKey = c.getLong(ProjectionMoveQuery.COLUMN_SRC_FOLDER_KEY);
|
|
final long dstFolderKey = c.getLong(ProjectionMoveQuery.COLUMN_DST_FOLDER_KEY);
|
|
final String srcFolderServerId =
|
|
c.getString(ProjectionMoveQuery.COLUMN_SRC_FOLDER_SERVER_ID);
|
|
final String dstFolderServerId =
|
|
c.getString(ProjectionMoveQuery.COLUMN_DST_FOLDER_SERVER_ID);
|
|
final MessageMove existingMove = movesMap.get(messageKey);
|
|
if (existingMove != null) {
|
|
if (existingMove.mLastId >= id) {
|
|
LogUtils.w(LOG_TAG, "Moves were not in ascending id order");
|
|
}
|
|
if (!existingMove.mDstFolderServerId.equals(srcFolderServerId) ||
|
|
existingMove.mDstFolderKey != srcFolderKey) {
|
|
LogUtils.w(LOG_TAG, "existing move's dst not same as this move's src");
|
|
}
|
|
existingMove.mDstFolderKey = dstFolderKey;
|
|
existingMove.mDstFolderServerId = dstFolderServerId;
|
|
existingMove.mLastId = id;
|
|
} else {
|
|
movesMap.put(messageKey, new MessageMove(messageKey, serverId, id,
|
|
srcFolderKey, dstFolderKey, srcFolderServerId, dstFolderServerId));
|
|
}
|
|
}
|
|
} finally {
|
|
c.close();
|
|
}
|
|
|
|
// Prune any no-op moves (i.e. messages that have been moved back to the initial folder).
|
|
final int moveCount = movesMap.size();
|
|
final long[] unmovedMessages = new long[moveCount];
|
|
int unmovedMessagesCount = 0;
|
|
final ArrayList<MessageMove> moves = new ArrayList(moveCount);
|
|
for (int i = 0; i < movesMap.size(); ++i) {
|
|
final MessageMove move = movesMap.valueAt(i);
|
|
// 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 {
|
|
moves.add(move);
|
|
}
|
|
}
|
|
if (unmovedMessagesCount != 0) {
|
|
deleteRowsForMessages(cr, CONTENT_URI, unmovedMessages, unmovedMessagesCount);
|
|
}
|
|
if (moves.isEmpty()) {
|
|
return null;
|
|
}
|
|
return moves;
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
|
|
/**
|
|
* Clean up the table to reflect upsyncs that failed and need to be reverted.
|
|
* @param cr A {@link ContentResolver}
|
|
* @param messageKeys The messages to update.
|
|
* @param count The number of messages.
|
|
*/
|
|
public static void upsyncFail(final ContentResolver cr, final long[] messageKeys,
|
|
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;
|
|
}
|
|
}
|