Home | History | Annotate | Download | only in provider
      1 package com.android.emailcommon.provider;
      2 
      3 import android.content.ContentResolver;
      4 import android.content.ContentValues;
      5 import android.database.Cursor;
      6 import android.net.Uri;
      7 
      8 /**
      9  * {@link EmailContent}-like base class for change log tables.
     10  * Accounts that upsync message changes require a change log to track local changes between upsyncs.
     11  * A single instance of this class (or subclass) represents one change to upsync to the server.
     12  * This object may actually correspond to multiple rows in the table.
     13  * This class (and subclasses) also contains constants for the table columns and values stored in
     14  * the DB. The base class contains the ones common to all change logs.
     15  */
     16 public abstract class MessageChangeLogTable {
     17 
     18     // DB columns. Note that this class (and subclasses) use some denormalized columns
     19     // (e.g. accountKey) for simplicity at query time and debugging ease.
     20     /** Column name for the row key; this is an autoincrement key. */
     21     public static final String ID = "_id";
     22     /** Column name for a foreign key into Message for the message that's moving. */
     23     public static final String MESSAGE_KEY = "messageKey";
     24     /** Column name for the server-side id for messageKey. */
     25     public static final String SERVER_ID = "messageServerId";
     26     /** Column name for a foreign key into Account for the message that's moving. */
     27     public static final String ACCOUNT_KEY = "accountKey";
     28     /** Column name for a status value indicating where we are with processing this move request. */
     29     public static final String STATUS = "status";
     30 
     31     // Status values.
     32     /** Status value indicating this move has not yet been unpsynced. */
     33     public static final int STATUS_NONE = 0;
     34     public static final String STATUS_NONE_STRING = String.valueOf(STATUS_NONE);
     35     /** Status value indicating this move is being upsynced right now. */
     36     public static final int STATUS_PROCESSING = 1;
     37     public static final String STATUS_PROCESSING_STRING = String.valueOf(STATUS_PROCESSING);
     38     /** Status value indicating this move failed to upsync. */
     39     public static final int STATUS_FAILED = 2;
     40     public static final String STATUS_FAILED_STRING = String.valueOf(STATUS_FAILED);
     41 
     42     /** Selection string for querying this table. */
     43     private static final String SELECTION_BY_ACCOUNT_KEY_AND_STATUS =
     44             ACCOUNT_KEY + "=? and " + STATUS + "=?";
     45 
     46     /** Selection string prefix for deleting moves for a set of messages. */
     47     private static final String SELECTION_BY_MESSAGE_KEYS_PREFIX = MESSAGE_KEY + " in (";
     48 
     49     protected final long mMessageKey;
     50     protected final String mServerId;
     51     protected long mLastId;
     52 
     53     protected MessageChangeLogTable(final long messageKey, final String serverId, final long id) {
     54         mMessageKey = messageKey;
     55         mServerId = serverId;
     56         mLastId = id;
     57     }
     58 
     59     public final long getMessageId() {
     60         return mMessageKey;
     61     }
     62 
     63     public final String getServerId() {
     64         return mServerId;
     65     }
     66 
     67     /**
     68      * Update status of all change entries for an account:
     69      * - {@link #STATUS_NONE} -> {@link #STATUS_PROCESSING}
     70      * - {@link #STATUS_PROCESSING} -> {@link #STATUS_FAILED}
     71      * @param cr A {@link ContentResolver}.
     72      * @param uri The content uri for this table.
     73      * @param accountId The account we want to update.
     74      * @return The number of change entries that are now in {@link #STATUS_PROCESSING}.
     75      */
     76     private static int startProcessing(final ContentResolver cr, final Uri uri,
     77             final String accountId) {
     78         final String[] args = new String[2];
     79         args[0] = accountId;
     80         final ContentValues cv = new ContentValues(1);
     81 
     82         // First mark anything that's still processing as failed.
     83         args[1] = STATUS_PROCESSING_STRING;
     84         cv.put(STATUS, STATUS_FAILED);
     85         cr.update(uri, cv, SELECTION_BY_ACCOUNT_KEY_AND_STATUS, args);
     86 
     87         // Now mark all unprocessed messages as processing.
     88         args[1] = STATUS_NONE_STRING;
     89         cv.put(STATUS, STATUS_PROCESSING);
     90         return cr.update(uri, cv, SELECTION_BY_ACCOUNT_KEY_AND_STATUS, args);
     91     }
     92 
     93     /**
     94      * Query for all move records that are in {@link #STATUS_PROCESSING}.
     95      * Note that this function assumes the underlying table uses an autoincrement id key: it assumes
     96      * that ascending id is the same as chronological order.
     97      * @param cr A {@link ContentResolver}.
     98      * @param uri The content uri for this table.
     99      * @param projection The projection to use for this query.
    100      * @param accountId The account we want to update.
    101      * @return A {@link android.database.Cursor} containing all rows, in id order.
    102      */
    103     private static Cursor getRowsToProcess(final ContentResolver cr, final Uri uri,
    104             final String[] projection, final String accountId) {
    105         final String[] args = { accountId, STATUS_PROCESSING_STRING };
    106         return cr.query(uri, projection, SELECTION_BY_ACCOUNT_KEY_AND_STATUS, args, ID + " ASC");
    107     }
    108 
    109     /**
    110      * Create a selection string for all messages in a set.
    111      * @param messageKeys The set of messages we're interested in.
    112      * @param count The number of messages we're interested in.
    113      * @return The selection string for these messages.
    114      */
    115     private static String getSelectionForMessages(final long[] messageKeys, final int count) {
    116         final StringBuilder sb = new StringBuilder(SELECTION_BY_MESSAGE_KEYS_PREFIX);
    117         for (int i = 0; i < count; ++i) {
    118             if (i != 0) {
    119                 sb.append(",");
    120             }
    121             sb.append(messageKeys[i]);
    122         }
    123         sb.append(")");
    124         return sb.toString();
    125     }
    126 
    127     /**
    128      * Delete all rows for a set of messages. Used to clear no-op changes (i.e. multiple rows for
    129      * a message that reverts it to the original state) and after successful upsync.
    130      * @param cr A {@link ContentResolver}.
    131      * @param uri The content uri for this table.
    132      * @param messageKeys The messages to clear.
    133      * @param count The number of message keys.
    134      * @return The number of rows deleted from the DB.
    135      */
    136     protected static int deleteRowsForMessages(final ContentResolver cr, final Uri uri,
    137             final long[] messageKeys, final int count) {
    138         if (count == 0) {
    139             return 0;
    140         }
    141         return cr.delete(uri, getSelectionForMessages(messageKeys, count), null);
    142     }
    143 
    144     /**
    145      * Set the status value for a set of messages.
    146      * @param cr A {@link ContentResolver}.
    147      * @param uri The {@link Uri} for the update.
    148      * @param messageKeys The messages to update.
    149      * @param count The number of messageKeys.
    150      * @param status The new status value for the messages.
    151      * @return The number of rows updated.
    152      */
    153     private static int updateStatusForMessages(final ContentResolver cr, final Uri uri,
    154             final long[] messageKeys, final int count, final int status) {
    155         if (count == 0) {
    156             return 0;
    157         }
    158         final ContentValues cv = new ContentValues(1);
    159         cv.put(STATUS, status);
    160         return cr.update(uri, cv, getSelectionForMessages(messageKeys, count), null);
    161     }
    162 
    163     /**
    164      * Set a set of messages to status = retry.
    165      * @param cr A {@link ContentResolver}.
    166      * @param uri The {@link Uri} for the update.
    167      * @param messageKeys The messages to update.
    168      * @param count The number of messageKeys.
    169      * @return The number of rows updated.
    170      */
    171     protected static int retryMessages(final ContentResolver cr, final Uri uri,
    172             final long[] messageKeys, final int count) {
    173         return updateStatusForMessages(cr, uri, messageKeys, count, STATUS_NONE);
    174     }
    175 
    176     /**
    177      * Set a set of messages to status = failed.
    178      * @param cr A {@link ContentResolver}.
    179      * @param uri The {@link Uri} for the update.
    180      * @param messageKeys The messages to update.
    181      * @param count The number of messageKeys.
    182      * @return The number of rows updated.
    183      */
    184     protected static int failMessages(final ContentResolver cr, final Uri uri,
    185             final long[] messageKeys, final int count) {
    186         return updateStatusForMessages(cr, uri, messageKeys, count, STATUS_FAILED);
    187     }
    188 
    189     /**
    190      * Start processing our table and get a {@link Cursor} for the rows to process.
    191      * @param cr A {@link ContentResolver}.
    192      * @param uri The {@link Uri} for the update.
    193      * @param projection The projection to use for our read.
    194      * @param accountId The account we're interested in.
    195      * @return A {@link Cursor} with the change log rows we're interested in.
    196      */
    197     protected static Cursor getCursor(final ContentResolver cr, final Uri uri,
    198             final String[] projection, final long accountId) {
    199         final String accountIdString = String.valueOf(accountId);
    200         if (startProcessing(cr, uri, accountIdString) <= 0) {
    201             return null;
    202         }
    203         return getRowsToProcess(cr, uri, projection, accountIdString);
    204     }
    205 }
    206