First part of Exchange meeting request/reply implementation

* Added two columns to Attachment in EmailProvider
  content: content that is written directly as an attachment
  suppressDisposition: to suppress the content-disposition header
  All meeting invitation emails use these two columns; the first
  for ics attachment data (which is quite small, rarely over 1k),
  and the second to indicate NOT sending the content-disposition
  header; without this, Exchange will consider the ics as an
  attachment rather than an iMIP style message (rfc2447)
* Modified tests to include these columns; added upgrade code for
  new database version
* New columns and code are designed to be usable outside Exchange,
  although there are no other clients of the code at this point.
* Modified Rfc822Output to use the content field, if present, in
  lieu of retrieving attachment data via URL; added support for
  suppressing the Content-Disposition header
This commit is contained in:
Andrew Stadler 2010-02-22 12:57:33 -08:00
parent c98a613153
commit 3aaba9eb87
5 changed files with 82 additions and 21 deletions

View File

@ -33,6 +33,7 @@ import android.util.base64.Base64;
import android.util.base64.Base64OutputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
@ -210,19 +211,28 @@ public class Rfc822Output {
writeHeader(writer, "Content-Type",
attachment.mMimeType + ";\n name=\"" + attachment.mFileName + "\"");
writeHeader(writer, "Content-Transfer-Encoding", "base64");
writeHeader(writer, "Content-Disposition",
"attachment;"
+ "\n filename=\"" + attachment.mFileName + "\";"
+ "\n size=" + Long.toString(attachment.mSize));
// Most attachments (real files) will send Content-Disposition. The suppression option
// is used when sending calendar invites.
if ((attachment.mFlags & Attachment.FLAG_SUPPRESS_DISPOSITION) == 0) {
writeHeader(writer, "Content-Disposition",
"attachment;"
+ "\n filename=\"" + attachment.mFileName + "\";"
+ "\n size=" + Long.toString(attachment.mSize));
}
writeHeader(writer, "Content-ID", attachment.mContentId);
writer.append("\r\n");
// Set up input stream and write it out via base64
InputStream inStream = null;
try {
// try to open the file
Uri fileUri = Uri.parse(attachment.mContentUri);
inStream = context.getContentResolver().openInputStream(fileUri);
// Use content, if provided; otherwise, use the contentUri
if (attachment.mContent != null) {
inStream = new ByteArrayInputStream(attachment.mContent.getBytes());
} else {
// try to open the file
Uri fileUri = Uri.parse(attachment.mContentUri);
inStream = context.getContentResolver().openInputStream(fileUri);
}
// switch to output stream for base64 text output
writer.flush();
Base64OutputStream base64Out = new Base64OutputStream(

View File

@ -573,12 +573,23 @@ public abstract class EmailContent {
public static final int FLAG_TYPE_REPLY = 1<<0;
public static final int FLAG_TYPE_FORWARD = 1<<1;
public static final int FLAG_TYPE_MASK = FLAG_TYPE_REPLY | FLAG_TYPE_FORWARD;
// The following flags indicate messages that are determined to be meeting related
// (e.g. invites)
public static final int FLAG_MEETING_INVITE = 1<<2;
public static final int FLAG_MEETING_CANCEL_NOTICE = 1<<3;
public static final int FLAG_MEETING_MASK =
FLAG_MEETING_INVITE | FLAG_MEETING_CANCEL_NOTICE;
// The following flags indicate messages that are determined to be incoming meeting related
// (e.g. invites from others)
public static final int FLAG_INCOMING_MEETING_INVITE = 1<<2;
public static final int FLAG_INCOMING_MEETING_CANCEL = 1<<3;
public static final int FLAG_INCOMING_MEETING_MASK =
FLAG_INCOMING_MEETING_INVITE | FLAG_INCOMING_MEETING_CANCEL;
// The following flags indicate messages that are outgoing and meeting related
// (e.g. invites TO others)
public static final int FLAG_OUTGOING_MEETING_INVITE = 1<<4;
public static final int FLAG_OUTGOING_MEETING_CANCEL = 1<<5;
public static final int FLAG_OUTGOING_MEETING_ACCEPT = 1<<6;
public static final int FLAG_OUTGOING_MEETING_DECLINE = 1<<7;
public static final int FLAG_OUTGOING_MEETING_TENTATIVE = 1<<8;
public static final int FLAG_OUTGOING_MEETING_MASK =
FLAG_OUTGOING_MEETING_INVITE | FLAG_OUTGOING_MEETING_CANCEL |
FLAG_OUTGOING_MEETING_ACCEPT | FLAG_OUTGOING_MEETING_DECLINE |
FLAG_OUTGOING_MEETING_TENTATIVE;
public Message() {
mBaseUri = CONTENT_URI;
@ -1543,6 +1554,10 @@ public abstract class EmailContent {
public static final String LOCATION = "location";
// The transfer encoding of the attachment
public static final String ENCODING = "encoding";
// Content that is actually contained in the Attachment row
public static final String CONTENT = "content";
// Flags
public static final String FLAGS = "flags";
}
public static final class Attachment extends EmailContent implements AttachmentColumns {
@ -1560,6 +1575,8 @@ public abstract class EmailContent {
public long mMessageKey;
public String mLocation;
public String mEncoding;
public String mContent;
public int mFlags;
public static final int CONTENT_ID_COLUMN = 0;
public static final int CONTENT_FILENAME_COLUMN = 1;
@ -1570,12 +1587,19 @@ public abstract class EmailContent {
public static final int CONTENT_MESSAGE_ID_COLUMN = 6;
public static final int CONTENT_LOCATION_COLUMN = 7;
public static final int CONTENT_ENCODING_COLUMN = 8;
public static final int CONTENT_CONTENT_COLUMN = 9;
public static final int CONTENT_FLAGS_COLUMN = 10;
public static final String[] CONTENT_PROJECTION = new String[] {
RECORD_ID, AttachmentColumns.FILENAME, AttachmentColumns.MIME_TYPE,
AttachmentColumns.SIZE, AttachmentColumns.CONTENT_ID, AttachmentColumns.CONTENT_URI,
AttachmentColumns.MESSAGE_KEY, AttachmentColumns.LOCATION, AttachmentColumns.ENCODING
AttachmentColumns.MESSAGE_KEY, AttachmentColumns.LOCATION, AttachmentColumns.ENCODING,
AttachmentColumns.CONTENT, AttachmentColumns.FLAGS
};
// Bits used in mFlags
// Instruct RFC822 output code to supress "content-disposition". Used for calendar invites.
public static final int FLAG_SUPPRESS_DISPOSITION = 1<<0;
/**
* no public constructor since this is a utility class
*/
@ -1672,6 +1696,8 @@ public abstract class EmailContent {
mMessageKey = cursor.getLong(CONTENT_MESSAGE_ID_COLUMN);
mLocation = cursor.getString(CONTENT_LOCATION_COLUMN);
mEncoding = cursor.getString(CONTENT_ENCODING_COLUMN);
mContent = cursor.getString(CONTENT_CONTENT_COLUMN);
mFlags = cursor.getInt(CONTENT_FLAGS_COLUMN);
return this;
}
@ -1686,6 +1712,8 @@ public abstract class EmailContent {
values.put(AttachmentColumns.MESSAGE_KEY, mMessageKey);
values.put(AttachmentColumns.LOCATION, mLocation);
values.put(AttachmentColumns.ENCODING, mEncoding);
values.put(AttachmentColumns.CONTENT, mContent);
values.put(AttachmentColumns.FLAGS, mFlags);
return values;
}
@ -1704,6 +1732,8 @@ public abstract class EmailContent {
dest.writeLong(mMessageKey);
dest.writeString(mLocation);
dest.writeString(mEncoding);
dest.writeString(mContent);
dest.writeInt(mFlags);
}
public Attachment(Parcel in) {
@ -1717,6 +1747,8 @@ public abstract class EmailContent {
mMessageKey = in.readLong();
mLocation = in.readString();
mEncoding = in.readString();
mContent = in.readString();
mFlags = in.readInt();
}
public static final Parcelable.Creator<EmailContent.Attachment> CREATOR
@ -1733,7 +1765,8 @@ public abstract class EmailContent {
@Override
public String toString() {
return "[" + mFileName + ", " + mMimeType + ", " + mSize + ", " + mContentId + ", "
+ mContentUri + ", " + mMessageKey + ", " + mLocation + ", " + mEncoding + "]";
+ mContentUri + ", " + mMessageKey + ", " + mLocation + ", " + mEncoding + ", "
+ mContent + ", " + mFlags + "]";
}
}

View File

@ -69,8 +69,6 @@ public class EmailProvider extends ContentProvider {
private static final String WHERE_ID = EmailContent.RECORD_ID + "=?";
private static final String[] COUNT_COLUMNS = new String[]{"count(*)"};
// Any changes to the database format *must* include update-in-place code.
// Original version: 3
// Version 4: Database wipe required; changing AccountManager interface w/Exchange
@ -81,7 +79,8 @@ public class EmailProvider extends ContentProvider {
// Version 8: Add security flags column to accounts table
// Version 9: Add security sync key and signature to accounts table
// Version 10: Add meeting info to message table
public static final int DATABASE_VERSION = 10;
// Version 11: Add content and flags to attachment table
public static final int DATABASE_VERSION = 11;
// Any changes to the database format *must* include update-in-place code.
// Original version: 2
@ -505,7 +504,9 @@ public class EmailProvider extends ContentProvider {
+ AttachmentColumns.CONTENT_URI + " text, "
+ AttachmentColumns.MESSAGE_KEY + " integer, "
+ AttachmentColumns.LOCATION + " text, "
+ AttachmentColumns.ENCODING + " text"
+ AttachmentColumns.ENCODING + " text, "
+ AttachmentColumns.CONTENT + " text, "
+ AttachmentColumns.FLAGS + " integer"
+ ");";
db.execSQL("create table " + Attachment.TABLE_NAME + s);
db.execSQL(createIndex(Attachment.TABLE_NAME, AttachmentColumns.MESSAGE_KEY));
@ -755,6 +756,19 @@ public class EmailProvider extends ContentProvider {
}
oldVersion = 10;
}
if (oldVersion == 10) {
// Attachment: add content and flags columns
try {
db.execSQL("alter table " + Attachment.TABLE_NAME
+ " add column " + AttachmentColumns.CONTENT + " text" + ";");
db.execSQL("alter table " + Attachment.TABLE_NAME
+ " add column " + AttachmentColumns.FLAGS + " integer" + ";");
} catch (SQLException e) {
// Shouldn't be needed unless we're debugging and interrupt the process
Log.w(TAG, "Exception upgrading EmailProvider.db from 10 to 11 " + e);
}
oldVersion = 11;
}
}
@Override

View File

@ -161,9 +161,9 @@ public class EmailSyncAdapter extends AbstractSyncAdapter {
case Tags.EMAIL_MESSAGE_CLASS:
String messageClass = getValue();
if (messageClass.equals("IPM.Schedule.Meeting.Request")) {
msg.mFlags |= Message.FLAG_MEETING_INVITE;
msg.mFlags |= Message.FLAG_INCOMING_MEETING_INVITE;
} else if (messageClass.equals("IPM.Schedule.Meeting.Canceled")) {
msg.mFlags |= Message.FLAG_MEETING_CANCEL_NOTICE;
msg.mFlags |= Message.FLAG_INCOMING_MEETING_CANCEL;
}
break;
case Tags.EMAIL_MEETING_REQUEST:

View File

@ -190,6 +190,8 @@ public class ProviderTestUtils extends Assert {
att.mMimeType = "mimeType " + fileName;
att.mLocation = "location " + fileName;
att.mEncoding = "encoding " + fileName;
att.mContent = "content " + fileName;
att.mFlags = 0;
if (saveIt) {
att.save(context);
}
@ -352,5 +354,7 @@ public class ProviderTestUtils extends Assert {
assertEquals(caller + " mMimeType", expect.mMimeType, actual.mMimeType);
assertEquals(caller + " mLocation", expect.mLocation, actual.mLocation);
assertEquals(caller + " mEncoding", expect.mEncoding, actual.mEncoding);
assertEquals(caller + " mContent", expect.mContent, actual.mContent);
assertEquals(caller + " mFlags", expect.mFlags, actual.mFlags);
}
}