Home | History | Annotate | Download | only in provider
      1 package com.android.emailcommon.provider;
      2 
      3 import android.content.ContentResolver;
      4 import android.content.ContentUris;
      5 import android.content.Context;
      6 import android.database.Cursor;
      7 import android.net.Uri;
      8 import android.support.v4.util.LongSparseArray;
      9 
     10 import com.android.mail.utils.LogUtils;
     11 
     12 import java.util.ArrayList;
     13 import java.util.List;
     14 
     15 /**
     16  * {@link EmailContent}-like class for the MessageMove table.
     17  */
     18 public class MessageMove extends MessageChangeLogTable {
     19     /** Logging tag. */
     20     public static final String LOG_TAG = "MessageMove";
     21 
     22     /** The name for this table in the database. */
     23     public static final String TABLE_NAME = "MessageMove";
     24 
     25     /** The path for the URI for interacting with message moves. */
     26     public static final String PATH = "messageMove";
     27 
     28     /** The URI for dealing with message move data. */
     29     public static Uri CONTENT_URI;
     30 
     31     // DB columns.
     32     /** Column name for a foreign key into Mailbox for the folder the message is moving from. */
     33     public static final String SRC_FOLDER_KEY = "srcFolderKey";
     34     /** Column name for a foreign key into Mailbox for the folder the message is moving to. */
     35     public static final String DST_FOLDER_KEY = "dstFolderKey";
     36     /** Column name for the server-side id for srcFolderKey. */
     37     public static final String SRC_FOLDER_SERVER_ID = "srcFolderServerId";
     38     /** Column name for the server-side id for dstFolderKey. */
     39     public static final String DST_FOLDER_SERVER_ID = "dstFolderServerId";
     40 
     41     /** Selection to get the last synced folder for a message. */
     42     private static final String SELECTION_LAST_SYNCED_MAILBOX = MESSAGE_KEY + "=? and " + STATUS
     43             + "!=" + STATUS_FAILED_STRING;
     44 
     45     /**
     46      * Projection for a query to get all columns necessary for an actual move.
     47      */
     48     private interface ProjectionMoveQuery {
     49         public static final int COLUMN_ID = 0;
     50         public static final int COLUMN_MESSAGE_KEY = 1;
     51         public static final int COLUMN_SERVER_ID = 2;
     52         public static final int COLUMN_SRC_FOLDER_KEY = 3;
     53         public static final int COLUMN_DST_FOLDER_KEY = 4;
     54         public static final int COLUMN_SRC_FOLDER_SERVER_ID = 5;
     55         public static final int COLUMN_DST_FOLDER_SERVER_ID = 6;
     56 
     57         public static final String[] PROJECTION = new String[] {
     58                 ID, MESSAGE_KEY, SERVER_ID,
     59                 SRC_FOLDER_KEY, DST_FOLDER_KEY,
     60                 SRC_FOLDER_SERVER_ID, DST_FOLDER_SERVER_ID
     61         };
     62     }
     63 
     64     /**
     65      * Projection for a query to get the original folder id for a message.
     66      */
     67     private interface ProjectionLastSyncedMailboxQuery {
     68         public static final int COLUMN_ID = 0;
     69         public static final int COLUMN_SRC_FOLDER_KEY = 1;
     70 
     71         public static final String[] PROJECTION = new String[] { ID, SRC_FOLDER_KEY };
     72     }
     73 
     74     // The actual fields.
     75     private final long mSrcFolderKey;
     76     private long mDstFolderKey;
     77     private final String mSrcFolderServerId;
     78     private String mDstFolderServerId;
     79 
     80     private MessageMove(final long messageKey,final String serverId, final long id,
     81             final long srcFolderKey, final long dstFolderKey,
     82             final String srcFolderServerId, final String dstFolderServerId) {
     83         super(messageKey, serverId, id);
     84         mSrcFolderKey = srcFolderKey;
     85         mDstFolderKey = dstFolderKey;
     86         mSrcFolderServerId = srcFolderServerId;
     87         mDstFolderServerId = dstFolderServerId;
     88     }
     89 
     90     public final long getSourceFolderKey() {
     91         return mSrcFolderKey;
     92     }
     93 
     94     public final String getSourceFolderId() {
     95         return mSrcFolderServerId;
     96     }
     97 
     98     public final String getDestFolderId() {
     99         return mDstFolderServerId;
    100     }
    101 
    102     /**
    103      * Initialize static state for this class.
    104      */
    105     public static void init() {
    106         CONTENT_URI = EmailContent.CONTENT_URI.buildUpon().appendEncodedPath(PATH).build();
    107     }
    108 
    109     /**
    110      * Get the final moves that we want to upsync to the server, setting the status in the DB for
    111      * all rows to {@link #STATUS_PROCESSING} that are being updated and to {@link #STATUS_FAILED}
    112      * for any old updates.
    113      * Messages whose sequence of pending moves results in a no-op (i.e. the message has been moved
    114      * back to its original folder) have their moves cleared from the DB without any upsync.
    115      * @param context A {@link Context}.
    116      * @param accountId The account we want to update.
    117      * @return The final moves to send to the server, or null if there are none.
    118      */
    119     public static List<MessageMove> getMoves(final Context context, final long accountId) {
    120         final ContentResolver cr = context.getContentResolver();
    121         final Cursor c = getCursor(cr, CONTENT_URI, ProjectionMoveQuery.PROJECTION, accountId);
    122         if (c == null) {
    123             return null;
    124         }
    125 
    126         // Collapse any rows in the cursor that are acting on the same message. We know the cursor
    127         // returned by getRowsToProcess is ordered from oldest to newest, and we use this fact to
    128         // get the original and final folder for the message.
    129         LongSparseArray<MessageMove> movesMap = new LongSparseArray();
    130         try {
    131             while (c.moveToNext()) {
    132                 final long id = c.getLong(ProjectionMoveQuery.COLUMN_ID);
    133                 final long messageKey = c.getLong(ProjectionMoveQuery.COLUMN_MESSAGE_KEY);
    134                 final String serverId = c.getString(ProjectionMoveQuery.COLUMN_SERVER_ID);
    135                 final long srcFolderKey = c.getLong(ProjectionMoveQuery.COLUMN_SRC_FOLDER_KEY);
    136                 final long dstFolderKey = c.getLong(ProjectionMoveQuery.COLUMN_DST_FOLDER_KEY);
    137                 final String srcFolderServerId =
    138                         c.getString(ProjectionMoveQuery.COLUMN_SRC_FOLDER_SERVER_ID);
    139                 final String dstFolderServerId =
    140                         c.getString(ProjectionMoveQuery.COLUMN_DST_FOLDER_SERVER_ID);
    141                 final MessageMove existingMove = movesMap.get(messageKey);
    142                 if (existingMove != null) {
    143                     if (existingMove.mLastId >= id) {
    144                         LogUtils.w(LOG_TAG, "Moves were not in ascending id order");
    145                     }
    146                     if (!existingMove.mDstFolderServerId.equals(srcFolderServerId) ||
    147                             existingMove.mDstFolderKey != srcFolderKey) {
    148                         LogUtils.w(LOG_TAG, "existing move's dst not same as this move's src");
    149                     }
    150                     existingMove.mDstFolderKey = dstFolderKey;
    151                     existingMove.mDstFolderServerId = dstFolderServerId;
    152                     existingMove.mLastId = id;
    153                 } else {
    154                     movesMap.put(messageKey, new MessageMove(messageKey, serverId, id,
    155                             srcFolderKey, dstFolderKey, srcFolderServerId, dstFolderServerId));
    156                 }
    157             }
    158         } finally {
    159             c.close();
    160         }
    161 
    162         // Prune any no-op moves (i.e. messages that have been moved back to the initial folder).
    163         final int moveCount = movesMap.size();
    164         final long[] unmovedMessages = new long[moveCount];
    165         int unmovedMessagesCount = 0;
    166         final ArrayList<MessageMove> moves = new ArrayList(moveCount);
    167         for (int i = 0; i < movesMap.size(); ++i) {
    168             final MessageMove move = movesMap.valueAt(i);
    169             // We also treat changes without a server id as a no-op.
    170             if ((move.mServerId == null || move.mServerId.length() == 0) ||
    171                     move.mSrcFolderKey == move.mDstFolderKey) {
    172                 unmovedMessages[unmovedMessagesCount] = move.mMessageKey;
    173                 ++unmovedMessagesCount;
    174             } else {
    175                 moves.add(move);
    176             }
    177         }
    178         if (unmovedMessagesCount != 0) {
    179             deleteRowsForMessages(cr, CONTENT_URI, unmovedMessages, unmovedMessagesCount);
    180         }
    181         if (moves.isEmpty()) {
    182             return null;
    183         }
    184         return moves;
    185     }
    186 
    187     /**
    188      * Clean up the table to reflect a successful set of upsyncs.
    189      * @param cr A {@link ContentResolver}
    190      * @param messageKeys The messages to update.
    191      * @param count The number of messages.
    192      */
    193     public static void upsyncSuccessful(final ContentResolver cr, final long[] messageKeys,
    194             final int count) {
    195         deleteRowsForMessages(cr, CONTENT_URI, messageKeys, count);
    196     }
    197 
    198     /**
    199      * Clean up the table to reflect upsyncs that need to be retried.
    200      * @param cr A {@link ContentResolver}
    201      * @param messageKeys The messages to update.
    202      * @param count The number of messages.
    203      */
    204     public static void upsyncRetry(final ContentResolver cr, final long[] messageKeys,
    205             final int count) {
    206         retryMessages(cr, CONTENT_URI, messageKeys, count);
    207     }
    208 
    209     /**
    210      * Clean up the table to reflect upsyncs that failed and need to be reverted.
    211      * @param cr A {@link ContentResolver}
    212      * @param messageKeys The messages to update.
    213      * @param count The number of messages.
    214      */
    215     public static void upsyncFail(final ContentResolver cr, final long[] messageKeys,
    216             final int count) {
    217         failMessages(cr, CONTENT_URI, messageKeys, count);
    218     }
    219 
    220     /**
    221      * Get the id for the mailbox this message is in (from the server's point of view).
    222      * @param cr A {@link ContentResolver}.
    223      * @param messageId The message we're interested in.
    224      * @return The id for the mailbox this message was in.
    225      */
    226     public static long getLastSyncedMailboxForMessage(final ContentResolver cr,
    227             final long messageId) {
    228         // Check if there's a pending move and get the original mailbox id.
    229         final String[] selectionArgs = { String.valueOf(messageId) };
    230         final Cursor moveCursor = cr.query(CONTENT_URI, ProjectionLastSyncedMailboxQuery.PROJECTION,
    231                 SELECTION_LAST_SYNCED_MAILBOX, selectionArgs, ID + " ASC");
    232         if (moveCursor != null) {
    233             try {
    234                 if (moveCursor.moveToFirst()) {
    235                     // We actually only care about the oldest one, i.e. the one we last got
    236                     // from the server before we started mucking with it.
    237                     return moveCursor.getLong(
    238                             ProjectionLastSyncedMailboxQuery.COLUMN_SRC_FOLDER_KEY);
    239                 }
    240             } finally {
    241                 moveCursor.close();
    242             }
    243         }
    244 
    245         // There are no pending moves for this message, so use the one in the Message table.
    246         final Cursor messageCursor = cr.query(ContentUris.withAppendedId(
    247                 EmailContent.Message.CONTENT_URI, messageId),
    248                 EmailContent.Message.MAILBOX_KEY_PROJECTION, null, null, null);
    249         if (messageCursor != null) {
    250             try {
    251                 if (messageCursor.moveToFirst()) {
    252                     return messageCursor.getLong(0);
    253                 }
    254             } finally {
    255                 messageCursor.close();
    256             }
    257         }
    258         return Mailbox.NO_MAILBOX;
    259     }
    260 }
    261