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 android.util.base64.Base64OutputStream;
|
||||||
|
|
||||||
import java.io.BufferedOutputStream;
|
import java.io.BufferedOutputStream;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
@ -210,19 +211,28 @@ public class Rfc822Output {
|
||||||
writeHeader(writer, "Content-Type",
|
writeHeader(writer, "Content-Type",
|
||||||
attachment.mMimeType + ";\n name=\"" + attachment.mFileName + "\"");
|
attachment.mMimeType + ";\n name=\"" + attachment.mFileName + "\"");
|
||||||
writeHeader(writer, "Content-Transfer-Encoding", "base64");
|
writeHeader(writer, "Content-Transfer-Encoding", "base64");
|
||||||
writeHeader(writer, "Content-Disposition",
|
// Most attachments (real files) will send Content-Disposition. The suppression option
|
||||||
"attachment;"
|
// is used when sending calendar invites.
|
||||||
+ "\n filename=\"" + attachment.mFileName + "\";"
|
if ((attachment.mFlags & Attachment.FLAG_SUPPRESS_DISPOSITION) == 0) {
|
||||||
+ "\n size=" + Long.toString(attachment.mSize));
|
writeHeader(writer, "Content-Disposition",
|
||||||
|
"attachment;"
|
||||||
|
+ "\n filename=\"" + attachment.mFileName + "\";"
|
||||||
|
+ "\n size=" + Long.toString(attachment.mSize));
|
||||||
|
}
|
||||||
writeHeader(writer, "Content-ID", attachment.mContentId);
|
writeHeader(writer, "Content-ID", attachment.mContentId);
|
||||||
writer.append("\r\n");
|
writer.append("\r\n");
|
||||||
|
|
||||||
// Set up input stream and write it out via base64
|
// Set up input stream and write it out via base64
|
||||||
InputStream inStream = null;
|
InputStream inStream = null;
|
||||||
try {
|
try {
|
||||||
// try to open the file
|
// Use content, if provided; otherwise, use the contentUri
|
||||||
Uri fileUri = Uri.parse(attachment.mContentUri);
|
if (attachment.mContent != null) {
|
||||||
inStream = context.getContentResolver().openInputStream(fileUri);
|
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
|
// switch to output stream for base64 text output
|
||||||
writer.flush();
|
writer.flush();
|
||||||
Base64OutputStream base64Out = new Base64OutputStream(
|
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_REPLY = 1<<0;
|
||||||
public static final int FLAG_TYPE_FORWARD = 1<<1;
|
public static final int FLAG_TYPE_FORWARD = 1<<1;
|
||||||
public static final int FLAG_TYPE_MASK = FLAG_TYPE_REPLY | FLAG_TYPE_FORWARD;
|
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
|
// The following flags indicate messages that are determined to be incoming meeting related
|
||||||
// (e.g. invites)
|
// (e.g. invites from others)
|
||||||
public static final int FLAG_MEETING_INVITE = 1<<2;
|
public static final int FLAG_INCOMING_MEETING_INVITE = 1<<2;
|
||||||
public static final int FLAG_MEETING_CANCEL_NOTICE = 1<<3;
|
public static final int FLAG_INCOMING_MEETING_CANCEL = 1<<3;
|
||||||
public static final int FLAG_MEETING_MASK =
|
public static final int FLAG_INCOMING_MEETING_MASK =
|
||||||
FLAG_MEETING_INVITE | FLAG_MEETING_CANCEL_NOTICE;
|
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() {
|
public Message() {
|
||||||
mBaseUri = CONTENT_URI;
|
mBaseUri = CONTENT_URI;
|
||||||
|
@ -1543,6 +1554,10 @@ public abstract class EmailContent {
|
||||||
public static final String LOCATION = "location";
|
public static final String LOCATION = "location";
|
||||||
// The transfer encoding of the attachment
|
// The transfer encoding of the attachment
|
||||||
public static final String ENCODING = "encoding";
|
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 {
|
public static final class Attachment extends EmailContent implements AttachmentColumns {
|
||||||
|
@ -1560,6 +1575,8 @@ public abstract class EmailContent {
|
||||||
public long mMessageKey;
|
public long mMessageKey;
|
||||||
public String mLocation;
|
public String mLocation;
|
||||||
public String mEncoding;
|
public String mEncoding;
|
||||||
|
public String mContent;
|
||||||
|
public int mFlags;
|
||||||
|
|
||||||
public static final int CONTENT_ID_COLUMN = 0;
|
public static final int CONTENT_ID_COLUMN = 0;
|
||||||
public static final int CONTENT_FILENAME_COLUMN = 1;
|
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_MESSAGE_ID_COLUMN = 6;
|
||||||
public static final int CONTENT_LOCATION_COLUMN = 7;
|
public static final int CONTENT_LOCATION_COLUMN = 7;
|
||||||
public static final int CONTENT_ENCODING_COLUMN = 8;
|
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[] {
|
public static final String[] CONTENT_PROJECTION = new String[] {
|
||||||
RECORD_ID, AttachmentColumns.FILENAME, AttachmentColumns.MIME_TYPE,
|
RECORD_ID, AttachmentColumns.FILENAME, AttachmentColumns.MIME_TYPE,
|
||||||
AttachmentColumns.SIZE, AttachmentColumns.CONTENT_ID, AttachmentColumns.CONTENT_URI,
|
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
|
* no public constructor since this is a utility class
|
||||||
*/
|
*/
|
||||||
|
@ -1672,6 +1696,8 @@ public abstract class EmailContent {
|
||||||
mMessageKey = cursor.getLong(CONTENT_MESSAGE_ID_COLUMN);
|
mMessageKey = cursor.getLong(CONTENT_MESSAGE_ID_COLUMN);
|
||||||
mLocation = cursor.getString(CONTENT_LOCATION_COLUMN);
|
mLocation = cursor.getString(CONTENT_LOCATION_COLUMN);
|
||||||
mEncoding = cursor.getString(CONTENT_ENCODING_COLUMN);
|
mEncoding = cursor.getString(CONTENT_ENCODING_COLUMN);
|
||||||
|
mContent = cursor.getString(CONTENT_CONTENT_COLUMN);
|
||||||
|
mFlags = cursor.getInt(CONTENT_FLAGS_COLUMN);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1686,6 +1712,8 @@ public abstract class EmailContent {
|
||||||
values.put(AttachmentColumns.MESSAGE_KEY, mMessageKey);
|
values.put(AttachmentColumns.MESSAGE_KEY, mMessageKey);
|
||||||
values.put(AttachmentColumns.LOCATION, mLocation);
|
values.put(AttachmentColumns.LOCATION, mLocation);
|
||||||
values.put(AttachmentColumns.ENCODING, mEncoding);
|
values.put(AttachmentColumns.ENCODING, mEncoding);
|
||||||
|
values.put(AttachmentColumns.CONTENT, mContent);
|
||||||
|
values.put(AttachmentColumns.FLAGS, mFlags);
|
||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1704,6 +1732,8 @@ public abstract class EmailContent {
|
||||||
dest.writeLong(mMessageKey);
|
dest.writeLong(mMessageKey);
|
||||||
dest.writeString(mLocation);
|
dest.writeString(mLocation);
|
||||||
dest.writeString(mEncoding);
|
dest.writeString(mEncoding);
|
||||||
|
dest.writeString(mContent);
|
||||||
|
dest.writeInt(mFlags);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Attachment(Parcel in) {
|
public Attachment(Parcel in) {
|
||||||
|
@ -1717,6 +1747,8 @@ public abstract class EmailContent {
|
||||||
mMessageKey = in.readLong();
|
mMessageKey = in.readLong();
|
||||||
mLocation = in.readString();
|
mLocation = in.readString();
|
||||||
mEncoding = in.readString();
|
mEncoding = in.readString();
|
||||||
|
mContent = in.readString();
|
||||||
|
mFlags = in.readInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final Parcelable.Creator<EmailContent.Attachment> CREATOR
|
public static final Parcelable.Creator<EmailContent.Attachment> CREATOR
|
||||||
|
@ -1733,7 +1765,8 @@ public abstract class EmailContent {
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "[" + mFileName + ", " + mMimeType + ", " + mSize + ", " + mContentId + ", "
|
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 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.
|
// Any changes to the database format *must* include update-in-place code.
|
||||||
// Original version: 3
|
// Original version: 3
|
||||||
// Version 4: Database wipe required; changing AccountManager interface w/Exchange
|
// 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 8: Add security flags column to accounts table
|
||||||
// Version 9: Add security sync key and signature to accounts table
|
// Version 9: Add security sync key and signature to accounts table
|
||||||
// Version 10: Add meeting info to message 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.
|
// Any changes to the database format *must* include update-in-place code.
|
||||||
// Original version: 2
|
// Original version: 2
|
||||||
|
@ -505,7 +504,9 @@ public class EmailProvider extends ContentProvider {
|
||||||
+ AttachmentColumns.CONTENT_URI + " text, "
|
+ AttachmentColumns.CONTENT_URI + " text, "
|
||||||
+ AttachmentColumns.MESSAGE_KEY + " integer, "
|
+ AttachmentColumns.MESSAGE_KEY + " integer, "
|
||||||
+ AttachmentColumns.LOCATION + " text, "
|
+ 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("create table " + Attachment.TABLE_NAME + s);
|
||||||
db.execSQL(createIndex(Attachment.TABLE_NAME, AttachmentColumns.MESSAGE_KEY));
|
db.execSQL(createIndex(Attachment.TABLE_NAME, AttachmentColumns.MESSAGE_KEY));
|
||||||
|
@ -755,6 +756,19 @@ public class EmailProvider extends ContentProvider {
|
||||||
}
|
}
|
||||||
oldVersion = 10;
|
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
|
@Override
|
||||||
|
|
|
@ -161,9 +161,9 @@ public class EmailSyncAdapter extends AbstractSyncAdapter {
|
||||||
case Tags.EMAIL_MESSAGE_CLASS:
|
case Tags.EMAIL_MESSAGE_CLASS:
|
||||||
String messageClass = getValue();
|
String messageClass = getValue();
|
||||||
if (messageClass.equals("IPM.Schedule.Meeting.Request")) {
|
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")) {
|
} else if (messageClass.equals("IPM.Schedule.Meeting.Canceled")) {
|
||||||
msg.mFlags |= Message.FLAG_MEETING_CANCEL_NOTICE;
|
msg.mFlags |= Message.FLAG_INCOMING_MEETING_CANCEL;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Tags.EMAIL_MEETING_REQUEST:
|
case Tags.EMAIL_MEETING_REQUEST:
|
||||||
|
|
|
@ -190,6 +190,8 @@ public class ProviderTestUtils extends Assert {
|
||||||
att.mMimeType = "mimeType " + fileName;
|
att.mMimeType = "mimeType " + fileName;
|
||||||
att.mLocation = "location " + fileName;
|
att.mLocation = "location " + fileName;
|
||||||
att.mEncoding = "encoding " + fileName;
|
att.mEncoding = "encoding " + fileName;
|
||||||
|
att.mContent = "content " + fileName;
|
||||||
|
att.mFlags = 0;
|
||||||
if (saveIt) {
|
if (saveIt) {
|
||||||
att.save(context);
|
att.save(context);
|
||||||
}
|
}
|
||||||
|
@ -352,5 +354,7 @@ public class ProviderTestUtils extends Assert {
|
||||||
assertEquals(caller + " mMimeType", expect.mMimeType, actual.mMimeType);
|
assertEquals(caller + " mMimeType", expect.mMimeType, actual.mMimeType);
|
||||||
assertEquals(caller + " mLocation", expect.mLocation, actual.mLocation);
|
assertEquals(caller + " mLocation", expect.mLocation, actual.mLocation);
|
||||||
assertEquals(caller + " mEncoding", expect.mEncoding, actual.mEncoding);
|
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