• 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 android.content.ContentUris;
20 import android.content.Context;
21 import android.database.Cursor;
22 import android.net.Uri;
23 import android.test.ProviderTestCase2;
24 import android.test.suitebuilder.annotation.Suppress;
25 
26 import com.android.email.provider.EmailProvider;
27 import com.android.email.provider.ProviderTestUtils;
28 import com.android.emailcommon.internet.MimeHeader;
29 import com.android.emailcommon.internet.MimeUtility;
30 import com.android.emailcommon.mail.Address;
31 import com.android.emailcommon.mail.BodyPart;
32 import com.android.emailcommon.mail.Flag;
33 import com.android.emailcommon.mail.Message;
34 import com.android.emailcommon.mail.Message.RecipientType;
35 import com.android.emailcommon.mail.MessageTestUtils;
36 import com.android.emailcommon.mail.MessageTestUtils.MessageBuilder;
37 import com.android.emailcommon.mail.MessageTestUtils.MultipartBuilder;
38 import com.android.emailcommon.mail.MessagingException;
39 import com.android.emailcommon.mail.Part;
40 import com.android.emailcommon.provider.EmailContent;
41 import com.android.emailcommon.provider.EmailContent.Attachment;
42 
43 import java.io.IOException;
44 import java.util.ArrayList;
45 
46 /**
47  * Tests of the Legacy Conversions code (used by MessagingController).
48  *
49  * NOTE:  It would probably make sense to rewrite this using a MockProvider, instead of the
50  * ProviderTestCase (which is a real provider running on a temp database).  This would be more of
51  * a true "unit test".
52  *
53  * You can run this entire test case with:
54  *   runtest -c com.android.email.LegacyConversionsTests email
55  */
56 @Suppress
57 public class LegacyConversionsTests extends ProviderTestCase2<EmailProvider> {
58 
59     Context mProviderContext;
60     Context mContext;
61 
LegacyConversionsTests()62     public LegacyConversionsTests() {
63         super(EmailProvider.class, EmailContent.AUTHORITY);
64     }
65 
66     @Override
setUp()67     public void setUp() throws Exception {
68         super.setUp();
69         mProviderContext = getMockContext();
70         mContext = getContext();
71     }
72 
73     /**
74      * TODO: basic Legacy -> Provider Message conversions
75      * TODO: basic Legacy -> Provider Body conversions
76      * TODO: rainy day tests of all kinds
77      */
78 
79     /**
80      * Sunny day test of adding attachments from an IMAP/POP message.
81      */
brokentestAddAttachments()82     public void brokentestAddAttachments() throws MessagingException, IOException {
83         // Prepare a local message to add the attachments to
84         final long accountId = 1;
85         final long mailboxId = 1;
86 
87         // test 1: legacy message using content-type:name style for name
88         final EmailContent.Message localMessage = ProviderTestUtils.setupMessage(
89                 "local-message", accountId, mailboxId, false, true, mProviderContext);
90         final Message legacyMessage = prepareLegacyMessageWithAttachments(2, false);
91         convertAndCheckcheckAddedAttachments(localMessage, legacyMessage);
92 
93         // test 2: legacy message using content-disposition:filename style for name
94         final EmailContent.Message localMessage2 = ProviderTestUtils.setupMessage(
95                 "local-message", accountId, mailboxId, false, true, mProviderContext);
96         final Message legacyMessage2 = prepareLegacyMessageWithAttachments(2, true);
97         convertAndCheckcheckAddedAttachments(localMessage2, legacyMessage2);
98     }
99 
100     /**
101      * Helper for testAddAttachments
102      */
convertAndCheckcheckAddedAttachments(final EmailContent.Message localMessage, final Message legacyMessage)103     private void convertAndCheckcheckAddedAttachments(final EmailContent.Message localMessage,
104             final Message legacyMessage) throws MessagingException, IOException {
105         // Now, convert from legacy to provider and see what happens
106         ArrayList<Part> viewables = new ArrayList<Part>();
107         ArrayList<Part> attachments = new ArrayList<Part>();
108         MimeUtility.collectParts(legacyMessage, viewables, attachments);
109         LegacyConversions.updateAttachments(mProviderContext, localMessage, attachments);
110 
111         // Read back all attachments for message and check field values
112         Uri uri = ContentUris.withAppendedId(Attachment.MESSAGE_ID_URI, localMessage.mId);
113         Cursor c = mProviderContext.getContentResolver().query(uri, Attachment.CONTENT_PROJECTION,
114                 null, null, null);
115         try {
116             assertEquals(2, c.getCount());
117             while (c.moveToNext()) {
118                 Attachment attachment =
119                         Attachment.getContent(mProviderContext, c, Attachment.class);
120                 if ("100".equals(attachment.mLocation)) {
121                     checkAttachment("attachment1Part", attachments.get(0), attachment,
122                             localMessage.mAccountKey);
123                 } else if ("101".equals(attachment.mLocation)) {
124                     checkAttachment("attachment2Part", attachments.get(1), attachment,
125                             localMessage.mAccountKey);
126                 } else {
127                     fail("Unexpected attachment with location " + attachment.mLocation);
128                 }
129             }
130         } finally {
131             c.close();
132         }
133     }
134 
135     /**
136      * Test that only "attachment" or "inline" attachments are captured and added.
137      * @throws MessagingException
138      * @throws IOException
139      */
brokentestAttachmentDispositions()140     public void brokentestAttachmentDispositions() throws MessagingException, IOException {
141         // Prepare a local message to add the attachments to
142         final long accountId = 1;
143         final long mailboxId = 1;
144 
145         // Prepare the three attachments we want to test
146         BodyPart[] sourceAttachments = new BodyPart[3];
147         BodyPart attachmentPart;
148 
149         // 1. Standard attachment
150         attachmentPart = MessageTestUtils.bodyPart("image/jpg", null);
151         attachmentPart.setHeader(MimeHeader.HEADER_CONTENT_TYPE, "image/jpg");
152         attachmentPart.setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, "base64");
153         attachmentPart.setHeader(MimeHeader.HEADER_CONTENT_DISPOSITION,
154                 "attachment;\n filename=\"file-1\";\n size=100");
155         attachmentPart.setHeader(MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA, "100");
156         sourceAttachments[0] = attachmentPart;
157 
158         // 2. Inline attachment
159         attachmentPart = MessageTestUtils.bodyPart("image/gif", null);
160         attachmentPart.setHeader(MimeHeader.HEADER_CONTENT_TYPE, "image/gif");
161         attachmentPart.setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, "base64");
162         attachmentPart.setHeader(MimeHeader.HEADER_CONTENT_DISPOSITION,
163                 "inline;\n filename=\"file-2\";\n size=200");
164         attachmentPart.setHeader(MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA, "101");
165         sourceAttachments[1] = attachmentPart;
166 
167         // 3. Neither (use VCALENDAR)
168         attachmentPart = MessageTestUtils.bodyPart("text/calendar", null);
169         attachmentPart.setHeader(MimeHeader.HEADER_CONTENT_TYPE,
170                 "text/calendar; charset=UTF-8; method=REQUEST");
171         attachmentPart.setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, "7bit");
172         attachmentPart.setHeader(MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA, "102");
173         sourceAttachments[2] = attachmentPart;
174 
175         // Prepare local message (destination) and legacy message w/attachments (source)
176         final EmailContent.Message localMessage = ProviderTestUtils.setupMessage(
177                 "local-message", accountId, mailboxId, false, true, mProviderContext);
178         final Message legacyMessage = prepareLegacyMessageWithAttachments(sourceAttachments);
179         convertAndCheckcheckAddedAttachments(localMessage, legacyMessage);
180 
181         // Run the conversion and check for the converted attachments - this test asserts
182         // that there are two attachments numbered 100 & 101 (so will fail if it finds 102)
183         convertAndCheckcheckAddedAttachments(localMessage, legacyMessage);
184     }
185 
186     /**
187      * Test that attachments aren't re-added in the DB.  This supports the "partial download"
188      * nature of POP messages.
189      */
brokentestAddDuplicateAttachments()190     public void brokentestAddDuplicateAttachments() throws MessagingException, IOException {
191         // Prepare a local message to add the attachments to
192         final long accountId = 1;
193         final long mailboxId = 1;
194         final EmailContent.Message localMessage = ProviderTestUtils.setupMessage(
195                 "local-message", accountId, mailboxId, false, true, mProviderContext);
196 
197         // Prepare a legacy message with attachments
198         Message legacyMessage = prepareLegacyMessageWithAttachments(2, false);
199 
200         // Now, convert from legacy to provider and see what happens
201         ArrayList<Part> viewables = new ArrayList<Part>();
202         ArrayList<Part> attachments = new ArrayList<Part>();
203         MimeUtility.collectParts(legacyMessage, viewables, attachments);
204         LegacyConversions.updateAttachments(mProviderContext, localMessage, attachments);
205 
206         // Confirm two attachment objects created
207         Uri uri = ContentUris.withAppendedId(Attachment.MESSAGE_ID_URI, localMessage.mId);
208         assertEquals(2, EmailContent.count(mProviderContext, uri, null, null));
209 
210         // Now add the attachments again and confirm there are still only two
211         LegacyConversions.updateAttachments(mProviderContext, localMessage, attachments);
212         assertEquals(2, EmailContent.count(mProviderContext, uri, null, null));
213 
214         // Now add a 3rd & 4th attachment and make sure the total is 4, not 2 or 6
215         legacyMessage = prepareLegacyMessageWithAttachments(4, false);
216         viewables = new ArrayList<Part>();
217         attachments = new ArrayList<Part>();
218         MimeUtility.collectParts(legacyMessage, viewables, attachments);
219         LegacyConversions.updateAttachments(mProviderContext, localMessage, attachments);
220         assertEquals(4, EmailContent.count(mProviderContext, uri, null, null));
221     }
222 
223     /**
224      * Prepare a legacy message with 1+ attachments
225      * @param numAttachments how many attachments to add
226      * @param filenameInDisposition False: attachment names are sent as content-type:name.  True:
227      *          attachment names are sent as content-disposition:filename.
228      */
prepareLegacyMessageWithAttachments(int numAttachments, boolean filenameInDisposition)229     private Message prepareLegacyMessageWithAttachments(int numAttachments,
230             boolean filenameInDisposition) throws MessagingException {
231         BodyPart[] attachmentParts = new BodyPart[numAttachments];
232         for (int i = 0; i < numAttachments; ++i) {
233             // construct parameter parts for content-type:name or content-disposition:filename.
234             String name = "";
235             String filename = "";
236             String quotedName = "\"test-attachment-" + i + "\"";
237             if (filenameInDisposition) {
238                 filename = ";\n filename=" + quotedName;
239             } else {
240                 name = ";\n name=" + quotedName;
241             }
242 
243             // generate an attachment that came from a server
244             BodyPart attachmentPart = MessageTestUtils.bodyPart("image/jpg", null);
245 
246             // name=attachmentN size=N00 location=10N
247             attachmentPart.setHeader(MimeHeader.HEADER_CONTENT_TYPE, "image/jpg" + name);
248             attachmentPart.setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, "base64");
249             attachmentPart.setHeader(MimeHeader.HEADER_CONTENT_DISPOSITION,
250                     "attachment" + filename +  ";\n size=" + (i+1) + "00");
251             attachmentPart.setHeader(MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA, "10" + i);
252 
253             attachmentParts[i] = attachmentPart;
254         }
255 
256         return prepareLegacyMessageWithAttachments(attachmentParts);
257     }
258 
259     /**
260      * Prepare a legacy message with 1+ attachments
261      * @param attachments array containing one or more attachments
262      */
prepareLegacyMessageWithAttachments(BodyPart[] attachments)263     private Message prepareLegacyMessageWithAttachments(BodyPart[] attachments)
264             throws MessagingException {
265         // Build the multipart that holds the attachments
266         MultipartBuilder mpBuilder = new MultipartBuilder("multipart/mixed");
267         for (int i = 0; i < attachments.length; ++i) {
268             mpBuilder.addBodyPart(attachments[i]);
269         }
270 
271         // Now build a message with them
272         final Message legacyMessage = new MessageBuilder()
273             .setBody(new MultipartBuilder("multipart/mixed")
274                      .addBodyPart(MessageTestUtils.bodyPart("text/html", null))
275                      .addBodyPart(mpBuilder.buildBodyPart())
276                      .build())
277                 .build();
278 
279         return legacyMessage;
280     }
281 
282     /**
283      * Compare attachment that was converted from Part (expected) to Provider Attachment (actual)
284      *
285      * TODO content URI should only be set if we also saved a file
286      * TODO other data encodings
287      */
checkAttachment(String tag, Part expected, EmailContent.Attachment actual, long accountKey)288     private void checkAttachment(String tag, Part expected, EmailContent.Attachment actual,
289             long accountKey) throws MessagingException {
290         String contentType = MimeUtility.unfoldAndDecode(expected.getContentType());
291         String contentTypeName = MimeUtility.getHeaderParameter(contentType, "name");
292         assertEquals(tag, expected.getMimeType(), actual.mMimeType);
293         String disposition = expected.getDisposition();
294         String sizeString = MimeUtility.getHeaderParameter(disposition, "size");
295         String dispositionFilename = MimeUtility.getHeaderParameter(disposition, "filename");
296         long expectedSize = (sizeString != null) ? Long.parseLong(sizeString) : 0;
297         assertEquals(tag, expectedSize, actual.mSize);
298         assertEquals(tag, expected.getContentId(), actual.mContentId);
299 
300         // filename is either content-type:name or content-disposition:filename
301         String expectedName = (contentTypeName != null) ? contentTypeName : dispositionFilename;
302         assertEquals(tag, expectedName, actual.mFileName);
303 
304         // content URI should be null
305         assertNull(tag, actual.getContentUri());
306 
307         assertTrue(tag, 0 != actual.mMessageKey);
308 
309         // location is either both null or both matching
310         String expectedPartId = null;
311         String[] storeData = expected.getHeader(MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA);
312         if (storeData != null && storeData.length > 0) {
313             expectedPartId = storeData[0];
314         }
315         assertEquals(tag, expectedPartId, actual.mLocation);
316         assertEquals(tag, "B", actual.mEncoding);
317         assertEquals(tag, accountKey, actual.mAccountKey);
318     }
319 
320     /**
321      * TODO: Sunny day test of adding attachments from a POP message.
322      */
323 
324     /**
325      * Sunny day tests of converting an original message to a legacy message
326      */
brokentestMakeLegacyMessage()327     public void brokentestMakeLegacyMessage() throws MessagingException {
328         // Set up and store a message in the provider
329         long account1Id = 1;
330         long mailbox1Id = 1;
331 
332         // Test message 1: No body
333         EmailContent.Message localMessage1 = ProviderTestUtils.setupMessage("make-legacy",
334                 account1Id, mailbox1Id, false, true, mProviderContext);
335         Message getMessage1 = LegacyConversions.makeMessage(mProviderContext, localMessage1);
336         checkLegacyMessage("no body", localMessage1, getMessage1);
337 
338         // Test message 2: Simple body
339         EmailContent.Message localMessage2 = ProviderTestUtils.setupMessage("make-legacy",
340                 account1Id, mailbox1Id, true, false, mProviderContext);
341         localMessage2.mFlags &= ~EmailContent.Message.FLAG_TYPE_MASK;
342         localMessage2.save(mProviderContext);
343         Message getMessage2 = LegacyConversions.makeMessage(mProviderContext, localMessage2);
344         checkLegacyMessage("simple body", localMessage2, getMessage2);
345 
346         // Test message 3: Body + replied-to text
347         EmailContent.Message localMessage3 = ProviderTestUtils.setupMessage("make-legacy",
348                 account1Id, mailbox1Id, true, false, mProviderContext);
349         localMessage3.mFlags &= ~EmailContent.Message.FLAG_TYPE_MASK;
350         localMessage3.mFlags |= EmailContent.Message.FLAG_TYPE_REPLY;
351         localMessage3.save(mProviderContext);
352         Message getMessage3 = LegacyConversions.makeMessage(mProviderContext, localMessage3);
353         checkLegacyMessage("reply-to", localMessage3, getMessage3);
354 
355         // Test message 4: Body + forwarded text
356         EmailContent.Message localMessage4 = ProviderTestUtils.setupMessage("make-legacy",
357                 account1Id, mailbox1Id, true, false, mProviderContext);
358         localMessage4.mFlags &= ~EmailContent.Message.FLAG_TYPE_MASK;
359         localMessage4.mFlags |= EmailContent.Message.FLAG_TYPE_FORWARD;
360         localMessage4.save(mProviderContext);
361         Message getMessage4 = LegacyConversions.makeMessage(mProviderContext, localMessage4);
362         checkLegacyMessage("forwarding", localMessage4, getMessage4);
363     }
364 
365     /**
366      * Check equality of a pair of converted messages
367      */
checkLegacyMessage(String tag, EmailContent.Message expect, Message actual)368     private void checkLegacyMessage(String tag, EmailContent.Message expect, Message actual)
369             throws MessagingException {
370         assertEquals(tag, expect.mServerId, actual.getUid());
371         assertEquals(tag, expect.mServerTimeStamp, actual.getInternalDate().getTime());
372         assertEquals(tag, expect.mSubject, actual.getSubject());
373         assertEquals(tag, expect.mFrom, Address.toHeader(actual.getFrom()));
374         assertEquals(tag, expect.mTimeStamp, actual.getSentDate().getTime());
375         assertEquals(tag, expect.mTo, Address.toHeader(actual.getRecipients(RecipientType.TO)));
376         assertEquals(tag, expect.mCc, Address.toHeader(actual.getRecipients(RecipientType.CC)));
377         assertEquals(tag, expect.mBcc, Address.toHeader(actual.getRecipients(RecipientType.BCC)));
378         assertEquals(tag, expect.mReplyTo, Address.toHeader(actual.getReplyTo()));
379         assertEquals(tag, expect.mMessageId, actual.getMessageId());
380         // check flags
381         assertEquals(tag, expect.mFlagRead, actual.isSet(Flag.SEEN));
382         assertEquals(tag, expect.mFlagFavorite, actual.isSet(Flag.FLAGGED));
383 
384         // Check the body of the message
385         ArrayList<Part> viewables = new ArrayList<Part>();
386         ArrayList<Part> attachments = new ArrayList<Part>();
387         MimeUtility.collectParts(actual, viewables, attachments);
388         String get1Text = null;
389         String get1Html = null;
390         for (Part viewable : viewables) {
391             String text = MimeUtility.getTextFromPart(viewable);
392             if (viewable.getMimeType().equalsIgnoreCase("text/html")) {
393                 get1Html = text;
394             } else {
395                 get1Text = text;
396             }
397         }
398         assertEquals(tag, expect.mText, get1Text);
399         assertEquals(tag, expect.mHtml, get1Html);
400 
401         // TODO Check the attachments
402 
403 //      cv.put("attachment_count", attachments.size());
404     }
405 }
406