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