• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 androidx.collection.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 
MessageMove(final long messageKey,final String serverId, final long id, final long srcFolderKey, final long dstFolderKey, final String srcFolderServerId, final String dstFolderServerId)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 
getSourceFolderKey()90     public final long getSourceFolderKey() {
91         return mSrcFolderKey;
92     }
93 
getSourceFolderId()94     public final String getSourceFolderId() {
95         return mSrcFolderServerId;
96     }
97 
getDestFolderId()98     public final String getDestFolderId() {
99         return mDstFolderServerId;
100     }
101 
102     /**
103      * Initialize static state for this class.
104      */
init()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      */
getMoves(final Context context, final long accountId)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      */
upsyncSuccessful(final ContentResolver cr, final long[] messageKeys, final int count)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      */
upsyncRetry(final ContentResolver cr, final long[] messageKeys, final int count)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      */
upsyncFail(final ContentResolver cr, final long[] messageKeys, final int count)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      */
getLastSyncedMailboxForMessage(final ContentResolver cr, final long messageId)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