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:
parent
c98a613153
commit
3aaba9eb87
|
@ -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(
|
||||
|
|
|
@ -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 + "]";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue