Home | History | Annotate | Download | only in provider
      1 package com.android.emailcommon.provider;
      2 
      3 import android.content.ContentResolver;
      4 import android.content.Context;
      5 import android.database.Cursor;
      6 import android.net.Uri;
      7 import android.support.v4.util.LongSparseArray;
      8 
      9 import com.android.mail.utils.LogUtils;
     10 
     11 import java.util.ArrayList;
     12 import java.util.List;
     13 
     14 /**
     15  * {@link EmailContent}-like class for the MessageStateChange table.
     16  */
     17 public class MessageStateChange extends MessageChangeLogTable {
     18     /** Logging tag. */
     19     public static final String LOG_TAG = "MessageStateChange";
     20 
     21     /** The name for this table in the database. */
     22     public static final String TABLE_NAME = "MessageStateChange";
     23 
     24     /** The path for the URI for interacting with message moves. */
     25     public static final String PATH = "messageChange";
     26 
     27     /** The URI for dealing with message move data. */
     28     public static Uri CONTENT_URI;
     29 
     30     // DB columns.
     31     /** Column name for the old value of flagRead. */
     32     public static final String OLD_FLAG_READ = "oldFlagRead";
     33     /** Column name for the new value of flagRead. */
     34     public static final String NEW_FLAG_READ = "newFlagRead";
     35     /** Column name for the old value of flagFavorite. */
     36     public static final String OLD_FLAG_FAVORITE = "oldFlagFavorite";
     37     /** Column name for the new value of flagFavorite. */
     38     public static final String NEW_FLAG_FAVORITE = "newFlagFavorite";
     39 
     40     /** Value stored in DB for "new" columns when an update did not touch this particular value. */
     41     public static final int VALUE_UNCHANGED = -1;
     42 
     43     /**
     44      * Projection for a query to get all columns necessary for an actual change.
     45      */
     46     private interface ProjectionChangeQuery {
     47         public static final int COLUMN_ID = 0;
     48         public static final int COLUMN_MESSAGE_KEY = 1;
     49         public static final int COLUMN_SERVER_ID = 2;
     50         public static final int COLUMN_OLD_FLAG_READ = 3;
     51         public static final int COLUMN_NEW_FLAG_READ = 4;
     52         public static final int COLUMN_OLD_FLAG_FAVORITE = 5;
     53         public static final int COLUMN_NEW_FLAG_FAVORITE = 6;
     54 
     55         public static final String[] PROJECTION = new String[] {
     56                 ID, MESSAGE_KEY, SERVER_ID,
     57                 OLD_FLAG_READ, NEW_FLAG_READ,
     58                 OLD_FLAG_FAVORITE, NEW_FLAG_FAVORITE
     59         };
     60     }
     61 
     62     // The actual fields.
     63     private final int mOldFlagRead;
     64     private int mNewFlagRead;
     65     private final int mOldFlagFavorite;
     66     private int mNewFlagFavorite;
     67     private final long mMailboxId;
     68 
     69     private MessageStateChange(final long messageKey,final String serverId, final long id,
     70             final int oldFlagRead, final int newFlagRead,
     71             final int oldFlagFavorite, final int newFlagFavorite,
     72             final long mailboxId) {
     73         super(messageKey, serverId, id);
     74         mOldFlagRead = oldFlagRead;
     75         mNewFlagRead = newFlagRead;
     76         mOldFlagFavorite = oldFlagFavorite;
     77         mNewFlagFavorite = newFlagFavorite;
     78         mMailboxId = mailboxId;
     79     }
     80 
     81     public final int getNewFlagRead() {
     82         if (mOldFlagRead == mNewFlagRead) {
     83             return VALUE_UNCHANGED;
     84         }
     85         return mNewFlagRead;
     86     }
     87 
     88     public final int getNewFlagFavorite() {
     89         if (mOldFlagFavorite == mNewFlagFavorite) {
     90             return VALUE_UNCHANGED;
     91         }
     92         return mNewFlagFavorite;
     93     }
     94 
     95     /**
     96      * Initialize static state for this class.
     97      */
     98     public static void init() {
     99         CONTENT_URI = EmailContent.CONTENT_URI.buildUpon().appendEncodedPath(PATH).build();
    100     }
    101 
    102     /**
    103      * Gets final state changes to upsync to the server, setting the status in the DB for all rows
    104      * to {@link #STATUS_PROCESSING} that are being updated and to {@link #STATUS_FAILED} for any
    105      * old updates. Messages whose sequence of changes results in a no-op are cleared from the DB
    106      * without any upsync.
    107      * @param context A {@link Context}.
    108      * @param accountId The account we want to update.
    109      * @param ignoreFavorites Whether to ignore changes to the favorites flag.
    110      * @return The final chnages to send to the server, or null if there are none.
    111      */
    112     public static List<MessageStateChange> getChanges(final Context context, final long accountId,
    113             final boolean ignoreFavorites) {
    114         final ContentResolver cr = context.getContentResolver();
    115         final Cursor c = getCursor(cr, CONTENT_URI, ProjectionChangeQuery.PROJECTION, accountId);
    116         if (c == null) {
    117             return null;
    118         }
    119 
    120         // Collapse rows acting on the same message.
    121         // TODO: Unify with MessageMove, move to base class as much as possible.
    122         LongSparseArray<MessageStateChange> changesMap = new LongSparseArray();
    123         try {
    124             while (c.moveToNext()) {
    125                 final long id = c.getLong(ProjectionChangeQuery.COLUMN_ID);
    126                 final long messageKey = c.getLong(ProjectionChangeQuery.COLUMN_MESSAGE_KEY);
    127                 final String serverId = c.getString(ProjectionChangeQuery.COLUMN_SERVER_ID);
    128                 final int oldFlagRead = c.getInt(ProjectionChangeQuery.COLUMN_OLD_FLAG_READ);
    129                 final int newFlagReadTable =  c.getInt(ProjectionChangeQuery.COLUMN_NEW_FLAG_READ);
    130                 final int newFlagRead = (newFlagReadTable == VALUE_UNCHANGED) ?
    131                         oldFlagRead : newFlagReadTable;
    132                 final int oldFlagFavorite =
    133                         c.getInt(ProjectionChangeQuery.COLUMN_OLD_FLAG_FAVORITE);
    134                 final int newFlagFavoriteTable =
    135                         c.getInt(ProjectionChangeQuery.COLUMN_NEW_FLAG_FAVORITE);
    136                 final int newFlagFavorite =
    137                         (ignoreFavorites || newFlagFavoriteTable == VALUE_UNCHANGED) ?
    138                                 oldFlagFavorite : newFlagFavoriteTable;
    139                 final MessageStateChange existingChange = changesMap.get(messageKey);
    140                 if (existingChange != null) {
    141                     if (existingChange.mLastId >= id) {
    142                         LogUtils.w(LOG_TAG, "DChanges were not in ascending id order");
    143                     }
    144                     if (existingChange.mNewFlagRead != oldFlagRead ||
    145                             existingChange.mNewFlagFavorite != oldFlagFavorite) {
    146                         LogUtils.w(LOG_TAG, "existing change inconsistent with new change");
    147                     }
    148                     existingChange.mNewFlagRead = newFlagRead;
    149                     existingChange.mNewFlagFavorite = newFlagFavorite;
    150                     existingChange.mLastId = id;
    151                 } else {
    152                     final long mailboxId = MessageMove.getLastSyncedMailboxForMessage(cr,
    153                             messageKey);
    154                     if (mailboxId == Mailbox.NO_MAILBOX) {
    155                         LogUtils.e(LOG_TAG, "No mailbox id for message %d", messageKey);
    156                     } else {
    157                         changesMap.put(messageKey, new MessageStateChange(messageKey, serverId, id,
    158                                 oldFlagRead, newFlagRead, oldFlagFavorite, newFlagFavorite,
    159                                 mailboxId));
    160                     }
    161                 }
    162             }
    163         } finally {
    164             c.close();
    165         }
    166 
    167         // Prune no-ops.
    168         // TODO: Unify with MessageMove, move to base class as much as possible.
    169         final int count = changesMap.size();
    170         final long[] unchangedMessages = new long[count];
    171         int unchangedMessagesCount = 0;
    172         final ArrayList<MessageStateChange> changes = new ArrayList(count);
    173         for (int i = 0; i < changesMap.size(); ++i) {
    174             final MessageStateChange change = changesMap.valueAt(i);
    175             // We also treat changes without a server id as a no-op.
    176             if ((change.mServerId == null || change.mServerId.length() == 0) ||
    177                     (change.mOldFlagRead == change.mNewFlagRead &&
    178                             change.mOldFlagFavorite == change.mNewFlagFavorite)) {
    179                 unchangedMessages[unchangedMessagesCount] = change.mMessageKey;
    180                 ++unchangedMessagesCount;
    181             } else {
    182                 changes.add(change);
    183             }
    184         }
    185         if (unchangedMessagesCount != 0) {
    186             deleteRowsForMessages(cr, CONTENT_URI, unchangedMessages, unchangedMessagesCount);
    187         }
    188         if (changes.isEmpty()) {
    189             return null;
    190         }
    191         return changes;
    192     }
    193 
    194     /**
    195      * Rearrange the changes list to a map by mailbox id.
    196      * @return The final changes to send to the server, or null if there are none.
    197      */
    198     public static LongSparseArray<List<MessageStateChange>> convertToChangesMap(
    199             final List<MessageStateChange> changes) {
    200         if (changes == null) {
    201             return null;
    202         }
    203 
    204         final LongSparseArray<List<MessageStateChange>> changesMap = new LongSparseArray();
    205         for (final MessageStateChange change : changes) {
    206             List<MessageStateChange> list = changesMap.get(change.mMailboxId);
    207             if (list == null) {
    208                 list = new ArrayList();
    209                 changesMap.put(change.mMailboxId, list);
    210             }
    211             list.add(change);
    212         }
    213         if (changesMap.size() == 0) {
    214             return null;
    215         }
    216         return changesMap;
    217     }
    218 
    219     /**
    220      * Clean up the table to reflect a successful set of upsyncs.
    221      * @param cr A {@link ContentResolver}
    222      * @param messageKeys The messages to update.
    223      * @param count The number of messages.
    224      */
    225     public static void upsyncSuccessful(final ContentResolver cr, final long[] messageKeys,
    226             final int count) {
    227         deleteRowsForMessages(cr, CONTENT_URI, messageKeys, count);
    228     }
    229 
    230     /**
    231      * Clean up the table to reflect upsyncs that need to be retried.
    232      * @param cr A {@link ContentResolver}
    233      * @param messageKeys The messages to update.
    234      * @param count The number of messages.
    235      */
    236     public static void upsyncRetry(final ContentResolver cr, final long[] messageKeys,
    237             final int count) {
    238         retryMessages(cr, CONTENT_URI, messageKeys, count);
    239     }
    240 }
    241