• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2009 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.email;
18 
19 import com.android.email.mail.Address;
20 import com.android.email.mail.BodyPart;
21 import com.android.email.mail.Flag;
22 import com.android.email.mail.Message;
23 import com.android.email.mail.MessageTestUtils;
24 import com.android.email.mail.MessagingException;
25 import com.android.email.mail.Part;
26 import com.android.email.mail.Message.RecipientType;
27 import com.android.email.mail.MessageTestUtils.MessageBuilder;
28 import com.android.email.mail.MessageTestUtils.MultipartBuilder;
29 import com.android.email.mail.internet.MimeBodyPart;
30 import com.android.email.mail.internet.MimeHeader;
31 import com.android.email.mail.internet.MimeMessage;
32 import com.android.email.mail.internet.MimeUtility;
33 import com.android.email.mail.internet.TextBody;
34 import com.android.email.provider.EmailContent;
35 import com.android.email.provider.EmailProvider;
36 import com.android.email.provider.ProviderTestUtils;
37 import com.android.email.provider.EmailContent.Attachment;
38 
39 import android.content.ContentUris;
40 import android.content.Context;
41 import android.database.Cursor;
42 import android.net.Uri;
43 import android.test.ProviderTestCase2;
44 
45 import java.io.IOException;
46 import java.util.ArrayList;
47 import java.util.Date;
48 
49 /**
50  * Tests of the Legacy Conversions code (used by MessagingController).
51  *
52  * NOTE:  It would probably make sense to rewrite this using a MockProvider, instead of the
53  * ProviderTestCase (which is a real provider running on a temp database).  This would be more of
54  * a true "unit test".
55  *
56  * You can run this entire test case with:
57  *   runtest -c com.android.email.LegacyConversionsTests email
58  */
59 public class LegacyConversionsTests extends ProviderTestCase2<EmailProvider> {
60 
61     private static final String UID = "UID.12345678";
62     private static final String SENDER = "sender@android.com";
63     private static final String RECIPIENT_TO = "recipient-to@android.com";
64     private static final String RECIPIENT_CC = "recipient-cc@android.com";
65     private static final String RECIPIENT_BCC = "recipient-bcc@android.com";
66     private static final String REPLY_TO = "reply-to@android.com";
67     private static final String SUBJECT = "This is the subject";
68     private static final String BODY = "This is the body.  This is also the body.";
69     private static final String MESSAGE_ID = "Test-Message-ID";
70     private static final String MESSAGE_ID_2 = "Test-Message-ID-Second";
71 
72     EmailProvider mProvider;
73     Context mProviderContext;
74     Context mContext;
75 
LegacyConversionsTests()76     public LegacyConversionsTests() {
77         super(EmailProvider.class, EmailProvider.EMAIL_AUTHORITY);
78     }
79 
80     @Override
setUp()81     public void setUp() throws Exception {
82         super.setUp();
83         mProviderContext = getMockContext();
84         mContext = getContext();
85     }
86 
87     @Override
tearDown()88     public void tearDown() throws Exception {
89         super.tearDown();
90     }
91 
92     /**
93      * TODO: basic Legacy -> Provider Message conversions
94      * TODO: basic Legacy -> Provider Body conversions
95      * TODO: rainy day tests of all kinds
96      */
97 
98     /**
99      * Test basic conversion from Store message to Provider message
100      *
101      * TODO: Not a complete test of all fields, and some fields need special tests (e.g. flags)
102      * TODO: There are many special cases in the tested function, that need to be
103      * tested here as well.
104      */
testUpdateMessageFields()105     public void testUpdateMessageFields() throws MessagingException {
106         MimeMessage message = buildTestMessage(RECIPIENT_TO, RECIPIENT_CC, RECIPIENT_BCC,
107                 REPLY_TO, SENDER, SUBJECT, null);
108         EmailContent.Message localMessage = new EmailContent.Message();
109 
110         boolean result = LegacyConversions.updateMessageFields(localMessage, message, 1, 1);
111         assertTrue(result);
112         checkProviderMessage("testUpdateMessageFields", message, localMessage);
113     }
114 
115     /**
116      * Test basic conversion from Store message to Provider message, when the provider message
117      * does not have a proper message-id.
118      */
testUpdateMessageFieldsNoMessageId()119     public void testUpdateMessageFieldsNoMessageId() throws MessagingException {
120         MimeMessage message = buildTestMessage(RECIPIENT_TO, RECIPIENT_CC, RECIPIENT_BCC,
121                 REPLY_TO, SENDER, SUBJECT, null);
122         EmailContent.Message localMessage = new EmailContent.Message();
123 
124         // If the source message-id is null, the target should be left as-is
125         localMessage.mMessageId = MESSAGE_ID_2;
126         message.removeHeader("Message-ID");
127 
128         boolean result = LegacyConversions.updateMessageFields(localMessage, message, 1, 1);
129         assertTrue(result);
130         assertEquals(MESSAGE_ID_2, localMessage.mMessageId);
131     }
132 
133     /**
134      * Build a lightweight Store message with simple field population
135      */
buildTestMessage(String to, String cc, String bcc, String replyTo, String sender, String subject, String content)136     private MimeMessage buildTestMessage(String to, String cc, String bcc, String replyTo,
137             String sender, String subject, String content) throws MessagingException {
138         MimeMessage message = new MimeMessage();
139 
140         if (to != null) {
141             Address[] addresses = Address.parse(to);
142             message.setRecipients(RecipientType.TO, addresses);
143         }
144         if (cc != null) {
145             Address[] addresses = Address.parse(cc);
146             message.setRecipients(RecipientType.CC, addresses);
147         }
148         if (bcc != null) {
149             Address[] addresses = Address.parse(bcc);
150             message.setRecipients(RecipientType.BCC, addresses);
151         }
152         if (replyTo != null) {
153             Address[] addresses = Address.parse(replyTo);
154             message.setReplyTo(addresses);
155         }
156         if (sender != null) {
157             Address[] addresses = Address.parse(sender);
158             message.setFrom(Address.parse(sender)[0]);
159         }
160         if (subject != null) {
161             message.setSubject(subject);
162         }
163         if (content != null) {
164             TextBody body = new TextBody(content);
165             message.setBody(body);
166         }
167 
168         message.setUid(UID);
169         message.setSentDate(new Date());
170         message.setInternalDate(new Date());
171         message.setMessageId(MESSAGE_ID);
172         return message;
173     }
174 
175     /**
176      * Basic test of body parts conversion from Store message to Provider message.
177      * This tests that a null body part simply results in null text, and does not crash
178      * or return "null".
179      *
180      * TODO very incomplete, there are many permutations to be explored
181      */
testUpdateBodyFieldsNullText()182     public void testUpdateBodyFieldsNullText() throws MessagingException {
183         EmailContent.Body localBody = new EmailContent.Body();
184         EmailContent.Message localMessage = new EmailContent.Message();
185         ArrayList<Part> viewables = new ArrayList<Part>();
186         Part emptyTextPart = new MimeBodyPart(null, "text/plain");
187         viewables.add(emptyTextPart);
188 
189         // a "null" body part of type text/plain should result in a null mTextContent
190         boolean result = LegacyConversions.updateBodyFields(localBody, localMessage, viewables);
191         assertTrue(result);
192         assertNull(localBody.mTextContent);
193     }
194 
195     /**
196      * Sunny day test of adding attachments from an IMAP message.
197      */
testAddAttachments()198     public void testAddAttachments() throws MessagingException, IOException {
199         // Prepare a local message to add the attachments to
200         final long accountId = 1;
201         final long mailboxId = 1;
202         final EmailContent.Message localMessage = ProviderTestUtils.setupMessage(
203                 "local-message", accountId, mailboxId, false, true, mProviderContext);
204 
205         // Prepare a legacy message with attachments
206         final Message legacyMessage = prepareLegacyMessageWithAttachments(2);
207 
208         // Now, convert from legacy to provider and see what happens
209         ArrayList<Part> viewables = new ArrayList<Part>();
210         ArrayList<Part> attachments = new ArrayList<Part>();
211         MimeUtility.collectParts(legacyMessage, viewables, attachments);
212         LegacyConversions.updateAttachments(mProviderContext, localMessage, attachments);
213 
214         // Read back all attachments for message and check field values
215         Uri uri = ContentUris.withAppendedId(Attachment.MESSAGE_ID_URI, localMessage.mId);
216         Cursor c = mProviderContext.getContentResolver().query(uri, Attachment.CONTENT_PROJECTION,
217                 null, null, null);
218         try {
219             assertEquals(2, c.getCount());
220             while (c.moveToNext()) {
221                 Attachment attachment = Attachment.getContent(c, Attachment.class);
222                 if ("101".equals(attachment.mLocation)) {
223                     checkAttachment("attachment1Part", attachments.get(0), attachment);
224                 } else if ("102".equals(attachment.mLocation)) {
225                     checkAttachment("attachment2Part", attachments.get(1), attachment);
226                 } else {
227                     fail("Unexpected attachment with location " + attachment.mLocation);
228                 }
229             }
230         } finally {
231             c.close();
232         }
233     }
234 
235     /**
236      * Test that attachments aren't re-added in the DB.  This supports the "partial download"
237      * nature of POP messages.
238      */
testAddDuplicateAttachments()239     public void testAddDuplicateAttachments() throws MessagingException, IOException {
240         // Prepare a local message to add the attachments to
241         final long accountId = 1;
242         final long mailboxId = 1;
243         final EmailContent.Message localMessage = ProviderTestUtils.setupMessage(
244                 "local-message", accountId, mailboxId, false, true, mProviderContext);
245 
246         // Prepare a legacy message with attachments
247         Message legacyMessage = prepareLegacyMessageWithAttachments(2);
248 
249         // Now, convert from legacy to provider and see what happens
250         ArrayList<Part> viewables = new ArrayList<Part>();
251         ArrayList<Part> attachments = new ArrayList<Part>();
252         MimeUtility.collectParts(legacyMessage, viewables, attachments);
253         LegacyConversions.updateAttachments(mProviderContext, localMessage, attachments);
254 
255         // Confirm two attachment objects created
256         Uri uri = ContentUris.withAppendedId(Attachment.MESSAGE_ID_URI, localMessage.mId);
257         assertEquals(2, EmailContent.count(mProviderContext, uri, null, null));
258 
259         // Now add the attachments again and confirm there are still only two
260         LegacyConversions.updateAttachments(mProviderContext, localMessage, attachments);
261         assertEquals(2, EmailContent.count(mProviderContext, uri, null, null));
262 
263         // Now add a 3rd & 4th attachment and make sure the total is 4, not 2 or 6
264         legacyMessage = prepareLegacyMessageWithAttachments(4);
265         viewables = new ArrayList<Part>();
266         attachments = new ArrayList<Part>();
267         MimeUtility.collectParts(legacyMessage, viewables, attachments);
268         LegacyConversions.updateAttachments(mProviderContext, localMessage, attachments);
269         assertEquals(4, EmailContent.count(mProviderContext, uri, null, null));
270     }
271 
272     /**
273      * Prepare a legacy message with 1+ attachments
274      */
prepareLegacyMessageWithAttachments(int numAttachments)275     private static Message prepareLegacyMessageWithAttachments(int numAttachments)
276             throws MessagingException {
277         // First, build one or more attachment parts
278         MultipartBuilder mpBuilder = new MultipartBuilder("multipart/mixed");
279         for (int i = 1; i <= numAttachments; ++i) {
280             BodyPart attachmentPart = MessageTestUtils.bodyPart("image/jpg", null);
281 
282             // name=attachmentN size=N00 location=10N
283             attachmentPart.setHeader(MimeHeader.HEADER_CONTENT_TYPE,
284                     "image/jpg;\n name=\"attachment" + i + "\"");
285             attachmentPart.setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, "base64");
286             attachmentPart.setHeader(MimeHeader.HEADER_CONTENT_DISPOSITION,
287                     "attachment;\n filename=\"attachment2\";\n size=" + i + "00");
288             attachmentPart.setHeader(MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA, "10" + i);
289 
290             mpBuilder.addBodyPart(attachmentPart);
291         }
292 
293         // Now build a message with them
294         final Message legacyMessage = new MessageBuilder()
295             .setBody(new MultipartBuilder("multipart/mixed")
296                      .addBodyPart(MessageTestUtils.bodyPart("text/html", null))
297                      .addBodyPart(mpBuilder.buildBodyPart())
298                      .build())
299                 .build();
300 
301         return legacyMessage;
302     }
303 
304     /**
305      * Test the stringInequal helper
306      */
testStringInequal()307     public void testStringInequal() {
308         // Pairs that are "equal"
309         assertFalse(LegacyConversions.stringNotEqual(null, null));
310         assertFalse(LegacyConversions.stringNotEqual(null, ""));
311         assertFalse(LegacyConversions.stringNotEqual("", null));
312         assertFalse(LegacyConversions.stringNotEqual("", ""));
313         assertFalse(LegacyConversions.stringNotEqual("string-equal", "string-equal"));
314         // Pairs that are "inequal"
315         assertTrue(LegacyConversions.stringNotEqual(null, "string-inequal"));
316         assertTrue(LegacyConversions.stringNotEqual("", "string-inequal"));
317         assertTrue(LegacyConversions.stringNotEqual("string-inequal", null));
318         assertTrue(LegacyConversions.stringNotEqual("string-inequal", ""));
319         assertTrue(LegacyConversions.stringNotEqual("string-inequal-a", "string-inequal-b"));
320     }
321 
322     /**
323      * Compare attachment that was converted from Part (expected) to Provider Attachment (actual)
324      *
325      * TODO content URI should only be set if we also saved a file
326      * TODO other data encodings
327      */
checkAttachment(String tag, Part expected, EmailContent.Attachment actual)328     private void checkAttachment(String tag, Part expected, EmailContent.Attachment actual)
329             throws MessagingException {
330         String contentType = MimeUtility.unfoldAndDecode(expected.getContentType());
331         String expectedName = MimeUtility.getHeaderParameter(contentType, "name");
332         assertEquals(tag, expectedName, actual.mFileName);
333         assertEquals(tag, expected.getMimeType(), actual.mMimeType);
334         String disposition = expected.getDisposition();
335         String sizeString = MimeUtility.getHeaderParameter(disposition, "size");
336         long expectedSize = Long.parseLong(sizeString);
337         assertEquals(tag, expectedSize, actual.mSize);
338         assertEquals(tag, expected.getContentId(), actual.mContentId);
339         assertNull(tag, actual.mContentUri);
340         assertTrue(tag, 0 != actual.mMessageKey);
341         String expectedPartId =
342             expected.getHeader(MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA)[0];
343         assertEquals(tag, expectedPartId, actual.mLocation);
344         assertEquals(tag, "B", actual.mEncoding);
345     }
346 
347     /**
348      * TODO: Sunny day test of adding attachments from a POP message.
349      */
350 
351     /**
352      * Sunny day tests of converting an original message to a legacy message
353      */
testMakeLegacyMessage()354     public void testMakeLegacyMessage() throws MessagingException {
355         // Set up and store a message in the provider
356         long account1Id = 1;
357         long mailbox1Id = 1;
358 
359         // Test message 1: No body
360         EmailContent.Message localMessage1 = ProviderTestUtils.setupMessage("make-legacy",
361                 account1Id, mailbox1Id, false, true, mProviderContext);
362         Message getMessage1 = LegacyConversions.makeMessage(mProviderContext, localMessage1);
363         checkLegacyMessage("no body", localMessage1, getMessage1);
364 
365         // Test message 2: Simple body
366         EmailContent.Message localMessage2 = ProviderTestUtils.setupMessage("make-legacy",
367                 account1Id, mailbox1Id, true, false, mProviderContext);
368         localMessage2.mTextReply = null;
369         localMessage2.mHtmlReply = null;
370         localMessage2.mIntroText = null;
371         localMessage2.mFlags &= ~EmailContent.Message.FLAG_TYPE_MASK;
372         localMessage2.save(mProviderContext);
373         Message getMessage2 = LegacyConversions.makeMessage(mProviderContext, localMessage2);
374         checkLegacyMessage("simple body", localMessage2, getMessage2);
375 
376         // Test message 3: Body + replied-to text
377         EmailContent.Message localMessage3 = ProviderTestUtils.setupMessage("make-legacy",
378                 account1Id, mailbox1Id, true, false, mProviderContext);
379         localMessage3.mFlags &= ~EmailContent.Message.FLAG_TYPE_MASK;
380         localMessage3.mFlags |= EmailContent.Message.FLAG_TYPE_REPLY;
381         localMessage3.save(mProviderContext);
382         Message getMessage3 = LegacyConversions.makeMessage(mProviderContext, localMessage3);
383         checkLegacyMessage("reply-to", localMessage3, getMessage3);
384 
385         // Test message 4: Body + forwarded text
386         EmailContent.Message localMessage4 = ProviderTestUtils.setupMessage("make-legacy",
387                 account1Id, mailbox1Id, true, false, mProviderContext);
388         localMessage4.mFlags &= ~EmailContent.Message.FLAG_TYPE_MASK;
389         localMessage4.mFlags |= EmailContent.Message.FLAG_TYPE_FORWARD;
390         localMessage4.save(mProviderContext);
391         Message getMessage4 = LegacyConversions.makeMessage(mProviderContext, localMessage4);
392         checkLegacyMessage("forwarding", localMessage4, getMessage4);
393     }
394 
395     /**
396      * Check equality of a pair of converted messages
397      */
checkProviderMessage(String tag, Message expect, EmailContent.Message actual)398     private void checkProviderMessage(String tag, Message expect, EmailContent.Message actual)
399             throws MessagingException {
400         assertEquals(tag, expect.getUid(), actual.mServerId);
401         assertEquals(tag, expect.getSubject(), actual.mSubject);
402         assertEquals(tag, Address.pack(expect.getFrom()), actual.mFrom);
403         assertEquals(tag, expect.getSentDate().getTime(), actual.mTimeStamp);
404         assertEquals(tag, Address.pack(expect.getRecipients(RecipientType.TO)), actual.mTo);
405         assertEquals(tag, Address.pack(expect.getRecipients(RecipientType.CC)), actual.mCc);
406         assertEquals(tag, ((MimeMessage)expect).getMessageId(), actual.mMessageId);
407         assertEquals(tag, expect.isSet(Flag.SEEN), actual.mFlagRead);
408         assertEquals(tag, expect.isSet(Flag.FLAGGED), actual.mFlagFavorite);
409     }
410 
411     /**
412      * Check equality of a pair of converted messages
413      */
checkLegacyMessage(String tag, EmailContent.Message expect, Message actual)414     private void checkLegacyMessage(String tag, EmailContent.Message expect, Message actual)
415             throws MessagingException {
416         assertEquals(tag, expect.mServerId, actual.getUid());
417         assertEquals(tag, expect.mServerTimeStamp, actual.getInternalDate().getTime());
418         assertEquals(tag, expect.mSubject, actual.getSubject());
419         assertEquals(tag, expect.mFrom, Address.pack(actual.getFrom()));
420         assertEquals(tag, expect.mTimeStamp, actual.getSentDate().getTime());
421         assertEquals(tag, expect.mTo, Address.pack(actual.getRecipients(RecipientType.TO)));
422         assertEquals(tag, expect.mCc, Address.pack(actual.getRecipients(RecipientType.CC)));
423         assertEquals(tag, expect.mBcc, Address.pack(actual.getRecipients(RecipientType.BCC)));
424         assertEquals(tag, expect.mReplyTo, Address.pack(actual.getReplyTo()));
425         assertEquals(tag, expect.mMessageId, ((MimeMessage)actual).getMessageId());
426         // check flags
427         assertEquals(tag, expect.mFlagRead, actual.isSet(Flag.SEEN));
428         assertEquals(tag, expect.mFlagFavorite, actual.isSet(Flag.FLAGGED));
429 
430         // Check the body of the message
431         ArrayList<Part> viewables = new ArrayList<Part>();
432         ArrayList<Part> attachments = new ArrayList<Part>();
433         MimeUtility.collectParts(actual, viewables, attachments);
434         String get1Text = null;
435         String get1Html = null;
436         String get1TextReply = null;
437         String get1HtmlReply = null;
438         String get1TextIntro = null;
439         for (Part viewable : viewables) {
440             String text = MimeUtility.getTextFromPart(viewable);
441             boolean isHtml = viewable.getMimeType().equalsIgnoreCase("text/html");
442             String[] headers = viewable.getHeader(MimeHeader.HEADER_ANDROID_BODY_QUOTED_PART);
443             if (headers != null) {
444                 String header = headers[0];
445                 boolean isReply = LegacyConversions.BODY_QUOTED_PART_REPLY.equalsIgnoreCase(header);
446                 boolean isFwd = LegacyConversions.BODY_QUOTED_PART_FORWARD.equalsIgnoreCase(header);
447                 boolean isIntro = LegacyConversions.BODY_QUOTED_PART_INTRO.equalsIgnoreCase(header);
448                 if (isReply || isFwd) {
449                     if (isHtml) {
450                         get1HtmlReply = text;
451                     } else {
452                         get1TextReply = text;
453                     }
454                 } else if (isIntro) {
455                     get1TextIntro = text;
456                 }
457                 // Check flags
458                 int replyTypeFlags = expect.mFlags & EmailContent.Message.FLAG_TYPE_MASK;
459                 if (isReply) {
460                     assertEquals(tag, EmailContent.Message.FLAG_TYPE_REPLY, replyTypeFlags);
461                 }
462                 if (isFwd) {
463                     assertEquals(tag, EmailContent.Message.FLAG_TYPE_FORWARD, replyTypeFlags);
464                 }
465             } else {
466                 if (isHtml) {
467                     get1Html = text;
468                 } else {
469                     get1Text = text;
470                 }
471             }
472         }
473         assertEquals(tag, expect.mText, get1Text);
474         assertEquals(tag, expect.mHtml, get1Html);
475         assertEquals(tag, expect.mTextReply, get1TextReply);
476         assertEquals(tag, expect.mHtmlReply, get1HtmlReply);
477         assertEquals(tag, expect.mIntroText, get1TextIntro);
478 
479         // TODO Check the attachments
480 
481 //      cv.put("attachment_count", attachments.size());
482     }
483 }
484