diff options
| author | Marc Blank <mblank@google.com> | 2009-12-21 11:48:14 -0800 |
|---|---|---|
| committer | Android Git Automerger <android-git-automerger@android.com> | 2009-12-21 11:48:14 -0800 |
| commit | c76a22b01a2cb78fe75cc591066441fbcf3d90ac (patch) | |
| tree | 511151187c94b96be4b460db780977f7e06013ba | |
| parent | 1889528e72f0d001c43f3aaea5dc2716326a4bfd (diff) | |
| parent | c874824ca8b76f795575538ffa34138e2de47cad (diff) | |
| download | android_packages_apps_Email-c76a22b01a2cb78fe75cc591066441fbcf3d90ac.tar.gz android_packages_apps_Email-c76a22b01a2cb78fe75cc591066441fbcf3d90ac.tar.bz2 android_packages_apps_Email-c76a22b01a2cb78fe75cc591066441fbcf3d90ac.zip | |
am c874824c: Merge change Iad377728 into eclair
Merge commit 'c874824ca8b76f795575538ffa34138e2de47cad' into eclair-plus-aosp
* commit 'c874824ca8b76f795575538ffa34138e2de47cad':
Don't delete referenced messages from the Exchange server DO NOT MERGE
| -rw-r--r-- | src/com/android/email/provider/EmailContent.java | 6 | ||||
| -rw-r--r-- | src/com/android/exchange/adapter/EmailSyncAdapter.java | 83 | ||||
| -rw-r--r-- | tests/src/com/android/exchange/adapter/EmailSyncAdapterTests.java (renamed from tests/src/com/android/exchange/EasEmailSyncAdapterTests.java) | 100 |
3 files changed, 163 insertions, 26 deletions
diff --git a/src/com/android/email/provider/EmailContent.java b/src/com/android/email/provider/EmailContent.java index 62c7c537e..955ea0d27 100644 --- a/src/com/android/email/provider/EmailContent.java +++ b/src/com/android/email/provider/EmailContent.java @@ -185,7 +185,9 @@ public abstract class EmailContent { public static final String HTML_REPLY = "htmlReply"; // Replied-to or forwarded body (in text form) public static final String TEXT_REPLY = "textReply"; - // Message id of the source (if this is a reply/forward) + // A reference to a message's unique id used in reply/forward. + // Protocol code can be expected to use this column in determining whether a message can be + // deleted safely (i.e. isn't referenced by other messages) public static final String SOURCE_MESSAGE_KEY = "sourceMessageKey"; // The text to be placed between a reply/forward response and the original message public static final String INTRO_TEXT = "introText"; @@ -519,7 +521,7 @@ public abstract class EmailContent { public String mBcc; public String mReplyTo; - // The following transient members may be used while building and manipulating messages, + // The following transient members may be used while building and manipulating messages, // but they are NOT persisted directly by EmailProvider transient public String mText; transient public String mHtml; diff --git a/src/com/android/exchange/adapter/EmailSyncAdapter.java b/src/com/android/exchange/adapter/EmailSyncAdapter.java index 6a5d2a5cd..946ae4e95 100644 --- a/src/com/android/exchange/adapter/EmailSyncAdapter.java +++ b/src/com/android/exchange/adapter/EmailSyncAdapter.java @@ -24,6 +24,7 @@ import com.android.email.provider.EmailProvider; import com.android.email.provider.EmailContent.Account; import com.android.email.provider.EmailContent.AccountColumns; import com.android.email.provider.EmailContent.Attachment; +import com.android.email.provider.EmailContent.Body; import com.android.email.provider.EmailContent.Mailbox; import com.android.email.provider.EmailContent.Message; import com.android.email.provider.EmailContent.MessageColumns; @@ -65,8 +66,10 @@ public class EmailSyncAdapter extends AbstractSyncAdapter { private static final String[] MESSAGE_ID_SUBJECT_PROJECTION = new String[] { Message.RECORD_ID, MessageColumns.SUBJECT }; + private static final String WHERE_BODY_SOURCE_MESSAGE_KEY = Body.SOURCE_MESSAGE_KEY + "=?"; - String[] bindArguments = new String[2]; + String[] mBindArguments = new String[2]; + String[] mBindArgument = new String[1]; ArrayList<Long> mDeletedIdList = new ArrayList<Long>(); ArrayList<Long> mUpdatedIdList = new ArrayList<Long>(); @@ -308,10 +311,10 @@ public class EmailSyncAdapter extends AbstractSyncAdapter { } private Cursor getServerIdCursor(String serverId, String[] projection) { - bindArguments[0] = serverId; - bindArguments[1] = mMailboxIdAsString; + mBindArguments[0] = serverId; + mBindArguments[1] = mMailboxIdAsString; return mContentResolver.query(Message.CONTENT_URI, projection, - WHERE_SERVER_ID_AND_MAILBOX_KEY, bindArguments, null); + WHERE_SERVER_ID_AND_MAILBOX_KEY, mBindArguments, null); } private void deleteParser(ArrayList<Long> deletes, int entryTag) throws IOException { @@ -562,46 +565,86 @@ public class EmailSyncAdapter extends AbstractSyncAdapter { return sb.toString(); } - @Override - public boolean sendLocalChanges(Serializer s) throws IOException { - ContentResolver cr = mContext.getContentResolver(); - - // Never upsync from these folders - if (mMailbox.mType == Mailbox.TYPE_DRAFTS || mMailbox.mType == Mailbox.TYPE_OUTBOX) { - return false; + /** + * Note that messages in the deleted database preserve the message's unique id; therefore, we + * can utilize this id to find references to the message. The only reference situation at this + * point is in the Body table; it is when sending messages via SmartForward and SmartReply + */ + private boolean messageReferenced(ContentResolver cr, long id) { + mBindArgument[0] = Long.toString(id); + // See if this id is referenced in a body + Cursor c = cr.query(Body.CONTENT_URI, Body.ID_PROJECTION, WHERE_BODY_SOURCE_MESSAGE_KEY, + mBindArgument, null); + try { + return c.moveToFirst(); + } finally { + c.close(); } + } + + /*private*/ /** + * Serialize commands to delete items from the server; as we find items to delete, add their + * id's to the deletedId's array + * + * @param s the Serializer we're using to create post data + * @param deletedIds ids whose deletions are being sent to the server + * @param first whether or not this is the first command being sent + * @return true if SYNC_COMMANDS hasn't been sent (false otherwise) + * @throws IOException + */ + boolean sendDeletedItems(Serializer s, ArrayList<Long> deletedIds, boolean first) + throws IOException { + ContentResolver cr = mContext.getContentResolver(); // Find any of our deleted items Cursor c = cr.query(Message.DELETED_CONTENT_URI, Message.LIST_PROJECTION, MessageColumns.MAILBOX_KEY + '=' + mMailbox.mId, null, null); - boolean first = true; // We keep track of the list of deleted item id's so that we can remove them from the // deleted table after the server receives our command - mDeletedIdList.clear(); + deletedIds.clear(); try { while (c.moveToNext()) { String serverId = c.getString(Message.LIST_SERVER_ID_COLUMN); // Keep going if there's no serverId if (serverId == null) { continue; + // Also check if this message is referenced elsewhere + } else if (messageReferenced(cr, c.getLong(Message.CONTENT_ID_COLUMN))) { + userLog("Postponing deletion of referenced message: ", serverId); + continue; } else if (first) { s.start(Tags.SYNC_COMMANDS); first = false; } // Send the command to delete this message s.start(Tags.SYNC_DELETE).data(Tags.SYNC_SERVER_ID, serverId).end(); - mDeletedIdList.add(c.getLong(Message.LIST_ID_COLUMN)); + deletedIds.add(c.getLong(Message.LIST_ID_COLUMN)); } } finally { c.close(); } + return first; + } + + @Override + public boolean sendLocalChanges(Serializer s) throws IOException { + ContentResolver cr = mContext.getContentResolver(); + + // Never upsync from these folders + if (mMailbox.mType == Mailbox.TYPE_DRAFTS || mMailbox.mType == Mailbox.TYPE_OUTBOX) { + return false; + } + + // This code is split out for unit testing purposes + boolean firstCommand = sendDeletedItems(s, mDeletedIdList, true); + // Find our trash mailbox, since deletions will have been moved there... long trashMailboxId = Mailbox.findMailboxOfType(mContext, mMailbox.mAccountKey, Mailbox.TYPE_TRASH); // Do the same now for updated items - c = cr.query(Message.UPDATED_CONTENT_URI, Message.LIST_PROJECTION, + Cursor c = cr.query(Message.UPDATED_CONTENT_URI, Message.LIST_PROJECTION, MessageColumns.MAILBOX_KEY + '=' + mMailbox.mId, null, null); // We keep track of the list of updated item id's as we did above with deleted items @@ -627,9 +670,9 @@ public class EmailSyncAdapter extends AbstractSyncAdapter { } // If the message is now in the trash folder, it has been deleted by the user if (currentCursor.getLong(UPDATES_MAILBOX_KEY_COLUMN) == trashMailboxId) { - if (first) { + if (firstCommand) { s.start(Tags.SYNC_COMMANDS); - first = false; + firstCommand = false; } // Send the command to delete this message s.start(Tags.SYNC_DELETE).data(Tags.SYNC_SERVER_ID, serverId).end(); @@ -659,9 +702,9 @@ public class EmailSyncAdapter extends AbstractSyncAdapter { continue; } - if (first) { + if (firstCommand) { s.start(Tags.SYNC_COMMANDS); - first = false; + firstCommand = false; } // Send the change to "read" and "favorite" (flagged) s.start(Tags.SYNC_CHANGE) @@ -708,7 +751,7 @@ public class EmailSyncAdapter extends AbstractSyncAdapter { c.close(); } - if (!first) { + if (!firstCommand) { s.end(); // SYNC_COMMANDS } return false; diff --git a/tests/src/com/android/exchange/EasEmailSyncAdapterTests.java b/tests/src/com/android/exchange/adapter/EmailSyncAdapterTests.java index a3e7daeec..a702428f7 100644 --- a/tests/src/com/android/exchange/EasEmailSyncAdapterTests.java +++ b/tests/src/com/android/exchange/adapter/EmailSyncAdapterTests.java @@ -14,22 +14,50 @@ * limitations under the License. */ -package com.android.exchange; +package com.android.exchange.adapter; +import com.android.email.provider.EmailContent; +import com.android.email.provider.EmailProvider; +import com.android.email.provider.ProviderTestUtils; import com.android.email.provider.EmailContent.Account; +import com.android.email.provider.EmailContent.Body; import com.android.email.provider.EmailContent.Mailbox; -import com.android.exchange.adapter.EmailSyncAdapter; +import com.android.email.provider.EmailContent.Message; +import com.android.exchange.EasSyncService; import com.android.exchange.adapter.EmailSyncAdapter.EasEmailSyncParser; -import android.test.AndroidTestCase; +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.test.ProviderTestCase2; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; import java.util.GregorianCalendar; import java.util.TimeZone; -public class EasEmailSyncAdapterTests extends AndroidTestCase { +public class EmailSyncAdapterTests extends ProviderTestCase2<EmailProvider> { + + EmailProvider mProvider; + Context mMockContext; + + public EmailSyncAdapterTests() { + super(EmailProvider.class, EmailProvider.EMAIL_AUTHORITY); + } + + @Override + public void setUp() throws Exception { + super.setUp(); + mMockContext = getMockContext(); + } + + @Override + public void tearDown() throws Exception { + super.tearDown(); + } /** * Create and return a short, simple InputStream that has at least four bytes, which is all @@ -100,4 +128,68 @@ public class EasEmailSyncAdapterTests extends AndroidTestCase { date = adapter.formatDateTime(calendar); assertEquals("2012-01-02T23:00:01.000Z", date); } + + public void testSendDeletedItems() throws IOException { + EasSyncService service = getTestService(); + EmailSyncAdapter adapter = new EmailSyncAdapter(service.mMailbox, service); + Serializer s = new Serializer(); + ArrayList<Long> ids = new ArrayList<Long>(); + ArrayList<Long> deletedIds = new ArrayList<Long>(); + + Context context = mMockContext; + adapter.mContext = context; + final ContentResolver resolver = context.getContentResolver(); + + // Create account and two mailboxes + Account acct = ProviderTestUtils.setupAccount("account", true, context); + adapter.mAccount = acct; + Mailbox box1 = ProviderTestUtils.setupMailbox("box1", acct.mId, true, context); + adapter.mMailbox = box1; + + // Create 3 messages + Message msg1 = + ProviderTestUtils.setupMessage("message1", acct.mId, box1.mId, true, true, context); + ids.add(msg1.mId); + Message msg2 = + ProviderTestUtils.setupMessage("message2", acct.mId, box1.mId, true, true, context); + ids.add(msg2.mId); + Message msg3 = + ProviderTestUtils.setupMessage("message3", acct.mId, box1.mId, true, true, context); + ids.add(msg3.mId); + assertEquals(3, EmailContent.count(context, Message.CONTENT_URI, null, null)); + + // Delete them + for (long id: ids) { + resolver.delete(ContentUris.withAppendedId(Message.SYNCED_CONTENT_URI, id), null, null); + } + + // Confirm that the messages are in the proper table + assertEquals(0, EmailContent.count(context, Message.CONTENT_URI, null, null)); + assertEquals(3, EmailContent.count(context, Message.DELETED_CONTENT_URI, null, null)); + + // Call code to send deletions; the id's of the ones actually deleted will be in the + // deletedIds list + adapter.sendDeletedItems(s, deletedIds, true); + assertEquals(3, deletedIds.size()); + + // Clear this out for the next test + deletedIds.clear(); + + // Create a new message + Message msg4 = + ProviderTestUtils.setupMessage("message3", acct.mId, box1.mId, true, true, context); + assertEquals(1, EmailContent.count(context, Message.CONTENT_URI, null, null)); + // Find the body for this message + Body body = Body.restoreBodyWithMessageId(context, msg4.mId); + // Set its source message to msg2's id + ContentValues values = new ContentValues(); + values.put(Body.SOURCE_MESSAGE_KEY, msg2.mId); + body.update(context, values); + + // Now send deletions again; this time only two should get deleted; msg2 should NOT be + // deleted as it's referenced by msg4 + adapter.sendDeletedItems(s, deletedIds, true); + assertEquals(2, deletedIds.size()); + assertFalse(deletedIds.contains(msg2.mId)); + } } |
