1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.exchange.adapter; 18 19 import android.content.ContentUris; 20 import android.content.ContentValues; 21 import android.test.suitebuilder.annotation.SmallTest; 22 23 import com.android.emailcommon.provider.Account; 24 import com.android.emailcommon.provider.EmailContent; 25 import com.android.emailcommon.provider.EmailContent.Body; 26 import com.android.emailcommon.provider.EmailContent.Message; 27 import com.android.emailcommon.provider.EmailContent.MessageColumns; 28 import com.android.emailcommon.provider.EmailContent.SyncColumns; 29 import com.android.emailcommon.provider.Mailbox; 30 import com.android.exchange.EasSyncService; 31 import com.android.exchange.adapter.EmailSyncAdapter.EasEmailSyncParser; 32 import com.android.exchange.adapter.EmailSyncAdapter.EasEmailSyncParser.ServerChange; 33 import com.android.exchange.provider.EmailContentSetupUtils; 34 35 import java.io.ByteArrayInputStream; 36 import java.io.IOException; 37 import java.util.ArrayList; 38 import java.util.GregorianCalendar; 39 import java.util.TimeZone; 40 @SmallTest 41 public class EmailSyncAdapterTests extends SyncAdapterTestCase<EmailSyncAdapter> { 42 43 private static final String WHERE_ACCOUNT_KEY = Message.ACCOUNT_KEY + "=?"; 44 private static final String[] ACCOUNT_ARGUMENT = new String[1]; 45 46 // A server id that is guaranteed to be test-related 47 private static final String TEST_SERVER_ID = "__1:22"; 48 49 public EmailSyncAdapterTests() { 50 super(); 51 } 52 53 /** 54 * Check functionality for getting mime type from a file name (using its extension) 55 * The default for all unknown files is application/octet-stream 56 */ 57 public void testGetMimeTypeFromFileName() throws IOException { 58 EasSyncService service = getTestService(); 59 EmailSyncAdapter adapter = new EmailSyncAdapter(service); 60 EasEmailSyncParser p = adapter.new EasEmailSyncParser(getTestInputStream(), adapter); 61 // Test a few known types 62 String mimeType = p.getMimeTypeFromFileName("foo.jpg"); 63 assertEquals("image/jpeg", mimeType); 64 // Make sure this is case insensitive 65 mimeType = p.getMimeTypeFromFileName("foo.JPG"); 66 assertEquals("image/jpeg", mimeType); 67 mimeType = p.getMimeTypeFromFileName("this_is_a_weird_filename.gif"); 68 assertEquals("image/gif", mimeType); 69 // Test an illegal file name ending with the extension prefix 70 mimeType = p.getMimeTypeFromFileName("foo."); 71 assertEquals("application/octet-stream", mimeType); 72 // Test a really awful name 73 mimeType = p.getMimeTypeFromFileName("....."); 74 assertEquals("application/octet-stream", mimeType); 75 // Test a bare file name (no extension) 76 mimeType = p.getMimeTypeFromFileName("foo"); 77 assertEquals("application/octet-stream", mimeType); 78 // And no name at all (null isn't a valid input) 79 mimeType = p.getMimeTypeFromFileName(""); 80 assertEquals("application/octet-stream", mimeType); 81 } 82 83 public void testFormatDateTime() throws IOException { 84 EmailSyncAdapter adapter = getTestSyncAdapter(EmailSyncAdapter.class); 85 GregorianCalendar calendar = new GregorianCalendar(TimeZone.getTimeZone("GMT")); 86 // Calendar is odd, months are zero based, so the first 11 below is December... 87 calendar.set(2008, 11, 11, 18, 19, 20); 88 String date = adapter.formatDateTime(calendar); 89 assertEquals("2008-12-11T18:19:20.000Z", date); 90 calendar.clear(); 91 calendar.set(2012, 0, 2, 23, 0, 1); 92 date = adapter.formatDateTime(calendar); 93 assertEquals("2012-01-02T23:00:01.000Z", date); 94 } 95 96 public void testSendDeletedItems() throws IOException { 97 setupAccountMailboxAndMessages(0); 98 // Setup our adapter and parser 99 setupSyncParserAndAdapter(mAccount, mMailbox); 100 101 Serializer s = new Serializer(); 102 ArrayList<Long> ids = new ArrayList<Long>(); 103 ArrayList<Long> deletedIds = new ArrayList<Long>(); 104 105 // Create account and two mailboxes 106 mSyncAdapter.mAccount = mAccount; 107 Mailbox box1 = EmailContentSetupUtils.setupMailbox("box1", mAccount.mId, true, 108 mProviderContext); 109 mSyncAdapter.mMailbox = box1; 110 111 // Create 3 messages 112 Message msg1 = EmailContentSetupUtils.setupMessage("message1", mAccount.mId, box1.mId, 113 true, true, mProviderContext); 114 ids.add(msg1.mId); 115 Message msg2 = EmailContentSetupUtils.setupMessage("message2", mAccount.mId, box1.mId, 116 true, true, mProviderContext); 117 ids.add(msg2.mId); 118 Message msg3 = EmailContentSetupUtils.setupMessage("message3", mAccount.mId, box1.mId, 119 true, true, mProviderContext); 120 ids.add(msg3.mId); 121 assertEquals(3, EmailContent.count(mProviderContext, Message.CONTENT_URI, WHERE_ACCOUNT_KEY, 122 getAccountArgument(mAccount.mId))); 123 124 // Delete them 125 for (long id: ids) { 126 mResolver.delete(ContentUris.withAppendedId(Message.SYNCED_CONTENT_URI, id), 127 null, null); 128 } 129 130 // Confirm that the messages are in the proper table 131 assertEquals(0, EmailContent.count(mProviderContext, Message.CONTENT_URI, WHERE_ACCOUNT_KEY, 132 getAccountArgument(mAccount.mId))); 133 assertEquals(3, EmailContent.count(mProviderContext, Message.DELETED_CONTENT_URI, 134 WHERE_ACCOUNT_KEY, getAccountArgument(mAccount.mId))); 135 136 // Call code to send deletions; the id's of the ones actually deleted will be in the 137 // deletedIds list 138 mSyncAdapter.sendDeletedItems(s, deletedIds, true); 139 assertEquals(3, deletedIds.size()); 140 141 // Clear this out for the next test 142 deletedIds.clear(); 143 144 // Create a new message 145 Message msg4 = EmailContentSetupUtils.setupMessage("message4", mAccount.mId, box1.mId, 146 true, true, mProviderContext); 147 assertEquals(1, EmailContent.count(mProviderContext, Message.CONTENT_URI, WHERE_ACCOUNT_KEY, 148 getAccountArgument(mAccount.mId))); 149 // Find the body for this message 150 Body body = Body.restoreBodyWithMessageId(mProviderContext, msg4.mId); 151 // Set its source message to msg2's id 152 ContentValues values = new ContentValues(); 153 values.put(Body.SOURCE_MESSAGE_KEY, msg2.mId); 154 body.update(mProviderContext, values); 155 156 // Now send deletions again; this time only two should get deleted; msg2 should NOT be 157 // deleted as it's referenced by msg4 158 mSyncAdapter.sendDeletedItems(s, deletedIds, true); 159 assertEquals(2, deletedIds.size()); 160 assertFalse(deletedIds.contains(msg2.mId)); 161 } 162 163 private String[] getAccountArgument(long id) { 164 ACCOUNT_ARGUMENT[0] = Long.toString(id); 165 return ACCOUNT_ARGUMENT; 166 } 167 168 void setupSyncParserAndAdapter(Account account, Mailbox mailbox) throws IOException { 169 EasSyncService service = getTestService(account, mailbox); 170 mSyncAdapter = new EmailSyncAdapter(service); 171 mSyncParser = mSyncAdapter.new EasEmailSyncParser(getTestInputStream(), mSyncAdapter); 172 } 173 174 ArrayList<Long> setupAccountMailboxAndMessages(int numMessages) { 175 ArrayList<Long> ids = new ArrayList<Long>(); 176 177 // Create account and two mailboxes 178 mAccount = EmailContentSetupUtils.setupAccount("account", true, mProviderContext); 179 mMailbox = EmailContentSetupUtils.setupMailbox("box1", mAccount.mId, true, 180 mProviderContext); 181 182 for (int i = 0; i < numMessages; i++) { 183 Message msg = EmailContentSetupUtils.setupMessage("message" + i, mAccount.mId, 184 mMailbox.mId, true, true, mProviderContext); 185 ids.add(msg.mId); 186 } 187 188 assertEquals(numMessages, EmailContent.count(mProviderContext, Message.CONTENT_URI, 189 WHERE_ACCOUNT_KEY, getAccountArgument(mAccount.mId))); 190 return ids; 191 } 192 193 public void testDeleteParser() throws IOException { 194 // Setup some messages 195 ArrayList<Long> messageIds = setupAccountMailboxAndMessages(3); 196 ContentValues cv = new ContentValues(); 197 cv.put(SyncColumns.SERVER_ID, TEST_SERVER_ID); 198 long deleteMessageId = messageIds.get(1); 199 mResolver.update(ContentUris.withAppendedId(Message.CONTENT_URI, deleteMessageId), cv, 200 null, null); 201 202 // Setup our adapter and parser 203 setupSyncParserAndAdapter(mAccount, mMailbox); 204 205 // Set up an input stream with a delete command 206 Serializer s = new Serializer(false); 207 s.start(Tags.SYNC_DELETE).data(Tags.SYNC_SERVER_ID, TEST_SERVER_ID).end().done(); 208 byte[] bytes = s.toByteArray(); 209 mSyncParser.resetInput(new ByteArrayInputStream(bytes)); 210 mSyncParser.nextTag(0); 211 212 // Run the delete parser 213 ArrayList<Long> deleteList = new ArrayList<Long>(); 214 mSyncParser.deleteParser(deleteList, Tags.SYNC_DELETE); 215 // It should have found the message 216 assertEquals(1, deleteList.size()); 217 long id = deleteList.get(0); 218 // And the id's should match 219 assertEquals(deleteMessageId, id); 220 } 221 222 public void testChangeParser() throws IOException { 223 // Setup some messages 224 ArrayList<Long> messageIds = setupAccountMailboxAndMessages(3); 225 ContentValues cv = new ContentValues(); 226 int randomFlags = Message.FLAG_INCOMING_MEETING_CANCEL | Message.FLAG_TYPE_FORWARD; 227 cv.put(SyncColumns.SERVER_ID, TEST_SERVER_ID); 228 cv.put(MessageColumns.FLAGS, randomFlags); 229 long changeMessageId = messageIds.get(1); 230 mResolver.update(ContentUris.withAppendedId(Message.CONTENT_URI, changeMessageId), cv, 231 null, null); 232 233 // Setup our adapter and parser 234 setupSyncParserAndAdapter(mAccount, mMailbox); 235 236 // Set up an input stream with a change command (marking TEST_SERVER_ID unread) 237 // Note that the test message creation code sets read to "true" 238 Serializer s = new Serializer(false); 239 s.start(Tags.SYNC_CHANGE).data(Tags.SYNC_SERVER_ID, TEST_SERVER_ID); 240 s.start(Tags.SYNC_APPLICATION_DATA); 241 s.data(Tags.EMAIL_READ, "0"); 242 s.data(Tags.EMAIL2_LAST_VERB_EXECUTED, 243 Integer.toString(EmailSyncAdapter.LAST_VERB_FORWARD)); 244 s.end().end().done(); 245 byte[] bytes = s.toByteArray(); 246 mSyncParser.resetInput(new ByteArrayInputStream(bytes)); 247 mSyncParser.nextTag(0); 248 249 // Run the delete parser 250 ArrayList<ServerChange> changeList = new ArrayList<ServerChange>(); 251 mSyncParser.changeParser(changeList); 252 // It should have found the message 253 assertEquals(1, changeList.size()); 254 // And the id's should match 255 ServerChange change = changeList.get(0); 256 assertEquals(changeMessageId, change.id); 257 assertNotNull(change.read); 258 assertFalse(change.read); 259 // Make sure we see the forwarded flag AND that the original flags are preserved 260 assertEquals((Integer)(randomFlags | Message.FLAG_FORWARDED), change.flags); 261 } 262 263 public void testCleanup() throws IOException { 264 // Setup some messages 265 ArrayList<Long> messageIds = setupAccountMailboxAndMessages(3); 266 // Setup our adapter and parser 267 setupSyncParserAndAdapter(mAccount, mMailbox); 268 269 // Delete two of the messages, change one 270 long id = messageIds.get(0); 271 mResolver.delete(ContentUris.withAppendedId(Message.SYNCED_CONTENT_URI, id), 272 null, null); 273 mSyncAdapter.mDeletedIdList.add(id); 274 id = messageIds.get(1); 275 mResolver.delete(ContentUris.withAppendedId(Message.SYNCED_CONTENT_URI, 276 id), null, null); 277 mSyncAdapter.mDeletedIdList.add(id); 278 id = messageIds.get(2); 279 ContentValues cv = new ContentValues(); 280 cv.put(Message.FLAG_READ, 0); 281 mResolver.update(ContentUris.withAppendedId(Message.SYNCED_CONTENT_URI, 282 id), cv, null, null); 283 mSyncAdapter.mUpdatedIdList.add(id); 284 285 // The changed message should still exist 286 assertEquals(1, EmailContent.count(mProviderContext, Message.CONTENT_URI, WHERE_ACCOUNT_KEY, 287 getAccountArgument(mAccount.mId))); 288 289 // As well, the two deletions and one update 290 assertEquals(2, EmailContent.count(mProviderContext, Message.DELETED_CONTENT_URI, 291 WHERE_ACCOUNT_KEY, getAccountArgument(mAccount.mId))); 292 assertEquals(1, EmailContent.count(mProviderContext, Message.UPDATED_CONTENT_URI, 293 WHERE_ACCOUNT_KEY, getAccountArgument(mAccount.mId))); 294 295 // Cleanup (i.e. after sync); should remove items from delete/update tables 296 mSyncAdapter.cleanup(); 297 298 // The three should be gone 299 assertEquals(0, EmailContent.count(mProviderContext, Message.DELETED_CONTENT_URI, 300 WHERE_ACCOUNT_KEY, getAccountArgument(mAccount.mId))); 301 assertEquals(0, EmailContent.count(mProviderContext, Message.UPDATED_CONTENT_URI, 302 WHERE_ACCOUNT_KEY, getAccountArgument(mAccount.mId))); 303 } 304 } 305