Merge change Iad377728 into eclair
* changes: Don't delete referenced messages from the Exchange server DO NOT MERGE
This commit is contained in:
commit
c874824ca8
@ -185,7 +185,9 @@ public abstract class EmailContent {
|
|||||||
public static final String HTML_REPLY = "htmlReply";
|
public static final String HTML_REPLY = "htmlReply";
|
||||||
// Replied-to or forwarded body (in text form)
|
// Replied-to or forwarded body (in text form)
|
||||||
public static final String TEXT_REPLY = "textReply";
|
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";
|
public static final String SOURCE_MESSAGE_KEY = "sourceMessageKey";
|
||||||
// The text to be placed between a reply/forward response and the original message
|
// The text to be placed between a reply/forward response and the original message
|
||||||
public static final String INTRO_TEXT = "introText";
|
public static final String INTRO_TEXT = "introText";
|
||||||
@ -519,7 +521,7 @@ public abstract class EmailContent {
|
|||||||
public String mBcc;
|
public String mBcc;
|
||||||
public String mReplyTo;
|
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
|
// but they are NOT persisted directly by EmailProvider
|
||||||
transient public String mText;
|
transient public String mText;
|
||||||
transient public String mHtml;
|
transient public String mHtml;
|
||||||
|
@ -24,6 +24,7 @@ import com.android.email.provider.EmailProvider;
|
|||||||
import com.android.email.provider.EmailContent.Account;
|
import com.android.email.provider.EmailContent.Account;
|
||||||
import com.android.email.provider.EmailContent.AccountColumns;
|
import com.android.email.provider.EmailContent.AccountColumns;
|
||||||
import com.android.email.provider.EmailContent.Attachment;
|
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.Mailbox;
|
||||||
import com.android.email.provider.EmailContent.Message;
|
import com.android.email.provider.EmailContent.Message;
|
||||||
import com.android.email.provider.EmailContent.MessageColumns;
|
import com.android.email.provider.EmailContent.MessageColumns;
|
||||||
@ -65,8 +66,10 @@ public class EmailSyncAdapter extends AbstractSyncAdapter {
|
|||||||
private static final String[] MESSAGE_ID_SUBJECT_PROJECTION =
|
private static final String[] MESSAGE_ID_SUBJECT_PROJECTION =
|
||||||
new String[] { Message.RECORD_ID, MessageColumns.SUBJECT };
|
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> mDeletedIdList = new ArrayList<Long>();
|
||||||
ArrayList<Long> mUpdatedIdList = 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) {
|
private Cursor getServerIdCursor(String serverId, String[] projection) {
|
||||||
bindArguments[0] = serverId;
|
mBindArguments[0] = serverId;
|
||||||
bindArguments[1] = mMailboxIdAsString;
|
mBindArguments[1] = mMailboxIdAsString;
|
||||||
return mContentResolver.query(Message.CONTENT_URI, projection,
|
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 {
|
private void deleteParser(ArrayList<Long> deletes, int entryTag) throws IOException {
|
||||||
@ -562,6 +565,68 @@ public class EmailSyncAdapter extends AbstractSyncAdapter {
|
|||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
// 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
|
||||||
|
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();
|
||||||
|
deletedIds.add(c.getLong(Message.LIST_ID_COLUMN));
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
c.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
return first;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean sendLocalChanges(Serializer s) throws IOException {
|
public boolean sendLocalChanges(Serializer s) throws IOException {
|
||||||
ContentResolver cr = mContext.getContentResolver();
|
ContentResolver cr = mContext.getContentResolver();
|
||||||
@ -571,37 +636,15 @@ public class EmailSyncAdapter extends AbstractSyncAdapter {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find any of our deleted items
|
// This code is split out for unit testing purposes
|
||||||
Cursor c = cr.query(Message.DELETED_CONTENT_URI, Message.LIST_PROJECTION,
|
boolean firstCommand = sendDeletedItems(s, mDeletedIdList, true);
|
||||||
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();
|
|
||||||
try {
|
|
||||||
while (c.moveToNext()) {
|
|
||||||
String serverId = c.getString(Message.LIST_SERVER_ID_COLUMN);
|
|
||||||
// Keep going if there's no serverId
|
|
||||||
if (serverId == null) {
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
c.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find our trash mailbox, since deletions will have been moved there...
|
// Find our trash mailbox, since deletions will have been moved there...
|
||||||
long trashMailboxId =
|
long trashMailboxId =
|
||||||
Mailbox.findMailboxOfType(mContext, mMailbox.mAccountKey, Mailbox.TYPE_TRASH);
|
Mailbox.findMailboxOfType(mContext, mMailbox.mAccountKey, Mailbox.TYPE_TRASH);
|
||||||
|
|
||||||
// Do the same now for updated items
|
// 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);
|
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
|
// 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 the message is now in the trash folder, it has been deleted by the user
|
||||||
if (currentCursor.getLong(UPDATES_MAILBOX_KEY_COLUMN) == trashMailboxId) {
|
if (currentCursor.getLong(UPDATES_MAILBOX_KEY_COLUMN) == trashMailboxId) {
|
||||||
if (first) {
|
if (firstCommand) {
|
||||||
s.start(Tags.SYNC_COMMANDS);
|
s.start(Tags.SYNC_COMMANDS);
|
||||||
first = false;
|
firstCommand = false;
|
||||||
}
|
}
|
||||||
// Send the command to delete this message
|
// Send the command to delete this message
|
||||||
s.start(Tags.SYNC_DELETE).data(Tags.SYNC_SERVER_ID, serverId).end();
|
s.start(Tags.SYNC_DELETE).data(Tags.SYNC_SERVER_ID, serverId).end();
|
||||||
@ -659,9 +702,9 @@ public class EmailSyncAdapter extends AbstractSyncAdapter {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (first) {
|
if (firstCommand) {
|
||||||
s.start(Tags.SYNC_COMMANDS);
|
s.start(Tags.SYNC_COMMANDS);
|
||||||
first = false;
|
firstCommand = false;
|
||||||
}
|
}
|
||||||
// Send the change to "read" and "favorite" (flagged)
|
// Send the change to "read" and "favorite" (flagged)
|
||||||
s.start(Tags.SYNC_CHANGE)
|
s.start(Tags.SYNC_CHANGE)
|
||||||
@ -708,7 +751,7 @@ public class EmailSyncAdapter extends AbstractSyncAdapter {
|
|||||||
c.close();
|
c.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!first) {
|
if (!firstCommand) {
|
||||||
s.end(); // SYNC_COMMANDS
|
s.end(); // SYNC_COMMANDS
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -14,22 +14,50 @@
|
|||||||
* limitations under the License.
|
* 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.Account;
|
||||||
|
import com.android.email.provider.EmailContent.Body;
|
||||||
import com.android.email.provider.EmailContent.Mailbox;
|
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 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.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.GregorianCalendar;
|
import java.util.GregorianCalendar;
|
||||||
import java.util.TimeZone;
|
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
|
* 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);
|
date = adapter.formatDateTime(calendar);
|
||||||
assertEquals("2012-01-02T23:00:01.000Z", date);
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user