Merge branch 'readonly-p4-donut' into donut

This commit is contained in:
Tadashi Takaoka 2009-05-27 19:06:31 -07:00 committed by The Android Open Source Project
commit 7b92d9a2a9
8 changed files with 391 additions and 9 deletions

View File

@ -36,6 +36,10 @@ public interface Part {
public String[] getHeader(String name) throws MessagingException;
public void setExtendedHeader(String name, String value) throws MessagingException;
public String getExtendedHeader(String name) throws MessagingException;
public int getSize() throws MessagingException;
public boolean isMimeType(String mimeType) throws MessagingException;

View File

@ -32,11 +32,14 @@ import com.android.email.mail.MessagingException;
*/
public class MimeBodyPart extends BodyPart {
protected MimeHeader mHeader = new MimeHeader();
protected MimeHeader mExtendedHeader;
protected Body mBody;
protected int mSize;
// regex that matches content id surrounded by "<>" optionally.
private static final Pattern REMOVE_OPTIONAL_BRACKETS = Pattern.compile("^<?([^>]+)>?$");
// regex that matches end of line.
private static final Pattern END_OF_LINE = Pattern.compile("\r?\n");
public MimeBodyPart() throws MessagingException {
this(null);
@ -135,6 +138,41 @@ public class MimeBodyPart extends BodyPart {
return mSize;
}
/**
* Set extended header
*
* @param name Extended header name
* @param value header value - flattened by removing CR-NL if any
* remove header if value is null
* @throws MessagingException
*/
public void setExtendedHeader(String name, String value) throws MessagingException {
if (value == null) {
if (mExtendedHeader != null) {
mExtendedHeader.removeHeader(name);
}
return;
}
if (mExtendedHeader == null) {
mExtendedHeader = new MimeHeader();
}
mExtendedHeader.setHeader(name, END_OF_LINE.matcher(value).replaceAll(""));
}
/**
* Get extended header
*
* @param name Extended header name
* @return header value - null if header does not exist
* @throws MessagingException
*/
public String getExtendedHeader(String name) throws MessagingException {
if (mExtendedHeader == null) {
return null;
}
return mExtendedHeader.getFirstHeader(name);
}
/**
* Write the MimeMessage out in MIME format.
*/

View File

@ -21,6 +21,7 @@ import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.regex.Pattern;
import com.android.email.Utility;
import com.android.email.mail.MessagingException;
@ -98,6 +99,25 @@ public class MimeHeader {
mFields.removeAll(removeFields);
}
/**
* Write header into String
*
* @return CR-NL separated header string except the headers in writeOmitFields
* null if header is empty
*/
public String writeToString() {
if (mFields.size() == 0) {
return null;
}
StringBuilder builder = new StringBuilder();
for (Field field : mFields) {
if (!Utility.arrayContains(writeOmitFields, field.name)) {
builder.append(field.name + ": " + field.value + "\r\n");
}
}
return builder.toString();
}
public void writeTo(OutputStream out) throws IOException, MessagingException {
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out), 1024);
for (Field field : mFields) {

View File

@ -30,7 +30,10 @@ import org.apache.james.mime4j.MimeStreamParser;
import org.apache.james.mime4j.field.DateTimeField;
import org.apache.james.mime4j.field.Field;
import android.text.TextUtils;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@ -47,6 +50,7 @@ import java.util.regex.Pattern;
*/
public class MimeMessage extends Message {
protected MimeHeader mHeader = new MimeHeader();
protected MimeHeader mExtendedHeader;
// NOTE: The fields here are transcribed out of headers, and values stored here will supercede
// the values found in the headers. Use caution to prevent any out-of-phase errors. In
@ -70,6 +74,8 @@ public class MimeMessage extends Message {
// regex that matches content id surrounded by "<>" optionally.
private static final Pattern REMOVE_OPTIONAL_BRACKETS = Pattern.compile("^<?([^>]+)>?$");
// regex that matches end of line.
private static final Pattern END_OF_LINE = Pattern.compile("\r?\n");
public MimeMessage() {
/*
@ -361,9 +367,85 @@ public class MimeMessage extends Message {
mHeader.removeHeader(name);
}
/**
* Set extended header
*
* @param name Extended header name
* @param value header value - flattened by removing CR-NL if any
* remove header if value is null
* @throws MessagingException
*/
public void setExtendedHeader(String name, String value) throws MessagingException {
if (value == null) {
if (mExtendedHeader != null) {
mExtendedHeader.removeHeader(name);
}
return;
}
if (mExtendedHeader == null) {
mExtendedHeader = new MimeHeader();
}
mExtendedHeader.setHeader(name, END_OF_LINE.matcher(value).replaceAll(""));
}
/**
* Get extended header
*
* @param name Extended header name
* @return header value - null if header does not exist
* @throws MessagingException
*/
public String getExtendedHeader(String name) throws MessagingException {
if (mExtendedHeader == null) {
return null;
}
return mExtendedHeader.getFirstHeader(name);
}
/**
* Set entire extended headers from String
*
* @param headers Extended header and its value - "CR-NL-separated pairs
* if null or empty, remove entire extended headers
* @throws MessagingException
*/
public void setExtendedHeaders(String headers) throws MessagingException {
if (TextUtils.isEmpty(headers)) {
mExtendedHeader = null;
} else {
mExtendedHeader = new MimeHeader();
for (String header : END_OF_LINE.split(headers)) {
String[] tokens = header.split(":", 2);
if (tokens.length != 2) {
throw new MessagingException("Illegal extended headers: " + headers);
}
mExtendedHeader.setHeader(tokens[0].trim(), tokens[1].trim());
}
}
}
/**
* Get entire extended headers as String
*
* @return "CR-NL-separated extended headers - null if extended header does not exist
*/
public String getExtendedHeaders() {
if (mExtendedHeader != null) {
return mExtendedHeader.writeToString();
}
return null;
}
/**
* Write message header and body to output stream
*
* @param out Output steam to write message header and body.
*/
public void writeTo(OutputStream out) throws IOException, MessagingException {
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out), 1024);
mHeader.writeTo(out);
// mExtendedHeader will not be write out to external output stream,
// because it is intended to internal use.
writer.write("\r\n");
writer.flush();
if (mBody != null) {

View File

@ -82,9 +82,10 @@ public class LocalStore extends Store implements PersistentDataCallbacks {
* 22 - Added store_flag_1 and store_flag_2 columns to messages table.
* 23 - Added flag_downloaded_full, flag_downloaded_partial, flag_deleted
* columns to message table.
* 24 - Added x_headers to messages table.
*/
private static final int DB_VERSION = 23;
private static final int DB_VERSION = 24;
private static final Flag[] PERMANENT_FLAGS = { Flag.DELETED, Flag.X_DESTROYED, Flag.SEEN };
@ -148,7 +149,7 @@ public class LocalStore extends Store implements PersistentDataCallbacks {
"html_content TEXT, text_content TEXT, attachment_count INTEGER, " +
"internal_date INTEGER, message_id TEXT, store_flag_1 INTEGER, " +
"store_flag_2 INTEGER, flag_downloaded_full INTEGER," +
"flag_downloaded_partial INTEGER, flag_deleted INTEGER)");
"flag_downloaded_partial INTEGER, flag_deleted INTEGER, x_headers TEXT)");
mDb.execSQL("DROP TABLE IF EXISTS attachments");
mDb.execSQL("CREATE TABLE attachments (id INTEGER PRIMARY KEY, message_id INTEGER,"
@ -218,6 +219,13 @@ public class LocalStore extends Store implements PersistentDataCallbacks {
mDb.endTransaction();
}
}
if (oldVersion < 24) {
/**
* Upgrade 23 to 24: add x_headers to messages table
*/
mDb.execSQL("ALTER TABLE messages ADD COLUMN x_headers TEXT;");
mDb.setVersion(24);
}
}
if (mDb.getVersion() != DB_VERSION) {
@ -845,7 +853,7 @@ public class LocalStore extends Store implements PersistentDataCallbacks {
"subject, sender_list, date, uid, flags, id, to_list, cc_list, " +
"bcc_list, reply_to_list, attachment_count, internal_date, message_id, " +
"store_flag_1, store_flag_2, flag_downloaded_full, flag_downloaded_partial, " +
"flag_deleted";
"flag_deleted, x_headers";
/**
* Populate a message from a cursor with the following columns:
@ -868,6 +876,7 @@ public class LocalStore extends Store implements PersistentDataCallbacks {
* 15 flag "downloaded full"
* 16 flag "downloaded partial"
* 17 flag "deleted"
* 18 extended headers ("\r\n"-separated string)
*/
private void populateMessageFromGetMessageCursor(LocalMessage message, Cursor cursor)
throws MessagingException{
@ -901,6 +910,7 @@ public class LocalStore extends Store implements PersistentDataCallbacks {
message.setFlagInternal(Flag.X_DOWNLOADED_FULL, (0 != cursor.getInt(15)));
message.setFlagInternal(Flag.X_DOWNLOADED_PARTIAL, (0 != cursor.getInt(16)));
message.setFlagInternal(Flag.DELETED, (0 != cursor.getInt(17)));
message.setExtendedHeaders(cursor.getString(18));
}
@Override
@ -1155,6 +1165,7 @@ public class LocalStore extends Store implements PersistentDataCallbacks {
cv.put("flag_downloaded_partial",
makeFlagNumeric(message, Flag.X_DOWNLOADED_PARTIAL));
cv.put("flag_deleted", makeFlagNumeric(message, Flag.DELETED));
cv.put("x_headers", ((MimeMessage) message).getExtendedHeaders());
long messageId = mDb.insert("messages", "uid", cv);
for (Part attachment : attachments) {
saveAttachment(messageId, attachment, copy);
@ -1209,7 +1220,7 @@ public class LocalStore extends Store implements PersistentDataCallbacks {
+ "html_content = ?, text_content = ?, reply_to_list = ?, "
+ "attachment_count = ?, message_id = ?, store_flag_1 = ?, "
+ "store_flag_2 = ?, flag_downloaded_full = ?, "
+ "flag_downloaded_partial = ?, flag_deleted = ? "
+ "flag_downloaded_partial = ?, flag_deleted = ?, x_headers = ? "
+ "WHERE id = ?",
new Object[] {
message.getUid(),
@ -1236,6 +1247,7 @@ public class LocalStore extends Store implements PersistentDataCallbacks {
makeFlagNumeric(message, Flag.X_DOWNLOADED_FULL),
makeFlagNumeric(message, Flag.X_DOWNLOADED_PARTIAL),
makeFlagNumeric(message, Flag.DELETED),
message.getExtendedHeaders(),
message.mId
});

View File

@ -0,0 +1,61 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.email.mail.internet;
import android.test.suitebuilder.annotation.SmallTest;
import junit.framework.TestCase;
/**
* This is a series of unit tests for the MimeHeader class. These tests must be locally
* complete - no server(s) required.
*/
@SmallTest
public class MimeHeaderUnitTests extends TestCase {
// TODO more test
/**
* Test for writeToString()
*/
public void testWriteToString() throws Exception {
MimeHeader header = new MimeHeader();
// empty header
String actual1 = header.writeToString();
assertEquals("empty header", actual1, null);
// single header
header.setHeader("Header1", "value1");
String actual2 = header.writeToString();
assertEquals("single header", actual2, "Header1: value1\r\n");
// multiple headers
header.setHeader("Header2", "value2");
String actual3 = header.writeToString();
assertEquals("multiple headers", actual3,
"Header1: value1\r\n"
+ "Header2: value2\r\n");
// omit header
header.setHeader(MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA, "value3");
String actual4 = header.writeToString();
assertEquals("multiple headers", actual4,
"Header1: value1\r\n"
+ "Header2: value2\r\n");
}
}

View File

@ -19,12 +19,11 @@ package com.android.email.mail.internet;
import com.android.email.mail.Address;
import com.android.email.mail.Flag;
import com.android.email.mail.MessagingException;
import com.android.email.mail.internet.MimeHeader;
import com.android.email.mail.internet.MimeMessage;
import com.android.email.mail.Message.RecipientType;
import android.test.suitebuilder.annotation.SmallTest;
import java.io.ByteArrayOutputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
@ -334,4 +333,90 @@ public class MimeMessageTest extends TestCase {
}
/*
* Test for setExtendedHeader() and getExtendedHeader()
*/
public void testExtendedHeader() throws MessagingException {
MimeMessage message = new MimeMessage();
assertNull("non existent header", message.getExtendedHeader("X-Non-Existent"));
message.setExtendedHeader("X-Header1", "value1");
message.setExtendedHeader("X-Header2", "value2\n value3\r\n value4\r\n");
assertEquals("simple value", "value1",
message.getExtendedHeader("X-Header1"));
assertEquals("multi line value", "value2 value3 value4",
message.getExtendedHeader("X-Header2"));
assertNull("non existent header 2", message.getExtendedHeader("X-Non-Existent"));
message.setExtendedHeader("X-Header1", "value4");
assertEquals("over written value", "value4", message.getExtendedHeader("X-Header1"));
message.setExtendedHeader("X-Header1", null);
assertNull("remove header", message.getExtendedHeader("X-Header1"));
}
/*
* Test for setExtendedHeaders() and getExtendedheaders()
*/
public void testExtendedHeaders() throws MessagingException {
MimeMessage message = new MimeMessage();
assertNull("new message", message.getExtendedHeaders());
message.setExtendedHeaders(null);
assertNull("null headers", message.getExtendedHeaders());
message.setExtendedHeaders("");
assertNull("empty headers", message.getExtendedHeaders());
message.setExtendedHeaders("X-Header1: value1\r\n");
assertEquals("header 1 value", "value1", message.getExtendedHeader("X-Header1"));
assertEquals("header 1", "X-Header1: value1\r\n", message.getExtendedHeaders());
message.setExtendedHeaders(null);
message.setExtendedHeader("X-Header2", "value2");
message.setExtendedHeader("X-Header3", "value3\n value4\r\n value5\r\n");
assertEquals("headers 2,3",
"X-Header2: value2\r\n" +
"X-Header3: value3 value4 value5\r\n",
message.getExtendedHeaders());
message.setExtendedHeaders(
"X-Header3: value3 value4 value5\r\n" +
"X-Header2: value2\r\n");
assertEquals("header 2", "value2", message.getExtendedHeader("X-Header2"));
assertEquals("header 3", "value3 value4 value5", message.getExtendedHeader("X-Header3"));
assertEquals("headers 3,2",
"X-Header3: value3 value4 value5\r\n" +
"X-Header2: value2\r\n",
message.getExtendedHeaders());
}
/*
* Test for writeTo(), only for header part.
*/
public void testWriteToHeader() throws Exception {
MimeMessage message = new MimeMessage();
message.setHeader("Header1", "value1");
message.setHeader(MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA, "value2");
message.setExtendedHeader("X-Header3", "value3");
message.setHeader("Header4", "value4");
message.setExtendedHeader("X-Header5", "value5");
ByteArrayOutputStream out = new ByteArrayOutputStream();
message.writeTo(out);
out.close();
String expectedString = "Message-ID: " + message.getMessageId() + "\r\n" +
"Header1: value1\r\n" +
"Header4: value4\r\n" +
"\r\n";
byte[] expected = expectedString.getBytes();
byte[] actual = out.toByteArray();
assertEquals("output length", expected.length, actual.length);
for (int i = 0; i < actual.length; ++i) {
assertEquals("output byte["+i+"]", expected[i], actual[i]);
}
}
// TODO more test for writeTo()
}

View File

@ -35,6 +35,7 @@ import com.android.email.mail.internet.BinaryTempFileBody;
import com.android.email.mail.internet.MimeMessage;
import com.android.email.mail.internet.MimeUtility;
import com.android.email.mail.internet.TextBody;
import com.android.email.mail.store.LocalStore.LocalMessage;
import android.content.ContentValues;
import android.database.Cursor;
@ -64,7 +65,7 @@ public class LocalStoreUnitTests extends AndroidTestCase {
private static final String MESSAGE_ID = "Test-Message-ID";
private static final String MESSAGE_ID_2 = "Test-Message-ID-Second";
private static final int DATABASE_VERSION = 23;
private static final int DATABASE_VERSION = 24;
private static final String FOLDER_NAME = "TEST";
private static final String MISSING_FOLDER_NAME = "TEST-NO-FOLDER";
@ -99,6 +100,11 @@ public class LocalStoreUnitTests extends AndroidTestCase {
*/
@Override
protected void tearDown() throws Exception {
super.tearDown();
if (mFolder != null) {
mFolder.close(false);
}
// First, try the official way
if (mStore != null) {
mStore.delete();
@ -873,6 +879,28 @@ public class LocalStoreUnitTests extends AndroidTestCase {
}
}
/**
* Test for setExtendedHeader() and getExtendedHeader()
*/
public void testExtendedHeader() throws MessagingException {
MimeMessage message = new MimeMessage();
message.setUid("message1");
mFolder.appendMessages(new Message[] { message });
message.setUid("message2");
message.setExtendedHeader("X-Header1", "value1");
message.setExtendedHeader("X-Header2", "value2\r\n value3\n value4\r\n");
mFolder.appendMessages(new Message[] { message });
LocalMessage message1 = (LocalMessage) mFolder.getMessage("message1");
assertNull("none existent header", message1.getExtendedHeader("X-None-Existent"));
LocalMessage message2 = (LocalMessage) mFolder.getMessage("message2");
assertEquals("header 1", "value1", message2.getExtendedHeader("X-Header1"));
assertEquals("header 2", "value2 value3 value4", message2.getExtendedHeader("X-Header2"));
assertNull("header 3", message2.getExtendedHeader("X-Header3"));
}
/**
* Tests for database version.
*/
@ -978,7 +1006,7 @@ public class LocalStoreUnitTests extends AndroidTestCase {
// check if data are expected
final ContentValues actualMessage = cursorToContentValues(c,
new String[] { "primary", "integer", "integer", "text" });
assertEquals("messages table cursor does not have expected values",
assertEquals("messages table cursor does not have expected values",
expectedMessage, actualMessage);
c.close();
@ -1187,6 +1215,57 @@ public class LocalStoreUnitTests extends AndroidTestCase {
}
/**
* Tests for database upgrade from version 23 to current version.
*/
public void testDbUpgrade23ToLatest() throws MessagingException, URISyntaxException {
final URI uri = new URI(mLocalStoreUri);
final String dbPath = uri.getPath();
SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(dbPath, null);
// create sample version 23 db tables
createSampleDb(db, 23);
// sample message data and expected data
final ContentValues initialMessage = new ContentValues();
initialMessage.put("folder_id", (long) 2); // folder_id type integer == Long
initialMessage.put("internal_date", (long) 3); // internal_date type integer == Long
final ContentValues expectedMessage = new ContentValues(initialMessage);
expectedMessage.put("id", db.insert("messages", null, initialMessage));
db.close();
// upgrade database 23 to latest
LocalStore.newInstance(mLocalStoreUri, getContext(), null);
// added message_id column should be initialized as null
expectedMessage.put("message_id", (String) null); // message_id type text == String
// database should be upgraded
db = SQLiteDatabase.openOrCreateDatabase(dbPath, null);
assertEquals("database should be upgraded", DATABASE_VERSION, db.getVersion());
Cursor c;
// check for all "latest version" tables
checkAllTablesFound(db);
// check message table
c = db.query("messages",
new String[] { "id", "folder_id", "internal_date", "message_id" },
null, null, null, null, null);
// check if data is available
assertTrue("messages table should have one data", c.moveToNext());
// check if data are expected
final ContentValues actualMessage = cursorToContentValues(c,
new String[] { "primary", "integer", "integer", "text" });
assertEquals("messages table cursor does not have expected values",
expectedMessage, actualMessage);
c.close();
db.close();
}
/**
* Checks the database to confirm that all tables, with all expected columns are found.
*/
private void checkAllTablesFound(SQLiteDatabase db) {
@ -1204,7 +1283,7 @@ public class LocalStoreUnitTests extends AndroidTestCase {
"to_list", "cc_list", "bcc_list", "reply_to_list",
"html_content", "text_content", "attachment_count",
"internal_date", "store_flag_1", "store_flag_2", "flag_downloaded_full",
"flag_downloaded_partial", "flag_deleted" }
"flag_downloaded_partial", "flag_deleted", "x_headers" }
));
assertTrue("messages", foundNames.containsAll(expectedNames));
@ -1243,6 +1322,7 @@ public class LocalStoreUnitTests extends AndroidTestCase {
((version >= 23) ?
", flag_downloaded_full INTEGER, flag_downloaded_partial INTEGER" : "") +
((version >= 23) ? ", flag_deleted INTEGER" : "") +
((version >= 24) ? ", x_headers TEXT" : "") +
")");
db.execSQL("DROP TABLE IF EXISTS attachments");
db.execSQL("CREATE TABLE attachments (id INTEGER PRIMARY KEY, message_id INTEGER," +