[Android Software Development] shafty023:
PURPOSE

So the purpose of this thread is to explain how I got HTML working for Exchange 2003 servers which technically is not supposed to be possible :) This is me giving back to the community and I give full writes to use this code in whatever means necessary. I had to figure this out on my own with absolutely no help online so now I'm putting this here so no one else has to go through the hell that I did!

This means Hotmail via Activesync will also have full HTML support with this code patch I'm submitting.

First, AOSP Email is set up to request plain text emails from an Exchange 2003 server which is no good for us. First step is we need to request MIME messages which will contain the text/plain & text/html versions of an e-mail. So if a message was originally text/html then the MIME message will contain text/plain & text/html. If the original message was text/plain then the MIME message will only contain text/plain. No biggie there that's obviously ok.

EasSyncService.java

// Set the truncation amount for all classes
if (mProtocolVersionDouble >= Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE) {
s.start(Tags.BASE_BODY_PREFERENCE)
// HTML for email; plain text for everything else
.data(Tags.BASE_TYPE, (className.equals("Email") ?
Eas.BODY_PREFERENCE_HTML : Eas.BODY_PREFERENCE_TEXT))
.data(Tags.BASE_TRUNCATION_SIZE,
Eas.EAS12_TRUNCATION_SIZE)
.end();
} else {
if (className.equals("Email")) {
s.data(Tags.SYNC_MIME_SUPPORT, "2")
.data(Tags.SYNC_MIME_TRUNCATION, "7");
}
s.data(Tags.SYNC_TRUNCATION, Eas.EAS2_5_TRUNCATION_SIZE);
}

Explanation

The section in the if() block is for exchange 2007/2010 and basically is telling the Exchange server to send HTML for Email and plain text for anything else (ie: calendar events).

This says send me MIME messages for Exchange 2003

s.data(Tags.SYNC_MIME_SUPPORT, "2")

This says truncate the MIME message to 102,400 characters and is very important. If you don't set this then you WILL run into out of memory issues because some messages can be very large and since a MIME message contains duplicate copies of a message in two formats you're doubling the memory requirements.

.data(Tags.SYNC_MIME_TRUNCATION, "7");

Now let's get on to the part that will actually parse the MIME encoded message.

EmailSyncAdapter.java

public void addData (Message msg) throws IOException {
ArrayList<Attachment> atts = new ArrayList<Attachment>();
mimeAtts = new ArrayList<String>();
textBody = new StringBuffer();
htmlBody = new StringBuffer();

The new additions are mimeAtts, textBody, and htmlBody. We'll use these later in the code.

case Tags.EMAIL_MIME_DATA:
try {
MimeMessage mimeMsg = new MimeMessage(new ByteArrayInputStream(getValue().getBytes()));

if (mimeMsg.getBody() instanceof Multipart) {
MimeMultipart multipart = (MimeMultipart) mimeMsg.getBody();
parseMimeBody(multipart);

if (htmlBody != null && htmlBody.length() != 0)
msg.mHtml = htmlBody.toString();
else if (textBody != null)
msg.mText = textBody.toString();
else
msg.mText = "";
}
else {
InputStream in = mimeMsg.getBody().getInputStream();
ByteArrayOutputStream out = new ByteArrayOutputStream();
IOUtils.copy(in, out);
in.close();
in = null;
String charset = MimeUtility.getHeaderParameter(mimeMsg.getContentType(), "charset");
if (charset == null)
charset = "UTF-8";
String mimeTxt = out.toString(charset);
charset = null;
out = null;
if (mimeMsg.isMimeType("text/html"))
msg.mHtml = mimeTxt;
else
msg.mText = mimeTxt;
}
} catch (MessagingException e) {}
break;

This is a new case block that I added specifically for parsing MIME encoded messages. In a nutshell I'm creating the MimeMessage object, checking if it is a multi-part message or single-part message and parsing accordingly.

if (atts.size() > 0 && mimeAtts != null) {
for(Attachment att : atts) {
for (String contentId : mimeAtts) {
if (contentId == null)
continue;

if (att.mFileName != null && contentId.contains(att.mFileName))
att.mContentId = contentId;
}
}
msg.mAttachments = atts;
}

This is really important and is still in the same method as the above two code sections. The issue is when an attachment is downloaded the content-id isn't specified. So now that we have the MIME message we can actually grab that content-id out of there and update each attachment with the correct content-id. Later in MessageView.java it will use that value to display the embedded images correctly.

private void parseMimeBody(MimeMultipart multipart) {
try {
for (int i=0; i<multipart.getCount(); ++i) {
BodyPart part = multipart.getBodyPart(i);

if (part.isMimeType("text/plain"))
textBody.append(MimeUtility.getTextFromPart(part));
else if (part.isMimeType("text/html"))
htmlBody.append(MimeUtility.getTextFromPart(part));
else if (part.isMimeType("multipart/alternative"))
parseMimeBody((MimeMultipart)part.getBody());
else
mimeAtts.add(part.getContentId());
}
}
catch (MessagingException e) {}
}

This is a new method I added which handles parsing out the message bodies from a multi-part MIME message. It uses recursion if there are embedded multi-part messages in the original multi-part message.

The last part which I won't paste here is incorporating the support for viewing MIME messages into MessageView.java. I've taken the 3 files that were modified and I'm attaching them to this e-mail. These are straight from my paid email app so there are other additions in there that someone can diff out. If I get time I'll patch these changes into AOSP email but I'm swamped 24/7 with Enhanced Email so I'd suggest someone else do it and contact me with questions.

This is the same hack that iPhone uses to enable HTML for ex2003 and once again is undocumented ANYWHERE on the net except for here by me :)