auto import from //branches/cupcake_rel/...@140373

This commit is contained in:
The Android Open Source Project 2009-03-18 17:39:48 -07:00
parent 3b85e2c2b5
commit 3469902379
33 changed files with 1123 additions and 125 deletions

View File

@ -84,6 +84,10 @@
<string name="message_deleted_toast">"Zpráva byla smazána."</string>
<string name="message_discarded_toast">"Zpráva byla zrušena."</string>
<string name="message_saved_toast">"Zpráva byla uložena jako koncept."</string>
<!-- no translation found for add_contact_dlg_title (7424641029188006580) -->
<skip />
<!-- no translation found for add_contact_dlg_message_fmt (4470846346726847478) -->
<skip />
<string name="account_setup_basics_title">"Nastavit e-mail"</string>
<string name="account_setup_basics_instructions">"Zadejte e-mailovou adresu účtu:"</string>
<string name="account_setup_basics_email_hint">"E-mailová adresa"</string>

View File

@ -84,6 +84,10 @@
<string name="message_deleted_toast">"Nachricht gelöscht"</string>
<string name="message_discarded_toast">"Nachricht gelöscht"</string>
<string name="message_saved_toast">"Nachricht als Entwurf gespeichert"</string>
<!-- no translation found for add_contact_dlg_title (7424641029188006580) -->
<skip />
<!-- no translation found for add_contact_dlg_message_fmt (4470846346726847478) -->
<skip />
<string name="account_setup_basics_title">"E-Mail einrichten"</string>
<string name="account_setup_basics_instructions">"Geben Sie Ihre im Konto gespeicherte E-Mail-Adresse ein:"</string>
<string name="account_setup_basics_email_hint">"E-Mail-Adresse"</string>

View File

@ -84,6 +84,10 @@
<string name="message_deleted_toast">"Mensaje suprimido"</string>
<string name="message_discarded_toast">"Mensaje descartado"</string>
<string name="message_saved_toast">"Mensaje guardado como borrador"</string>
<!-- no translation found for add_contact_dlg_title (7424641029188006580) -->
<skip />
<!-- no translation found for add_contact_dlg_message_fmt (4470846346726847478) -->
<skip />
<string name="account_setup_basics_title">"Configurar correo electrónico"</string>
<string name="account_setup_basics_instructions">"Introduce la dirección de correo electrónico de tu cuenta:"</string>
<string name="account_setup_basics_email_hint">"Dirección de correo electrónico"</string>

View File

@ -84,6 +84,10 @@
<string name="message_deleted_toast">"Message supprimé."</string>
<string name="message_discarded_toast">"Message supprimé."</string>
<string name="message_saved_toast">"Message enregistré comme brouillon."</string>
<!-- no translation found for add_contact_dlg_title (7424641029188006580) -->
<skip />
<!-- no translation found for add_contact_dlg_message_fmt (4470846346726847478) -->
<skip />
<string name="account_setup_basics_title">"Configurer la messagerie électronique"</string>
<string name="account_setup_basics_instructions">"Saisissez l\'adresse e-mail de votre compte :"</string>
<string name="account_setup_basics_email_hint">"Adresse e-mail"</string>

View File

@ -84,6 +84,10 @@
<string name="message_deleted_toast">"Messaggio eliminato."</string>
<string name="message_discarded_toast">"Messaggio eliminato."</string>
<string name="message_saved_toast">"Messaggio salvato come bozza."</string>
<!-- no translation found for add_contact_dlg_title (7424641029188006580) -->
<skip />
<!-- no translation found for add_contact_dlg_message_fmt (4470846346726847478) -->
<skip />
<string name="account_setup_basics_title">"Imposta email"</string>
<string name="account_setup_basics_instructions">"Digita l\'indirizzo email del tuo account:"</string>
<string name="account_setup_basics_email_hint">"Indirizzo email"</string>

View File

@ -84,6 +84,10 @@
<string name="message_deleted_toast">"メッセージを削除しました。"</string>
<string name="message_discarded_toast">"メッセージを破棄しました。"</string>
<string name="message_saved_toast">"メッセージを下書きとして保存しました。"</string>
<!-- no translation found for add_contact_dlg_title (7424641029188006580) -->
<skip />
<!-- no translation found for add_contact_dlg_message_fmt (4470846346726847478) -->
<skip />
<string name="account_setup_basics_title">"メールアカウントの登録"</string>
<string name="account_setup_basics_instructions">"メールのアカウント情報を入力:"</string>
<string name="account_setup_basics_email_hint">"メールアドレス"</string>

View File

@ -84,6 +84,10 @@
<string name="message_deleted_toast">"메일이 삭제되었습니다."</string>
<string name="message_discarded_toast">"메일이 삭제되었습니다."</string>
<string name="message_saved_toast">"메일을 임시로 저장했습니다."</string>
<!-- no translation found for add_contact_dlg_title (7424641029188006580) -->
<skip />
<!-- no translation found for add_contact_dlg_message_fmt (4470846346726847478) -->
<skip />
<string name="account_setup_basics_title">"이메일 설정"</string>
<string name="account_setup_basics_instructions">"계정 이메일 주소 입력:"</string>
<string name="account_setup_basics_email_hint">"이메일 주소"</string>

View File

@ -84,6 +84,10 @@
<string name="message_deleted_toast">"Meldingen ble slettet."</string>
<string name="message_discarded_toast">"Meldingen ble forkastet."</string>
<string name="message_saved_toast">"Meldingen ble lagret som utkast."</string>
<!-- no translation found for add_contact_dlg_title (7424641029188006580) -->
<skip />
<!-- no translation found for add_contact_dlg_message_fmt (4470846346726847478) -->
<skip />
<string name="account_setup_basics_title">"Sett opp e-post"</string>
<string name="account_setup_basics_instructions">"Skriv e-postadressen til kontoen din:"</string>
<string name="account_setup_basics_email_hint">"E-postadresse"</string>

View File

@ -84,6 +84,10 @@
<string name="message_deleted_toast">"Bericht verwijderd."</string>
<string name="message_discarded_toast">"Bericht wordt verwijderd"</string>
<string name="message_saved_toast">"Bericht opgeslagen als concept."</string>
<!-- no translation found for add_contact_dlg_title (7424641029188006580) -->
<skip />
<!-- no translation found for add_contact_dlg_message_fmt (4470846346726847478) -->
<skip />
<string name="account_setup_basics_title">"E-mail instellen"</string>
<string name="account_setup_basics_instructions">"Typ het e-mailadres van je account:"</string>
<string name="account_setup_basics_email_hint">"E-mailadres"</string>

View File

@ -84,6 +84,10 @@
<string name="message_deleted_toast">"Wiadomość została usunięta."</string>
<string name="message_discarded_toast">"Wiadomość została odrzucona."</string>
<string name="message_saved_toast">"Wiadomość została zapisana jako wersja robocza."</string>
<!-- no translation found for add_contact_dlg_title (7424641029188006580) -->
<skip />
<!-- no translation found for add_contact_dlg_message_fmt (4470846346726847478) -->
<skip />
<string name="account_setup_basics_title">"Skonfiguruj konto e-mail"</string>
<string name="account_setup_basics_instructions">"Podaj adres e-mail swojego konta:"</string>
<string name="account_setup_basics_email_hint">"Adres e-mail"</string>

View File

@ -84,6 +84,10 @@
<string name="message_deleted_toast">"Письмо удалено."</string>
<string name="message_discarded_toast">"Письмо не сохранено."</string>
<string name="message_saved_toast">"Письмо сохранено как черновик."</string>
<!-- no translation found for add_contact_dlg_title (7424641029188006580) -->
<skip />
<!-- no translation found for add_contact_dlg_message_fmt (4470846346726847478) -->
<skip />
<string name="account_setup_basics_title">"Настройка электронной почты"</string>
<string name="account_setup_basics_instructions">"Укажите почтовый адрес своего аккаунта:"</string>
<string name="account_setup_basics_email_hint">"Адрес электронной почты"</string>

View File

@ -84,6 +84,10 @@
<string name="message_deleted_toast">"邮件已删除。"</string>
<string name="message_discarded_toast">"邮件已取消。"</string>
<string name="message_saved_toast">"邮件已另存为草稿。"</string>
<!-- no translation found for add_contact_dlg_title (7424641029188006580) -->
<skip />
<!-- no translation found for add_contact_dlg_message_fmt (4470846346726847478) -->
<skip />
<string name="account_setup_basics_title">"设置电子邮件"</string>
<string name="account_setup_basics_instructions">"键入您的帐户电子邮件地址:"</string>
<string name="account_setup_basics_email_hint">"电子邮件地址"</string>

View File

@ -84,6 +84,10 @@
<string name="message_deleted_toast">"已刪除郵件。"</string>
<string name="message_discarded_toast">"已捨棄郵件。"</string>
<string name="message_saved_toast">"已儲存郵件草稿。"</string>
<!-- no translation found for add_contact_dlg_title (7424641029188006580) -->
<skip />
<!-- no translation found for add_contact_dlg_message_fmt (4470846346726847478) -->
<skip />
<string name="account_setup_basics_title">"設定電子郵件"</string>
<string name="account_setup_basics_instructions">"請輸入您帳戶的電子郵件地址:"</string>
<string name="account_setup_basics_email_hint">"電子郵件地址"</string>

View File

@ -75,9 +75,8 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.Map;
import java.util.Random;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class MessageView extends Activity
implements OnClickListener {
@ -93,6 +92,9 @@ public class MessageView extends Activity
};
private static final int METHODS_STATUS_COLUMN = 1;
// regex that matches start of img tag. '.*<(?i)img\s+.*'.
private static final Pattern IMG_TAG_START_REGEX = Pattern.compile(".*<(?i)img\\s+.*");
private TextView mSubjectView;
private TextView mFromView;
private TextView mDateView;
@ -451,6 +453,10 @@ public class MessageView extends Activity
Intent contactIntent = new Intent(Contacts.Intents.SHOW_OR_CREATE_CONTACT);
contactIntent.setData(contactUri);
// Pass along full E-mail string for possible create dialog
contactIntent.putExtra(Contacts.Intents.EXTRA_CREATE_DESCRIPTION,
senderEmail.toString());
// Only provide personal name hint if we have one
String senderPersonal = senderEmail.getPersonal();
if (senderPersonal != null) {
@ -694,6 +700,50 @@ public class MessageView extends Activity
}
}
/**
* Resolve content-id reference in src attribute of img tag to AttachmentProvider's
* content uri. This method calls itself recursively at most the number of
* LocalAttachmentPart that mime type is image and has content id.
* The attribute src="cid:content_id" is resolved as src="content://...".
* This method is package scope for testing purpose.
*
* @param text html email text
* @param part mime part which may contain inline image
* @return html text in which src attribute of img tag may be replaced with content uri
*/
/* package */ String resolveInlineImage(String text, Part part, int depth)
throws MessagingException {
// avoid too deep recursive call.
if (depth >= 10) {
return text;
}
String contentType = MimeUtility.unfoldAndDecode(part.getContentType());
String contentId = part.getContentId();
if (contentType.startsWith("image/") &&
contentId != null &&
part instanceof LocalAttachmentBodyPart) {
LocalAttachmentBodyPart attachment = (LocalAttachmentBodyPart)part;
Uri contentUri = AttachmentProvider.getAttachmentUri(
mAccount,
attachment.getAttachmentId());
if (contentUri != null) {
// Regexp which matches ' src="cid:contentId"'.
String contentIdRe = "\\s+(?i)src=\"cid(?-i):\\Q" + contentId + "\\E\"";
// Replace all occurrences of src attribute with ' src="content://contentUri"'.
text = text.replaceAll(contentIdRe, " src=\"" + contentUri + "\"");
}
}
if (part.getBody() instanceof Multipart) {
Multipart mp = (Multipart)part.getBody();
for (int i = 0; i < mp.getCount(); i++) {
text = resolveInlineImage(text, mp.getBodyPart(i), depth + 1);
}
}
return text;
}
private void renderAttachments(Part part, int depth) throws MessagingException {
String contentType = MimeUtility.unfoldAndDecode(part.getContentType());
String name = MimeUtility.getHeaderParameter(contentType, "name");
@ -875,7 +925,7 @@ public class MessageView extends Activity
if (part != null) {
String text = MimeUtility.getTextFromPart(part);
if (part.getMimeType().equalsIgnoreCase("text/html")) {
text = text.replaceAll("cid:", "http://cid/");
text = resolveInlineImage(text, mMessage, 0);
} else {
/*
* Linkify the plain text and convert it to HTML by replacing
@ -898,10 +948,11 @@ public class MessageView extends Activity
}
/*
* TODO this should be smarter, change to regex for img, but consider how to
* get backgroung images and a million other things that HTML allows.
* TODO consider how to get background images and a million other things
* that HTML allows.
*/
if (text.contains("img")) {
// Check if text contains img tag.
if (IMG_TAG_START_REGEX.matcher(text).matches()) {
mHandler.showShowPictures(true);
}

View File

@ -73,10 +73,10 @@ public class Address {
* @return An array of 0 or more Addresses.
*/
public static Address[] parse(String addressList) {
ArrayList<Address> addresses = new ArrayList<Address>();
if (addressList == null) {
if (addressList == null || addressList.length() == 0) {
return new Address[] {};
}
ArrayList<Address> addresses = new ArrayList<Address>();
try {
MailboxList parsedList = AddressList.parse(addressList).flatten();
for (int i = 0, count = parsedList.size(); i < count; i++) {
@ -173,7 +173,7 @@ public class Address {
* @return
*/
public static Address[] unpack(String addressList) {
if (addressList == null) {
if (addressList == null || addressList.length() == 0) {
return new Address[] { };
}
ArrayList<Address> addresses = new ArrayList<Address>();
@ -205,7 +205,7 @@ public class Address {
/**
* Packs an address list into a String that is very quick to read
* and parse. Packed lists can be unpacked with unpackAddressList()
* The packed list is a comma seperated list of:
* The packed list is a comma separated list of:
* URLENCODE(address)[;URLENCODE(personal)]
* @param list
* @return
@ -213,6 +213,8 @@ public class Address {
public static String pack(Address[] addresses) {
if (addresses == null) {
return null;
} else if (addresses.length == 0) {
return "";
}
StringBuffer sb = new StringBuffer();
for (int i = 0, count = addresses.length; i < count; i++) {

View File

@ -31,6 +31,8 @@ public interface Part {
public String getContentType() throws MessagingException;
public String getDisposition() throws MessagingException;
public String getContentId() throws MessagingException;
public String[] getHeader(String name) throws MessagingException;

View File

@ -20,6 +20,7 @@ import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.regex.Pattern;
import com.android.email.mail.Body;
import com.android.email.mail.BodyPart;
@ -34,6 +35,9 @@ public class MimeBodyPart extends BodyPart {
protected Body mBody;
protected int mSize;
// regex that matches content id surrounded by "<>" optionally.
private static final Pattern REMOVE_OPTIONAL_BRACKETS = Pattern.compile("^<?([^>]+)>?$");
public MimeBodyPart() throws MessagingException {
this(null);
}
@ -109,6 +113,16 @@ public class MimeBodyPart extends BodyPart {
}
}
public String getContentId() throws MessagingException {
String contentId = getFirstHeader(MimeHeader.HEADER_CONTENT_ID);
if (contentId == null) {
return null;
} else {
// remove optionally surrounding brackets.
return REMOVE_OPTIONAL_BRACKETS.matcher(contentId).replaceAll("$1");
}
}
public String getMimeType() throws MessagingException {
return MimeUtility.getHeaderParameter(getContentType(), null);
}

View File

@ -38,6 +38,7 @@ public class MimeHeader {
public static final String HEADER_CONTENT_TYPE = "Content-Type";
public static final String HEADER_CONTENT_TRANSFER_ENCODING = "Content-Transfer-Encoding";
public static final String HEADER_CONTENT_DISPOSITION = "Content-Disposition";
public static final String HEADER_CONTENT_ID = "Content-ID";
/**
* Fields that should be omitted when writing the header using writeTo()

View File

@ -25,6 +25,7 @@ import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Stack;
import java.util.regex.Pattern;
import org.apache.james.mime4j.BodyDescriptor;
import org.apache.james.mime4j.ContentHandler;
@ -38,6 +39,7 @@ import com.android.email.mail.Body;
import com.android.email.mail.BodyPart;
import com.android.email.mail.Message;
import com.android.email.mail.MessagingException;
import com.android.email.mail.Multipart;
import com.android.email.mail.Part;
/**
@ -46,12 +48,17 @@ import com.android.email.mail.Part;
*/
public class MimeMessage extends Message {
protected MimeHeader mHeader = new MimeHeader();
// 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
// particular, any adds/changes/deletes here must be echoed by changes in the parse() function.
protected Address[] mFrom;
protected Address[] mTo;
protected Address[] mCc;
protected Address[] mBcc;
protected Address[] mReplyTo;
protected Date mSentDate;
// In MIME, en_US-like date format should be used. In other words "MMM" should be encoded to
// "Jan", not the other localized format like "Ene" (meaning January in locale es).
// This conversion is used when generating outgoing MIME messages. Incoming MIME date
@ -62,6 +69,9 @@ public class MimeMessage extends Message {
protected Body mBody;
protected int mSize;
// regex that matches content id surrounded by "<>" optionally.
private static final Pattern REMOVE_OPTIONAL_BRACKETS = Pattern.compile("^<?([^>]+)>?$");
public MimeMessage() {
/*
* Every new messages gets a Message-ID
@ -100,12 +110,16 @@ public class MimeMessage extends Message {
}
protected void parse(InputStream in) throws IOException, MessagingException {
// Before parsing the input stream, clear all local fields that may be superceded by
// the new incoming message.
mHeader.clear();
mBody = null;
mBcc = null;
mTo = null;
mFrom = null;
mTo = null;
mCc = null;
mBcc = null;
mReplyTo = null;
mSentDate = null;
mBody = null;
MimeStreamParser parser = new MimeStreamParser();
parser.setContentHandler(new MimeMessageBuilder());
@ -152,6 +166,16 @@ public class MimeMessage extends Message {
}
}
public String getContentId() throws MessagingException {
String contentId = getFirstHeader(MimeHeader.HEADER_CONTENT_ID);
if (contentId == null) {
return null;
} else {
// remove optionally surrounding brackets.
return REMOVE_OPTIONAL_BRACKETS.matcher(contentId).replaceAll("$1");
}
}
public String getMimeType() throws MessagingException {
return MimeUtility.getHeaderParameter(getContentType(), null);
}

View File

@ -126,13 +126,9 @@ public class MimeUtility {
}
}
}
String[] header = part.getHeader("Content-ID");
if (header != null) {
for (String s : header) {
if (s.equals(contentId)) {
return part;
}
}
String cid = part.getContentId();
if (contentId.equals(cid)) {
return part;
}
return null;
}

View File

@ -16,6 +16,15 @@
package com.android.email.mail.store;
import com.android.email.Email;
import com.android.email.FixedLengthInputStream;
import com.android.email.PeekableInputStream;
import com.android.email.mail.MessagingException;
import com.android.email.mail.transport.LoggingInputStream;
import android.util.Config;
import android.util.Log;
import java.io.IOException;
import java.io.InputStream;
import java.text.ParseException;
@ -24,14 +33,6 @@ import java.util.ArrayList;
import java.util.Date;
import java.util.Locale;
import android.util.Config;
import android.util.Log;
import com.android.email.Email;
import com.android.email.FixedLengthInputStream;
import com.android.email.PeekableInputStream;
import com.android.email.mail.MessagingException;
public class ImapResponseParser {
// DEBUG ONLY - Always check in as "false"
private static boolean DEBUG_LOG_RAW_STREAM = false;
@ -43,11 +44,11 @@ public class ImapResponseParser {
PeekableInputStream mIn;
InputStream mActiveLiteral;
public ImapResponseParser(PeekableInputStream in) {
public ImapResponseParser(InputStream in) {
if (DEBUG_LOG_RAW_STREAM && Config.LOGD && Email.DEBUG) {
in = new LoggingInputStream(in);
}
this.mIn = in;
this.mIn = new PeekableInputStream(in);
}
/**
@ -383,69 +384,4 @@ public class ImapResponseParser {
}
}
/**
* Simple class used for debugging only that affords us a view of the raw Imap stream,
* in addition to the tokenized version.
*/
private static class LoggingInputStream extends PeekableInputStream {
PeekableInputStream mIn;
StringBuilder mSb;
public LoggingInputStream(PeekableInputStream in) {
super(null);
mIn = in;
mSb = new StringBuilder();
}
/**
* Collect chars as read, and log them when EOL reached.
*/
@Override
public int read() throws IOException {
int oneByte = mIn.read();
logRaw(oneByte);
return oneByte;
}
/**
* Collect chars as read, and log them when EOL reached.
*/
@Override
public int read(byte[] b, int offset, int length) throws IOException {
int bytesRead = mIn.read(b, offset, length);
int copyBytes = bytesRead;
while (copyBytes > 0) {
logRaw((char)b[offset]);
copyBytes--;
offset++;
}
return bytesRead;
}
/**
* Pass-through any peeks
*/
@Override
public int peek() throws IOException {
return mIn.peek();
}
/**
* Write and clear the buffer
*/
private void logRaw(int oneByte) {
if (oneByte == '\r' || oneByte == '\n') {
if (mSb.length() > 0) {
Log.d(Email.LOG_TAG, "RAW " + mSb.toString());
mSb = new StringBuilder();
}
} else {
mSb.append((char)oneByte);
}
}
}
}

View File

@ -856,6 +856,7 @@ public class ImapStore extends Store {
if (bs.get(2) instanceof ImapList) {
bodyParams = bs.getList(2);
}
String cid = bs.getString(3);
String encoding = bs.getString(5);
int size = bs.getNumber(6);
@ -941,6 +942,12 @@ public class ImapStore extends Store {
* to parse the body.
*/
part.setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, encoding);
/*
* Set the Content-ID header.
*/
if (!"NIL".equalsIgnoreCase(cid)) {
part.setHeader(MimeHeader.HEADER_CONTENT_ID, cid);
}
if (part instanceof ImapMessage) {
((ImapMessage) part).setSize(size);
@ -1084,8 +1091,6 @@ public class ImapStore extends Store {
private int mNextCommandTag;
public void open() throws IOException, MessagingException {
PeekableInputStream mIn;
if (mTransport != null && mTransport.isOpen()) {
return;
}
@ -1101,8 +1106,7 @@ public class ImapStore extends Store {
mTransport.open();
mTransport.setSoTimeout(MailTransport.SOCKET_READ_TIMEOUT);
mIn = new PeekableInputStream(mTransport.getInputStream());
mParser = new ImapResponseParser(mIn);
mParser = new ImapResponseParser(mTransport.getInputStream());
// BANNER
mParser.readResponse();
@ -1119,8 +1123,7 @@ public class ImapStore extends Store {
mTransport.reopenTls();
mTransport.setSoTimeout(MailTransport.SOCKET_READ_TIMEOUT);
mIn = new PeekableInputStream(mTransport.getInputStream());
mParser = new ImapResponseParser(mIn);
mParser = new ImapResponseParser(mTransport.getInputStream());
} else if (mTransport.getSecurity() ==
Transport.CONNECTION_SECURITY_TLS_REQUIRED) {
if (Config.LOGD && Email.DEBUG) {

View File

@ -76,10 +76,11 @@ public class LocalStore extends Store {
* ---------- ---------- -----
* 18 pre-1.0 Development versions. No upgrade path.
* 18 1.0, 1.1 1.0 Release version.
* 19 1.5 Added message_id column
* 19 - Added message_id column to messages table.
* 20 1.5 Added content_id column to attachments table.
*/
private static final int DB_VERSION = 19;
private static final int DB_VERSION = 20;
private static final Flag[] PERMANENT_FLAGS = { Flag.DELETED, Flag.X_DESTROYED, Flag.SEEN };
@ -110,6 +111,10 @@ public class LocalStore extends Store {
}
mDb = SQLiteDatabase.openOrCreateDatabase(mPath, null);
int oldVersion = mDb.getVersion();
/*
* TODO we should have more sophisticated way to upgrade database.
*/
if (oldVersion != DB_VERSION) {
if (Config.LOGV) {
Log.v(Email.LOG_TAG, String.format("Upgrading database from %d to %d",
@ -133,7 +138,7 @@ public class LocalStore extends Store {
mDb.execSQL("DROP TABLE IF EXISTS attachments");
mDb.execSQL("CREATE TABLE attachments (id INTEGER PRIMARY KEY, message_id INTEGER,"
+ "store_data TEXT, content_uri TEXT, size INTEGER, name TEXT,"
+ "mime_type TEXT)");
+ "mime_type TEXT, content_id TEXT)");
mDb.execSQL("DROP TABLE IF EXISTS pending_commands");
mDb.execSQL("CREATE TABLE pending_commands " +
@ -146,12 +151,21 @@ public class LocalStore extends Store {
mDb.execSQL("CREATE TRIGGER delete_message BEFORE DELETE ON messages BEGIN DELETE FROM attachments WHERE old.id = message_id; END;");
mDb.setVersion(DB_VERSION);
}
else if (oldVersion < 19) {
/**
* Upgrade 18 to 19: add message_id to messages table
*/
mDb.execSQL("ALTER TABLE messages ADD COLUMN message_id TEXT;");
mDb.setVersion(DB_VERSION);
else {
if (oldVersion < 19) {
/**
* Upgrade 18 to 19: add message_id to messages table
*/
mDb.execSQL("ALTER TABLE messages ADD COLUMN message_id TEXT;");
mDb.setVersion(19);
}
if (oldVersion < 20) {
/**
* Upgrade 19 to 20: add content_id to attachments table
*/
mDb.execSQL("ALTER TABLE attachments ADD COLUMN content_id TEXT;");
mDb.setVersion(20);
}
}
if (mDb.getVersion() != DB_VERSION) {
@ -523,7 +537,8 @@ public class LocalStore extends Store {
"name",
"mime_type",
"store_data",
"content_uri" },
"content_uri",
"content_id" },
"message_id = ?",
new String[] { Long.toString(localMessage.mId) },
null,
@ -537,6 +552,7 @@ public class LocalStore extends Store {
String type = cursor.getString(3);
String storeData = cursor.getString(4);
String contentUri = cursor.getString(5);
String contentId = cursor.getString(6);
Body body = null;
if (contentUri != null) {
body = new LocalAttachmentBody(Uri.parse(contentUri), mContext);
@ -551,6 +567,7 @@ public class LocalStore extends Store {
String.format("attachment;\n filename=\"%s\";\n size=%d",
name,
size));
bp.setHeader(MimeHeader.HEADER_CONTENT_ID, contentId);
/*
* HEADER_ANDROID_ATTACHMENT_STORE_DATA is a custom header we add to that
@ -924,6 +941,7 @@ public class LocalStore extends Store {
MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA), ',');
String name = MimeUtility.getHeaderParameter(attachment.getContentType(), "name");
String contentId = attachment.getContentId();
if (attachmentId == -1) {
ContentValues cv = new ContentValues();
@ -933,6 +951,7 @@ public class LocalStore extends Store {
cv.put("size", size);
cv.put("name", name);
cv.put("mime_type", attachment.getMimeType());
cv.put("content_id", contentId);
attachmentId = mDb.insert("attachments", "message_id", cv);
}
@ -940,6 +959,7 @@ public class LocalStore extends Store {
ContentValues cv = new ContentValues();
cv.put("content_uri", contentUri != null ? contentUri.toString() : null);
cv.put("size", size);
cv.put("content_id", contentId);
mDb.update(
"attachments",
cv,

View File

@ -29,6 +29,7 @@ import com.android.email.mail.Store;
import com.android.email.mail.Transport;
import com.android.email.mail.Folder.OpenMode;
import com.android.email.mail.internet.MimeMessage;
import com.android.email.mail.transport.LoggingInputStream;
import com.android.email.mail.transport.MailTransport;
import android.util.Config;
@ -46,6 +47,7 @@ public class Pop3Store extends Store {
// All flags defining debug or development code settings must be FALSE
// when code is checked in or released.
private static boolean DEBUG_FORCE_SINGLE_LINE_UIDL = false;
private static boolean DEBUG_LOG_RAW_STREAM = false;
private static final Flag[] PERMANENT_FLAGS = { Flag.DELETED };
@ -718,7 +720,11 @@ public class Pop3Store extends Store {
}
if (response != null) {
try {
message.parse(new Pop3ResponseInputStream(mTransport.getInputStream()));
InputStream in = mTransport.getInputStream();
if (DEBUG_LOG_RAW_STREAM && Config.LOGD && Email.DEBUG) {
in = new LoggingInputStream(in);
}
message.parse(new Pop3ResponseInputStream(in));
}
catch (MessagingException me) {
/*

View File

@ -0,0 +1,88 @@
/*
* Copyright (C) 2008 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.transport;
import com.android.email.Email;
import android.util.Log;
import java.io.IOException;
import java.io.InputStream;
/**
* Simple class used for debugging only that affords us a view of the raw IMAP or POP3 stream,
* in addition to the tokenized version.
*
* Use of this class *MUST* be restricted to logging-enabled situations only.
*/
public class LoggingInputStream extends InputStream {
InputStream mIn;
StringBuilder mSb;
boolean mBufferDirty;
private final String LINE_TAG = "RAW ";
public LoggingInputStream(InputStream in) {
super();
mIn = in;
mSb = new StringBuilder(LINE_TAG);
mBufferDirty = false;
}
/**
* Collect chars as read, and log them when EOL reached.
*/
@Override
public int read() throws IOException {
int oneByte = mIn.read();
logRaw(oneByte);
return oneByte;
}
/**
* Collect chars as read, and log them when EOL reached.
*/
@Override
public int read(byte[] b, int offset, int length) throws IOException {
int bytesRead = mIn.read(b, offset, length);
int copyBytes = bytesRead;
while (copyBytes > 0) {
logRaw((char)b[offset]);
copyBytes--;
offset++;
}
return bytesRead;
}
/**
* Write and clear the buffer
*/
private void logRaw(int oneByte) {
if (oneByte == '\r' || oneByte == '\n') {
if (mBufferDirty) {
Log.d(Email.LOG_TAG, mSb.toString());
mSb = new StringBuilder(LINE_TAG);
mBufferDirty = false;
}
} else {
mSb.append((char)oneByte);
mBufferDirty = true;
}
}
}

View File

@ -21,16 +21,25 @@ import com.android.email.Email;
import com.android.email.MessagingController;
import com.android.email.Preferences;
import com.android.email.R;
import com.android.email.mail.MessageTestUtils;
import com.android.email.mail.Message;
import com.android.email.mail.MessagingException;
import com.android.email.mail.MessageTestUtils.MessageBuilder;
import com.android.email.mail.MessageTestUtils.MultipartBuilder;
import com.android.email.mail.MessageTestUtils.TextBuilder;
import com.android.email.mail.internet.BinaryTempFileBody;
import com.android.email.mail.store.LocalStore;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.test.ActivityInstrumentationTestCase2;
import android.test.suitebuilder.annotation.MediumTest;
import android.test.suitebuilder.annotation.Suppress;
import android.view.MenuItem;
import android.webkit.WebView;
import android.widget.TextView;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
@ -58,6 +67,7 @@ public class MessageViewTests
private TextView mToView;
private TextView mSubjectView;
private WebView mMessageContentView;
private Context mContext;
public MessageViewTests() {
super("com.android.email", MessageView.class);
@ -66,13 +76,13 @@ public class MessageViewTests
@Override
protected void setUp() throws Exception {
super.setUp();
Context context = getInstrumentation().getTargetContext();
Account[] accounts = Preferences.getPreferences(context).getAccounts();
mContext = getInstrumentation().getTargetContext();
Account[] accounts = Preferences.getPreferences(mContext).getAccounts();
if (accounts.length > 0)
{
// This depends on getDefaultAccount() to auto-assign the default account, if necessary
mAccount = Preferences.getPreferences(context).getDefaultAccount();
Email.setServicesEnabled(context);
mAccount = Preferences.getPreferences(mContext).getDefaultAccount();
Email.setServicesEnabled(mContext);
}
// configure a mock controller
@ -93,6 +103,9 @@ public class MessageViewTests
mToView = (TextView) a.findViewById(R.id.to);
mSubjectView = (TextView) a.findViewById(R.id.subject);
mMessageContentView = (WebView) a.findViewById(R.id.message_content);
// This is needed for mime image bodypart.
BinaryTempFileBody.setTempDirectory(getActivity().getCacheDir());
}
/**
@ -135,6 +148,90 @@ public class MessageViewTests
a.handleMenuItem(R.id.forward);
a.handleMenuItem(R.id.mark_as_unread);
}
/**
* Tests for resolving inline image src cid: reference to content uri.
*/
public void testResolveInlineImage() throws MessagingException, IOException {
final MessageView a = getActivity();
final LocalStore store = new LocalStore(mAccount.getLocalStoreUri(), mContext);
// Single cid case.
final String cid1 = "cid.1@android.com";
final long aid1 = 10;
final Uri uri1 = MessageTestUtils.contentUri(aid1, mAccount);
final String text1 = new TextBuilder("text1 > ").addCidImg(cid1).build(" <.");
final String expected1 = new TextBuilder("text1 > ").addUidImg(uri1).build(" <.");
// message with cid1
final Message msg1 = new MessageBuilder()
.setBody(new MultipartBuilder("multipart/related")
.addBodyPart(MessageTestUtils.textPart("text/html", text1))
.addBodyPart(MessageTestUtils.imagePart("image/jpeg", "<"+cid1+">", aid1, store))
.build())
.build();
// Simple case.
final String actual1 = a.resolveInlineImage(text1, msg1, 0);
assertEquals("one content id reference is not resolved",
expected1, actual1);
// Exceed recursive limit.
final String actual0 = a.resolveInlineImage(text1, msg1, 10);
assertEquals("recursive call limit may exceeded",
text1, actual0);
// Multiple cids case.
final String cid2 = "cid.2@android.com";
final long aid2 = 20;
final Uri uri2 = MessageTestUtils.contentUri(aid2, mAccount);
final String text2 = new TextBuilder("text2 ").addCidImg(cid2).build(".");
final String expected2 = new TextBuilder("text2 ").addUidImg(uri2).build(".");
// message with only cid2
final Message msg2 = new MessageBuilder()
.setBody(new MultipartBuilder("multipart/related")
.addBodyPart(MessageTestUtils.textPart("text/html", text1 + text2))
.addBodyPart(MessageTestUtils.imagePart("image/gif", cid2, aid2, store))
.build())
.build();
// cid1 is not replaced
final String actual2 = a.resolveInlineImage(text1 + text2, msg2, 0);
assertEquals("only one of two content id is resolved",
text1 + expected2, actual2);
// message with cid1 and cid2
final Message msg3 = new MessageBuilder()
.setBody(new MultipartBuilder("multipart/related")
.addBodyPart(MessageTestUtils.textPart("text/html", text2 + text1))
.addBodyPart(MessageTestUtils.imagePart("image/jpeg", cid1, aid1, store))
.addBodyPart(MessageTestUtils.imagePart("image/gif", cid2, aid2, store))
.build())
.build();
// cid1 and cid2 are replaced
final String actual3 = a.resolveInlineImage(text2 + text1, msg3, 0);
assertEquals("two content ids are resolved correctly",
expected2 + expected1, actual3);
// message with many cids and normal attachments
final Message msg4 = new MessageBuilder()
.setBody(new MultipartBuilder("multipart/mixed")
.addBodyPart(MessageTestUtils.imagePart("image/jpeg", null, 30, store))
.addBodyPart(MessageTestUtils.imagePart("application/pdf", cid1, aid1, store))
.addBodyPart(new MultipartBuilder("multipart/related")
.addBodyPart(MessageTestUtils.textPart("text/html", text2 + text1))
.addBodyPart(MessageTestUtils.imagePart("image/jpg", cid1, aid1, store))
.addBodyPart(MessageTestUtils.imagePart("image/gif", cid2, aid2, store))
.buildBodyPart())
.addBodyPart(MessageTestUtils.imagePart("application/pdf", cid2, aid2, store))
.build())
.build();
// cid1 and cid2 are replaced
final String actual4 = a.resolveInlineImage(text2 + text1, msg4, 0);
assertEquals("two content ids in deep multipart level are resolved",
expected2 + expected1, actual4);
}
/**
* Mock Messaging controller, so we can drive its callbacks. This probably should be

View File

@ -43,9 +43,27 @@ public class AddressUnitTests extends AndroidTestCase {
}
/**
* TODO: test parse()
* TODO: more in-depth tests for parse()
*/
/**
* Simple quick checks of empty-input edge conditions for parse()
*
* NOTE: This is not a claim that these edge cases are "correct", only to maintain consistent
* behavior while I am changing some of the code in the function under test.
*/
public void testEmptyParse() {
Address[] result;
// null input => empty array
result = Address.parse(null);
assertTrue("parsing null address", result != null && result.length == 0);
// empty string input => empty array
result = Address.parse("");
assertTrue("parsing zero-length", result != null && result.length == 0);
}
/**
* TODO: test toString() (single & list)
*/
@ -75,6 +93,43 @@ public class AddressUnitTests extends AndroidTestCase {
}
/**
* TODO: test pack() and unpack()
* TODO: more in-depth tests for pack() and unpack()
*/
/**
* Simple quick checks of empty-input edge conditions for pack()
*
* NOTE: This is not a claim that these edge cases are "correct", only to maintain consistent
* behavior while I am changing some of the code in the function under test.
*/
public void testEmptyPack() {
String result;
// null input => null string
result = Address.pack(null);
assertNull("packing null", result);
// zero-length input => empty string
result = Address.pack(new Address[] { });
assertEquals("packing empty array", "", result);
}
/**
* Simple quick checks of empty-input edge conditions for unpack()
*
* NOTE: This is not a claim that these edge cases are "correct", only to maintain consistent
* behavior while I am changing some of the code in the function under test.
*/
public void testEmptyUnpack() {
Address[] result;
// null input => empty array
result = Address.unpack(null);
assertTrue("unpacking null address", result != null && result.length == 0);
// empty string input => empty array
result = Address.unpack("");
assertTrue("unpacking zero-length", result != null && result.length == 0);
}
}

View File

@ -0,0 +1,302 @@
/*
* 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;
import com.android.email.Account;
import com.android.email.mail.internet.BinaryTempFileBody;
import com.android.email.mail.internet.MimeBodyPart;
import com.android.email.mail.internet.MimeHeader;
import com.android.email.mail.internet.MimeMessage;
import com.android.email.mail.internet.MimeMultipart;
import com.android.email.mail.internet.TextBody;
import com.android.email.mail.store.LocalStore;
import com.android.email.provider.AttachmentProvider;
import android.net.Uri;
import java.io.IOException;
import java.util.ArrayList;
/**
* Utility class makes it easier for developer to build mail message objects.
* <p>
* Typical usage of these helper functions and builder objects are as follows.
* <p>
* <pre>
* String text2 = new TextBuilder("<html>").text("<head></head>")
* .text("<body>").cidImg("contetid@domain").text("</body>").build("</html");
* String text2 = new TextBuilder("<html>").text("<head></head>")
* .text("<body>").uriImg(contentUri).text("</body>").build("</html");
* Message msg = new MessageBuilder()
* .setBody(new MultipartBuilder("multipart/mixed")
* .addBodyPart(MessageTestUtils.imagePart("image/jpeg", null, 30, store))
* .addBodyPart(MessageTestUtils.imagePart("application/pdf", cid1, aid1, store))
* .addBodyPart(new MultipartBuilder("multipart/related")
* .addBodyPart(MessageTestUtils.textPart("text/html", text2 + text1))
* .addBodyPart(MessageTestUtils.imagePart("image/jpg", cid1, aid1, store))
* .addBodyPart(MessageTestUtils.imagePart("image/gif", cid2, aid2, store))
* .buildBodyPart())
* .addBodyPart(MessageTestUtils.imagePart("application/pdf", cid2, aid2, store))
* .build())
* .build();
* </pre>
*/
public class MessageTestUtils {
/**
* Generate AttachmentProvider content URI from attachment ID and Account.
*
* @param attachmentId attachment id
* @param account Account object
* @return AttachmentProvider content URI
*/
public static Uri contentUri(long attachmentId, Account account) {
return AttachmentProvider.getAttachmentUri(account, attachmentId);
}
/**
* Create simple MimeBodyPart.
*
* @param mimeType MIME type of body part
* @param contentId content-id header value (optional - null for no header)
* @return MimeBodyPart object which body is null.
* @throws MessagingException
*/
public static BodyPart bodyPart(String mimeType, String contentId) throws MessagingException {
final MimeBodyPart bp = new MimeBodyPart(null, mimeType);
if (contentId != null) {
bp.setHeader(MimeHeader.HEADER_CONTENT_ID, contentId);
}
return bp;
}
/**
* Create MimeBodyPart with TextBody.
*
* @param mimeType MIME type of text
* @param text body text string
* @return MimeBodyPart object whose body is TextBody
* @throws MessagingException
*/
public static BodyPart textPart(String mimeType, String text) throws MessagingException {
final TextBody textBody = new TextBody(text);
final MimeBodyPart textPart = new MimeBodyPart(textBody);
textPart.setHeader(MimeHeader.HEADER_CONTENT_TYPE, mimeType);
return textPart;
}
/**
* Create attachment BodyPart with content-id.
*
* @param mimeType MIME type of image body
* @param contentId content-id header value (optional - null for no header)
* @param attachmentId attachment id of store
* @param store LocalStore which stores attachment
* @return LocalAttachmentBodyPart with content-id
* @throws MessagingException
* @throws IOException
*/
public static BodyPart imagePart(String mimeType, String contentId,
long attachmentId, LocalStore store) throws MessagingException, IOException {
final BinaryTempFileBody imageBody = new BinaryTempFileBody();
final LocalStore.LocalAttachmentBodyPart imagePart =
store.new LocalAttachmentBodyPart(imageBody, attachmentId);
imagePart.setHeader(MimeHeader.HEADER_CONTENT_TYPE, mimeType);
if (contentId != null) {
imagePart.setHeader(MimeHeader.HEADER_CONTENT_ID, contentId);
}
return imagePart;
}
/**
* Builder class for Multipart.
*
* This builder object accepts any number of BodyParts and then can produce
* Multipart or BodyPart which contains accepted BodyParts. Usually combined with other
* builder object and helper method.
*/
public static class MultipartBuilder {
private final String mContentType;
private final ArrayList<BodyPart> mParts = new ArrayList<BodyPart>();
/**
* Create builder object with MIME type and dummy boundary string.
*
* @param mimeType MIME type of this Multipart
*/
public MultipartBuilder(String mimeType) {
this(mimeType, "this_is_boundary");
}
/**
* Create builder object with MIME type and boundary string.
*
* @param mimeType MIME type of this Multipart
* @param boundary boundary string
*/
public MultipartBuilder(String mimeType, String boundary) {
mContentType = mimeType + "; boundary=" + boundary;
}
/**
* Modifier method to add BodyPart to intended Multipart.
*
* @param bodyPart BodyPart to be added
* @return builder object itself
*/
public MultipartBuilder addBodyPart(final BodyPart bodyPart) {
mParts.add(bodyPart);
return this;
}
/**
* Build method to create Multipart.
*
* @return intended Multipart object
* @throws MessagingException
*/
public Multipart build() throws MessagingException {
final MimeMultipart mp = new MimeMultipart(mContentType);
for (BodyPart p : mParts) {
mp.addBodyPart(p);
}
return mp;
}
/**
* Build method to create BodyPart that contains this "Multipart"
* @return BodyPart whose body is intended Multipart.
* @throws MessagingException
*/
public BodyPart buildBodyPart() throws MessagingException {
final BodyPart bp = new MimeBodyPart();
bp.setBody(this.build());
return bp;
}
}
/**
* Builder class for Message
*
* This builder object accepts Body and then can produce Message object.
* Usually combined with other builder object and helper method.
*/
public static class MessageBuilder {
private Body mBody;
/**
* Create Builder object.
*/
public MessageBuilder() {
}
/**
* Modifier method to set Body.
*
* @param body Body of intended Message
* @return builder object itself
*/
public MessageBuilder setBody(final Body body) {
mBody = body;
return this;
}
/**
* Build method to create Message.
*
* @return intended Message object
* @throws MessagingException
*/
public Message build() throws MessagingException {
final MimeMessage msg = new MimeMessage();
if (mBody == null) {
throw new MessagingException("body is not specified");
}
msg.setBody(mBody);
return msg;
}
}
/**
* Builder class for simple HTML String.
* This builder object accepts some type of object or and string and then create String object.
* Usually combined with other builder object and helper method.
*/
public static class TextBuilder {
final StringBuilder mBuilder = new StringBuilder();
/**
* Create builder with preamble string
* @param preamble
*/
public TextBuilder(String preamble) {
mBuilder.append(preamble);
}
/**
* Modifier method to add img tag that has cid: src attribute.
* @param contentId content id string
* @return builder object itself
*/
public TextBuilder addCidImg(String contentId) {
return addTag("img", "SRC", "cid:" + contentId);
}
/**
* Modifier method to add img tag that has content:// src attribute.
* @param contentUri content uri object
* @return builder object itself
*/
public TextBuilder addUidImg(Uri contentUri) {
return addTag("img", "src", contentUri.toString());
}
/**
* Modifier method to add tag with specified attribute and value.
*
* @param tag tag name
* @param attribute attribute name
* @param value attribute value
* @return builder object itself
*/
public TextBuilder addTag(String tag, String attribute, String value) {
return addText(String.format("<%s %s=\"%s\">", tag, attribute, value));
}
/**
* Modifier method to add simple string.
* @param text string to add
* @return builder object itself
*/
public TextBuilder addText(String text) {
mBuilder.append(text);
return this;
}
/**
* Build method to create intended String
* @param epilogue string to add to the end
* @return intended String
*/
public String build(String epilogue) {
mBuilder.append(epilogue);
return mBuilder.toString();
}
}
}

View File

@ -0,0 +1,54 @@
/*
* 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 com.android.email.mail.MessagingException;
import com.android.email.mail.internet.MimeBodyPart;
import com.android.email.mail.internet.MimeHeader;
import junit.framework.TestCase;
import android.test.suitebuilder.annotation.SmallTest;
/**
* This is a series of unit tests for the MimeBodyPart class. These tests must be locally
* complete - no server(s) required.
*/
@SmallTest
public class MimeBodyPartTest extends TestCase {
// TODO: more tests.
/*
* Confirm getContentID() correctly works.
*/
public void testGetContentId() throws MessagingException {
MimeBodyPart bp = new MimeBodyPart();
// no content-id
assertNull(bp.getContentId());
// normal case
final String cid1 = "cid.1@android.com";
bp.setHeader(MimeHeader.HEADER_CONTENT_ID, cid1);
assertEquals(cid1, bp.getContentId());
// surrounded by optional bracket
bp.setHeader(MimeHeader.HEADER_CONTENT_ID, "<" + cid1 + ">");
assertEquals(cid1, bp.getContentId());
}
}

View File

@ -17,6 +17,8 @@
package com.android.email.mail.internet;
import com.android.email.mail.MessagingException;
import com.android.email.mail.internet.MimeHeader;
import com.android.email.mail.internet.MimeMessage;
import android.test.suitebuilder.annotation.SmallTest;
@ -95,4 +97,23 @@ public class MimeMessageTest extends TestCase {
message2.setMessageId(testId2);
assertEquals("set and get Message-ID", testId2, message2.getMessageId());
}
/*
* Confirm getContentID() correctly works.
*/
public void testGetContentId() throws MessagingException {
MimeMessage message = new MimeMessage();
// no content-id
assertNull(message.getContentId());
// normal case
final String cid1 = "cid.1@android.com";
message.setHeader(MimeHeader.HEADER_CONTENT_ID, cid1);
assertEquals(cid1, message.getContentId());
// surrounded by optional bracket
message.setHeader(MimeHeader.HEADER_CONTENT_ID, "<" + cid1 + ">");
assertEquals(cid1, message.getContentId());
}
}

View File

@ -16,7 +16,13 @@
package com.android.email.mail.internet;
import com.android.email.mail.BodyPart;
import com.android.email.mail.MessageTestUtils;
import com.android.email.mail.Message;
import com.android.email.mail.MessagingException;
import com.android.email.mail.Part;
import com.android.email.mail.MessageTestUtils.MessageBuilder;
import com.android.email.mail.MessageTestUtils.MultipartBuilder;
import android.test.suitebuilder.annotation.SmallTest;
@ -35,7 +41,46 @@ public class MimeUtilityTest extends TestCase {
// TODO: tests for foldAndEncode(String s)
// TODO: tests for getHeaderParameter(String header, String name)
// TODO: tests for findFirstPartByMimeType(Part part, String mimeType)
// TODO: tests for findPartByContentId(Part part, String contentId) throws Exception
/** Tests for findPartByContentId(Part part, String contentId) */
public void testFindPartByContentIdTestCase() throws MessagingException, Exception {
final String cid1 = "cid.1@android.com";
final Part cid1bp = MessageTestUtils.bodyPart("image/gif", cid1);
final String cid2 = "cid.2@android.com";
final Part cid2bp = MessageTestUtils.bodyPart("image/gif", "<" + cid2 + ">");
final Message msg1 = new MessageBuilder()
.setBody(new MultipartBuilder("multipart/related")
.addBodyPart(MessageTestUtils.bodyPart("text/html", null))
.addBodyPart((BodyPart)cid1bp)
.build())
.build();
// found cid1 part
final Part actual1_1 = MimeUtility.findPartByContentId(msg1, cid1);
assertEquals("could not found expected content-id part", cid1bp, actual1_1);
final Message msg2 = new MessageBuilder()
.setBody(new MultipartBuilder("multipart/mixed")
.addBodyPart(MessageTestUtils.bodyPart("image/tiff", "cid.4@android.com"))
.addBodyPart(new MultipartBuilder("multipart/related")
.addBodyPart(new MultipartBuilder("multipart/alternative")
.addBodyPart(MessageTestUtils.bodyPart("text/plain", null))
.addBodyPart(MessageTestUtils.bodyPart("text/html", null))
.buildBodyPart())
.addBodyPart((BodyPart)cid1bp)
.buildBodyPart())
.addBodyPart(MessageTestUtils.bodyPart("image/gif", "cid.3@android.com"))
.addBodyPart((BodyPart)cid2bp)
.build())
.build();
// found cid1 part
final Part actual2_1 = MimeUtility.findPartByContentId(msg2, cid1);
assertEquals("found part from related multipart", cid1bp, actual2_1);
// found cid2 part
final Part actual2_2 = MimeUtility.findPartByContentId(msg2, cid2);
assertEquals("found part from mixed multipart", cid2bp, actual2_2);
}
/** Tests for getTextFromPart(Part part) */
public void testGetTextFromPartContentTypeCase() throws MessagingException {
@ -111,4 +156,5 @@ public class MimeUtilityTest extends TestCase {
// TODO: tests for decodeBody(InputStream in, String contentTransferEncoding)
// TODO: tests for collectParts(Part part, ArrayList<Part> viewables, ArrayList<Part> attachments)
}

View File

@ -18,7 +18,6 @@ package com.android.email.mail.store;
import com.android.email.mail.Address;
import com.android.email.mail.Message;
import com.android.email.mail.MessageRetrievalListener;
import com.android.email.mail.MessagingException;
import com.android.email.mail.Folder.OpenMode;
import com.android.email.mail.Message.RecipientType;
@ -26,13 +25,15 @@ import com.android.email.mail.internet.BinaryTempFileBody;
import com.android.email.mail.internet.MimeMessage;
import com.android.email.mail.internet.TextBody;
import android.app.Application;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.test.AndroidTestCase;
import android.test.mock.MockApplication;
import android.test.suitebuilder.annotation.SmallTest;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
/**
* This is a series of unit tests for the LocalStore class.
@ -53,6 +54,7 @@ public class LocalStoreUnitTests extends AndroidTestCase {
private String mLocalStoreUri = null;
private LocalStore mStore = null;
private LocalStore.LocalFolder mFolder = null;
private File mCacheDir;
/**
* Setup code. We generate a lightweight LocalStore and LocalStore.LocalFolder.
@ -69,7 +71,8 @@ public class LocalStoreUnitTests extends AndroidTestCase {
mFolder = (LocalStore.LocalFolder) mStore.getFolder("TEST");
// This is needed for parsing mime messages
BinaryTempFileBody.setTempDirectory(this.getContext().getCacheDir());
mCacheDir = getContext().getCacheDir();
BinaryTempFileBody.setTempDirectory(mCacheDir);
}
/**
@ -199,6 +202,200 @@ public class LocalStoreUnitTests extends AndroidTestCase {
return message;
}
/**
* Tests for database version.
*/
public void testDbVersion() throws MessagingException, URISyntaxException {
final LocalStore store = new LocalStore(mLocalStoreUri, getContext());
final URI uri = new URI(mLocalStoreUri);
final String dbPath = uri.getPath();
final SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(dbPath, null);
// database version should be latest.
assertEquals("database version should be latest", 20, db.getVersion());
db.close();
}
/**
* Helper function convert Cursor data to ContentValues
*/
private ContentValues cursorToContentValues(Cursor c, String[] schema) {
if (c.getColumnCount() != schema.length) {
throw new IndexOutOfBoundsException("schema length is not mach with cursor columns");
}
final ContentValues cv = new ContentValues();
for (int i = 0, count = c.getColumnCount(); i < count; ++i) {
final String key = c.getColumnName(i);
final String type = schema[i];
if (type == "text") {
cv.put(key, c.getString(i));
} else if (type == "integer" || type == "primary") {
cv.put(key, c.getLong(i));
} else if (type == "numeric" || type == "real") {
cv.put(key, c.getDouble(i));
} else if (type == "blob") {
cv.put(key, c.getBlob(i));
} else {
throw new IllegalArgumentException("unsupported type at index " + i);
}
}
return cv;
}
/**
* Tests for database upgrade from version 18 to version 20.
*/
public void testDbUpgrade18To20() throws MessagingException, URISyntaxException {
final URI uri = new URI(mLocalStoreUri);
final String dbPath = uri.getPath();
SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(dbPath, null);
// create sample version 18 db tables
createSampleDb(db, 18);
// 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));
// sample attachment data and expected data
final ContentValues initialAttachment = new ContentValues();
initialAttachment.put("message_id", (long) 4); // message_id type integer == Long
initialAttachment.put("mime_type", (String) "a"); // mime_type type text == String
final ContentValues expectedAttachment = new ContentValues(initialAttachment);
expectedAttachment.put("id", db.insert("attachments", null, initialAttachment));
db.close();
// upgrade database 18 to 20
new LocalStore(mLocalStoreUri, getContext());
// added message_id column should be initialized as null
expectedMessage.put("message_id", (String) null); // message_id type text == String
// added content_id column should be initialized as null
expectedAttachment.put("content_id", (String) null); // content_id type text == String
// database should be upgraded
db = SQLiteDatabase.openOrCreateDatabase(dbPath, null);
assertEquals("database should be upgraded", 20, db.getVersion());
Cursor c;
// 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();
// check attachment table
c = db.query("attachments",
new String[] { "id", "message_id", "mime_type", "content_id" },
null, null, null, null, null);
// check if data is available
assertTrue("attachments table should have one data", c.moveToNext());
// check if data are expected
final ContentValues actualAttachment = cursorToContentValues(c,
new String[] { "primary", "integer", "text", "text" });
assertEquals("attachment table cursor does not have expected values",
expectedAttachment, actualAttachment);
c.close();
db.close();
}
/**
* Tests for database upgrade from version 19 to version 20.
*/
public void testDbUpgrade19To20() throws MessagingException, URISyntaxException {
final URI uri = new URI(mLocalStoreUri);
final String dbPath = uri.getPath();
SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(dbPath, null);
// create minimu version 18 db tables
createSampleDb(db, 19);
// 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 integer == Long
initialMessage.put("message_id", (String) "x"); // message_id text == String
final ContentValues expectedMessage = new ContentValues(initialMessage);
expectedMessage.put("id", db.insert("messages", null, initialMessage));
// sample attachment data and expected data
final ContentValues initialAttachment = new ContentValues();
initialAttachment.put("message_id", (long) 4); // message_id type integer == Long
initialAttachment.put("mime_type", (String) "a"); // mime_type type text == String
final ContentValues expectedAttachment = new ContentValues(initialAttachment);
expectedAttachment.put("id", db.insert("attachments", null, initialAttachment));
db.close();
// upgrade database 19 to 20
new LocalStore(mLocalStoreUri, getContext());
// added content_id column should be initialized as null
expectedAttachment.put("content_id", (String) null); // content_id type text == String
// database should be upgraded
db = SQLiteDatabase.openOrCreateDatabase(dbPath, null);
assertEquals(20, db.getVersion());
Cursor c;
// 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("attachments 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);
// check attachment table
c = db.query("attachments",
new String[] { "id", "message_id", "mime_type", "content_id" },
null, null, null, null, null);
// check if data is available
assertTrue("attachments table should have one data", c.moveToNext());
// check if data are expected
final ContentValues actualAttachment = cursorToContentValues(c,
new String[] { "primary", "integer", "text", "text" });
assertEquals("attachment table cursor does not have expected values",
expectedAttachment, actualAttachment);
db.close();
}
private static void createSampleDb(SQLiteDatabase db, int version) {
db.execSQL("DROP TABLE IF EXISTS messages");
db.execSQL("CREATE TABLE messages (id INTEGER PRIMARY KEY, folder_id INTEGER, " +
"uid TEXT, subject TEXT, date INTEGER, flags TEXT, sender_list TEXT, " +
"to_list TEXT, cc_list TEXT, bcc_list TEXT, reply_to_list TEXT, " +
"html_content TEXT, text_content TEXT, attachment_count INTEGER, " +
"internal_date INTEGER" +
((version >= 19) ? ", message_id TEXT" : "") +
")");
db.execSQL("DROP TABLE IF EXISTS attachments");
db.execSQL("CREATE TABLE attachments (id INTEGER PRIMARY KEY, message_id INTEGER," +
"store_data TEXT, content_uri TEXT, size INTEGER, name TEXT," +
"mime_type TEXT" +
((version >= 20) ? ", content_id" : "") +
")");
db.setVersion(version);
}
}

View File

@ -27,6 +27,7 @@ import com.android.email.mail.Folder.FolderType;
import com.android.email.mail.Folder.OpenMode;
import com.android.email.mail.Message.RecipientType;
import com.android.email.mail.internet.BinaryTempFileBody;
import com.android.email.mail.internet.MimeMessage;
import com.android.email.mail.transport.MockTransport;
import android.test.AndroidTestCase;
@ -529,6 +530,16 @@ public class Pop3StoreUnitTests extends AndroidTestCase {
fp.add(FetchProfile.Item.ENVELOPE);
mFolder.fetch(messages, fp, null);
assertEquals(PER_MESSAGE_SIZE, messages[0].getSize());
// A side effect of how messages work is that if you get fields that are empty,
// then empty arrays are written back into the parsed header fields (e.g. mTo, mFrom). The
// standard message parser needs to clear these before parsing. Make sure that this
// is happening. (This doesn't affect IMAP, which reads the headers directly via
// IMAP evelopes.)
MimeMessage message = (MimeMessage) messages[0];
message.getRecipients(RecipientType.TO);
message.getRecipients(RecipientType.CC);
message.getRecipients(RecipientType.BCC);
// now try fetching the message
setupSingleMessage(mockTransport, 1, false);
@ -586,6 +597,20 @@ public class Pop3StoreUnitTests extends AndroidTestCase {
assertEquals(1, from.length);
assertEquals("Jones@Registry.Org", from[0].getAddress());
assertNull(from[0].getPersonal());
// check Cc:
Address[] cc = message.getRecipients(RecipientType.CC);
assertNotNull(cc);
assertEquals(1, cc.length);
assertEquals("Chris@Registry.Org", cc[0].getAddress());
assertNull(cc[0].getPersonal());
// check Reply-To:
Address[] replyto = message.getReplyTo();
assertNotNull(replyto);
assertEquals(1, replyto.length);
assertEquals("Roger@Registry.Org", replyto[0].getAddress());
assertNull(replyto[0].getPersonal());
// TODO date
@ -649,6 +674,10 @@ public class Pop3StoreUnitTests extends AndroidTestCase {
* Date: 26 Aug 76 1429 EDT
* From: Jones@Registry.Org
* To: Smith@Registry.Org
*
* We'll add the following fields to support additional tests:
* Cc: Chris@Registry.Org
* Reply-To: Roger@Registry.Org
*
* @param transport the mock transport to preload
* @param msgNum the message number to expect and return
@ -659,6 +688,8 @@ public class Pop3StoreUnitTests extends AndroidTestCase {
transport.expect(null, "Date: 26 Aug 76 1429 EDT");
transport.expect(null, "From: Jones@Registry.Org");
transport.expect(null, "To: Smith@Registry.Org");
transport.expect(null, "CC: Chris@Registry.Org");
transport.expect(null, "Reply-To: Roger@Registry.Org");
transport.expect(null, "");
transport.expect(null, ".");
}