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
17package com.android.exchange.adapter;
18
19import android.content.ContentUris;
20import android.content.ContentValues;
21import android.test.suitebuilder.annotation.SmallTest;
22
23import com.android.emailcommon.provider.Account;
24import com.android.emailcommon.provider.EmailContent;
25import com.android.emailcommon.provider.EmailContent.Body;
26import com.android.emailcommon.provider.EmailContent.Message;
27import com.android.emailcommon.provider.EmailContent.MessageColumns;
28import com.android.emailcommon.provider.EmailContent.SyncColumns;
29import com.android.emailcommon.provider.Mailbox;
30import com.android.exchange.EasSyncService;
31import com.android.exchange.adapter.EmailSyncAdapter.EasEmailSyncParser;
32import com.android.exchange.adapter.EmailSyncAdapter.EasEmailSyncParser.ServerChange;
33import com.android.exchange.provider.EmailContentSetupUtils;
34
35import java.io.ByteArrayInputStream;
36import java.io.IOException;
37import java.util.ArrayList;
38import java.util.GregorianCalendar;
39import java.util.TimeZone;
40@SmallTest
41public 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