auto import from //branches/cupcake_rel/...@140373
This commit is contained in:
parent
3b85e2c2b5
commit
3469902379
|
@ -84,6 +84,10 @@
|
||||||
<string name="message_deleted_toast">"Zpráva byla smazána."</string>
|
<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_discarded_toast">"Zpráva byla zrušena."</string>
|
||||||
<string name="message_saved_toast">"Zpráva byla uložena jako koncept."</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_title">"Nastavit e-mail"</string>
|
||||||
<string name="account_setup_basics_instructions">"Zadejte e-mailovou adresu účtu:"</string>
|
<string name="account_setup_basics_instructions">"Zadejte e-mailovou adresu účtu:"</string>
|
||||||
<string name="account_setup_basics_email_hint">"E-mailová adresa"</string>
|
<string name="account_setup_basics_email_hint">"E-mailová adresa"</string>
|
||||||
|
|
|
@ -84,6 +84,10 @@
|
||||||
<string name="message_deleted_toast">"Nachricht gelöscht"</string>
|
<string name="message_deleted_toast">"Nachricht gelöscht"</string>
|
||||||
<string name="message_discarded_toast">"Nachricht gelöscht"</string>
|
<string name="message_discarded_toast">"Nachricht gelöscht"</string>
|
||||||
<string name="message_saved_toast">"Nachricht als Entwurf gespeichert"</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_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_instructions">"Geben Sie Ihre im Konto gespeicherte E-Mail-Adresse ein:"</string>
|
||||||
<string name="account_setup_basics_email_hint">"E-Mail-Adresse"</string>
|
<string name="account_setup_basics_email_hint">"E-Mail-Adresse"</string>
|
||||||
|
|
|
@ -84,6 +84,10 @@
|
||||||
<string name="message_deleted_toast">"Mensaje suprimido"</string>
|
<string name="message_deleted_toast">"Mensaje suprimido"</string>
|
||||||
<string name="message_discarded_toast">"Mensaje descartado"</string>
|
<string name="message_discarded_toast">"Mensaje descartado"</string>
|
||||||
<string name="message_saved_toast">"Mensaje guardado como borrador"</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_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_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>
|
<string name="account_setup_basics_email_hint">"Dirección de correo electrónico"</string>
|
||||||
|
|
|
@ -84,6 +84,10 @@
|
||||||
<string name="message_deleted_toast">"Message supprimé."</string>
|
<string name="message_deleted_toast">"Message supprimé."</string>
|
||||||
<string name="message_discarded_toast">"Message supprimé."</string>
|
<string name="message_discarded_toast">"Message supprimé."</string>
|
||||||
<string name="message_saved_toast">"Message enregistré comme brouillon."</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_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_instructions">"Saisissez l\'adresse e-mail de votre compte :"</string>
|
||||||
<string name="account_setup_basics_email_hint">"Adresse e-mail"</string>
|
<string name="account_setup_basics_email_hint">"Adresse e-mail"</string>
|
||||||
|
|
|
@ -84,6 +84,10 @@
|
||||||
<string name="message_deleted_toast">"Messaggio eliminato."</string>
|
<string name="message_deleted_toast">"Messaggio eliminato."</string>
|
||||||
<string name="message_discarded_toast">"Messaggio eliminato."</string>
|
<string name="message_discarded_toast">"Messaggio eliminato."</string>
|
||||||
<string name="message_saved_toast">"Messaggio salvato come bozza."</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_title">"Imposta email"</string>
|
||||||
<string name="account_setup_basics_instructions">"Digita l\'indirizzo email del tuo account:"</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>
|
<string name="account_setup_basics_email_hint">"Indirizzo email"</string>
|
||||||
|
|
|
@ -84,6 +84,10 @@
|
||||||
<string name="message_deleted_toast">"メッセージを削除しました。"</string>
|
<string name="message_deleted_toast">"メッセージを削除しました。"</string>
|
||||||
<string name="message_discarded_toast">"メッセージを破棄しました。"</string>
|
<string name="message_discarded_toast">"メッセージを破棄しました。"</string>
|
||||||
<string name="message_saved_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_title">"メールアカウントの登録"</string>
|
||||||
<string name="account_setup_basics_instructions">"メールのアカウント情報を入力:"</string>
|
<string name="account_setup_basics_instructions">"メールのアカウント情報を入力:"</string>
|
||||||
<string name="account_setup_basics_email_hint">"メールアドレス"</string>
|
<string name="account_setup_basics_email_hint">"メールアドレス"</string>
|
||||||
|
|
|
@ -84,6 +84,10 @@
|
||||||
<string name="message_deleted_toast">"메일이 삭제되었습니다."</string>
|
<string name="message_deleted_toast">"메일이 삭제되었습니다."</string>
|
||||||
<string name="message_discarded_toast">"메일이 삭제되었습니다."</string>
|
<string name="message_discarded_toast">"메일이 삭제되었습니다."</string>
|
||||||
<string name="message_saved_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_title">"이메일 설정"</string>
|
||||||
<string name="account_setup_basics_instructions">"계정 이메일 주소 입력:"</string>
|
<string name="account_setup_basics_instructions">"계정 이메일 주소 입력:"</string>
|
||||||
<string name="account_setup_basics_email_hint">"이메일 주소"</string>
|
<string name="account_setup_basics_email_hint">"이메일 주소"</string>
|
||||||
|
|
|
@ -84,6 +84,10 @@
|
||||||
<string name="message_deleted_toast">"Meldingen ble slettet."</string>
|
<string name="message_deleted_toast">"Meldingen ble slettet."</string>
|
||||||
<string name="message_discarded_toast">"Meldingen ble forkastet."</string>
|
<string name="message_discarded_toast">"Meldingen ble forkastet."</string>
|
||||||
<string name="message_saved_toast">"Meldingen ble lagret som utkast."</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_title">"Sett opp e-post"</string>
|
||||||
<string name="account_setup_basics_instructions">"Skriv e-postadressen til kontoen din:"</string>
|
<string name="account_setup_basics_instructions">"Skriv e-postadressen til kontoen din:"</string>
|
||||||
<string name="account_setup_basics_email_hint">"E-postadresse"</string>
|
<string name="account_setup_basics_email_hint">"E-postadresse"</string>
|
||||||
|
|
|
@ -84,6 +84,10 @@
|
||||||
<string name="message_deleted_toast">"Bericht verwijderd."</string>
|
<string name="message_deleted_toast">"Bericht verwijderd."</string>
|
||||||
<string name="message_discarded_toast">"Bericht wordt verwijderd"</string>
|
<string name="message_discarded_toast">"Bericht wordt verwijderd"</string>
|
||||||
<string name="message_saved_toast">"Bericht opgeslagen als concept."</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_title">"E-mail instellen"</string>
|
||||||
<string name="account_setup_basics_instructions">"Typ het e-mailadres van je account:"</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>
|
<string name="account_setup_basics_email_hint">"E-mailadres"</string>
|
||||||
|
|
|
@ -84,6 +84,10 @@
|
||||||
<string name="message_deleted_toast">"Wiadomość została usunięta."</string>
|
<string name="message_deleted_toast">"Wiadomość została usunięta."</string>
|
||||||
<string name="message_discarded_toast">"Wiadomość została odrzucona."</string>
|
<string name="message_discarded_toast">"Wiadomość została odrzucona."</string>
|
||||||
<string name="message_saved_toast">"Wiadomość została zapisana jako wersja robocza."</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_title">"Skonfiguruj konto e-mail"</string>
|
||||||
<string name="account_setup_basics_instructions">"Podaj adres e-mail swojego konta:"</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>
|
<string name="account_setup_basics_email_hint">"Adres e-mail"</string>
|
||||||
|
|
|
@ -84,6 +84,10 @@
|
||||||
<string name="message_deleted_toast">"Письмо удалено."</string>
|
<string name="message_deleted_toast">"Письмо удалено."</string>
|
||||||
<string name="message_discarded_toast">"Письмо не сохранено."</string>
|
<string name="message_discarded_toast">"Письмо не сохранено."</string>
|
||||||
<string name="message_saved_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_title">"Настройка электронной почты"</string>
|
||||||
<string name="account_setup_basics_instructions">"Укажите почтовый адрес своего аккаунта:"</string>
|
<string name="account_setup_basics_instructions">"Укажите почтовый адрес своего аккаунта:"</string>
|
||||||
<string name="account_setup_basics_email_hint">"Адрес электронной почты"</string>
|
<string name="account_setup_basics_email_hint">"Адрес электронной почты"</string>
|
||||||
|
|
|
@ -84,6 +84,10 @@
|
||||||
<string name="message_deleted_toast">"邮件已删除。"</string>
|
<string name="message_deleted_toast">"邮件已删除。"</string>
|
||||||
<string name="message_discarded_toast">"邮件已取消。"</string>
|
<string name="message_discarded_toast">"邮件已取消。"</string>
|
||||||
<string name="message_saved_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_title">"设置电子邮件"</string>
|
||||||
<string name="account_setup_basics_instructions">"键入您的帐户电子邮件地址:"</string>
|
<string name="account_setup_basics_instructions">"键入您的帐户电子邮件地址:"</string>
|
||||||
<string name="account_setup_basics_email_hint">"电子邮件地址"</string>
|
<string name="account_setup_basics_email_hint">"电子邮件地址"</string>
|
||||||
|
|
|
@ -84,6 +84,10 @@
|
||||||
<string name="message_deleted_toast">"已刪除郵件。"</string>
|
<string name="message_deleted_toast">"已刪除郵件。"</string>
|
||||||
<string name="message_discarded_toast">"已捨棄郵件。"</string>
|
<string name="message_discarded_toast">"已捨棄郵件。"</string>
|
||||||
<string name="message_saved_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_title">"設定電子郵件"</string>
|
||||||
<string name="account_setup_basics_instructions">"請輸入您帳戶的電子郵件地址:"</string>
|
<string name="account_setup_basics_instructions">"請輸入您帳戶的電子郵件地址:"</string>
|
||||||
<string name="account_setup_basics_email_hint">"電子郵件地址"</string>
|
<string name="account_setup_basics_email_hint">"電子郵件地址"</string>
|
||||||
|
|
|
@ -75,9 +75,8 @@ import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Random;
|
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
public class MessageView extends Activity
|
public class MessageView extends Activity
|
||||||
implements OnClickListener {
|
implements OnClickListener {
|
||||||
|
@ -93,6 +92,9 @@ public class MessageView extends Activity
|
||||||
};
|
};
|
||||||
private static final int METHODS_STATUS_COLUMN = 1;
|
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 mSubjectView;
|
||||||
private TextView mFromView;
|
private TextView mFromView;
|
||||||
private TextView mDateView;
|
private TextView mDateView;
|
||||||
|
@ -451,6 +453,10 @@ public class MessageView extends Activity
|
||||||
Intent contactIntent = new Intent(Contacts.Intents.SHOW_OR_CREATE_CONTACT);
|
Intent contactIntent = new Intent(Contacts.Intents.SHOW_OR_CREATE_CONTACT);
|
||||||
contactIntent.setData(contactUri);
|
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
|
// Only provide personal name hint if we have one
|
||||||
String senderPersonal = senderEmail.getPersonal();
|
String senderPersonal = senderEmail.getPersonal();
|
||||||
if (senderPersonal != null) {
|
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 {
|
private void renderAttachments(Part part, int depth) throws MessagingException {
|
||||||
String contentType = MimeUtility.unfoldAndDecode(part.getContentType());
|
String contentType = MimeUtility.unfoldAndDecode(part.getContentType());
|
||||||
String name = MimeUtility.getHeaderParameter(contentType, "name");
|
String name = MimeUtility.getHeaderParameter(contentType, "name");
|
||||||
|
@ -875,7 +925,7 @@ public class MessageView extends Activity
|
||||||
if (part != null) {
|
if (part != null) {
|
||||||
String text = MimeUtility.getTextFromPart(part);
|
String text = MimeUtility.getTextFromPart(part);
|
||||||
if (part.getMimeType().equalsIgnoreCase("text/html")) {
|
if (part.getMimeType().equalsIgnoreCase("text/html")) {
|
||||||
text = text.replaceAll("cid:", "http://cid/");
|
text = resolveInlineImage(text, mMessage, 0);
|
||||||
} else {
|
} else {
|
||||||
/*
|
/*
|
||||||
* Linkify the plain text and convert it to HTML by replacing
|
* 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
|
* TODO consider how to get background images and a million other things
|
||||||
* get backgroung images and a million other things that HTML allows.
|
* that HTML allows.
|
||||||
*/
|
*/
|
||||||
if (text.contains("img")) {
|
// Check if text contains img tag.
|
||||||
|
if (IMG_TAG_START_REGEX.matcher(text).matches()) {
|
||||||
mHandler.showShowPictures(true);
|
mHandler.showShowPictures(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -73,10 +73,10 @@ public class Address {
|
||||||
* @return An array of 0 or more Addresses.
|
* @return An array of 0 or more Addresses.
|
||||||
*/
|
*/
|
||||||
public static Address[] parse(String addressList) {
|
public static Address[] parse(String addressList) {
|
||||||
ArrayList<Address> addresses = new ArrayList<Address>();
|
if (addressList == null || addressList.length() == 0) {
|
||||||
if (addressList == null) {
|
|
||||||
return new Address[] {};
|
return new Address[] {};
|
||||||
}
|
}
|
||||||
|
ArrayList<Address> addresses = new ArrayList<Address>();
|
||||||
try {
|
try {
|
||||||
MailboxList parsedList = AddressList.parse(addressList).flatten();
|
MailboxList parsedList = AddressList.parse(addressList).flatten();
|
||||||
for (int i = 0, count = parsedList.size(); i < count; i++) {
|
for (int i = 0, count = parsedList.size(); i < count; i++) {
|
||||||
|
@ -173,7 +173,7 @@ public class Address {
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public static Address[] unpack(String addressList) {
|
public static Address[] unpack(String addressList) {
|
||||||
if (addressList == null) {
|
if (addressList == null || addressList.length() == 0) {
|
||||||
return new Address[] { };
|
return new Address[] { };
|
||||||
}
|
}
|
||||||
ArrayList<Address> addresses = new ArrayList<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
|
* Packs an address list into a String that is very quick to read
|
||||||
* and parse. Packed lists can be unpacked with unpackAddressList()
|
* 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)]
|
* URLENCODE(address)[;URLENCODE(personal)]
|
||||||
* @param list
|
* @param list
|
||||||
* @return
|
* @return
|
||||||
|
@ -213,6 +213,8 @@ public class Address {
|
||||||
public static String pack(Address[] addresses) {
|
public static String pack(Address[] addresses) {
|
||||||
if (addresses == null) {
|
if (addresses == null) {
|
||||||
return null;
|
return null;
|
||||||
|
} else if (addresses.length == 0) {
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
StringBuffer sb = new StringBuffer();
|
StringBuffer sb = new StringBuffer();
|
||||||
for (int i = 0, count = addresses.length; i < count; i++) {
|
for (int i = 0, count = addresses.length; i < count; i++) {
|
||||||
|
|
|
@ -31,6 +31,8 @@ public interface Part {
|
||||||
public String getContentType() throws MessagingException;
|
public String getContentType() throws MessagingException;
|
||||||
|
|
||||||
public String getDisposition() throws MessagingException;
|
public String getDisposition() throws MessagingException;
|
||||||
|
|
||||||
|
public String getContentId() throws MessagingException;
|
||||||
|
|
||||||
public String[] getHeader(String name) throws MessagingException;
|
public String[] getHeader(String name) throws MessagingException;
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ import java.io.BufferedWriter;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.OutputStreamWriter;
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import com.android.email.mail.Body;
|
import com.android.email.mail.Body;
|
||||||
import com.android.email.mail.BodyPart;
|
import com.android.email.mail.BodyPart;
|
||||||
|
@ -34,6 +35,9 @@ public class MimeBodyPart extends BodyPart {
|
||||||
protected Body mBody;
|
protected Body mBody;
|
||||||
protected int mSize;
|
protected int mSize;
|
||||||
|
|
||||||
|
// regex that matches content id surrounded by "<>" optionally.
|
||||||
|
private static final Pattern REMOVE_OPTIONAL_BRACKETS = Pattern.compile("^<?([^>]+)>?$");
|
||||||
|
|
||||||
public MimeBodyPart() throws MessagingException {
|
public MimeBodyPart() throws MessagingException {
|
||||||
this(null);
|
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 {
|
public String getMimeType() throws MessagingException {
|
||||||
return MimeUtility.getHeaderParameter(getContentType(), null);
|
return MimeUtility.getHeaderParameter(getContentType(), null);
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,7 @@ public class MimeHeader {
|
||||||
public static final String HEADER_CONTENT_TYPE = "Content-Type";
|
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_TRANSFER_ENCODING = "Content-Transfer-Encoding";
|
||||||
public static final String HEADER_CONTENT_DISPOSITION = "Content-Disposition";
|
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()
|
* Fields that should be omitted when writing the header using writeTo()
|
||||||
|
|
|
@ -25,6 +25,7 @@ import java.text.SimpleDateFormat;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Stack;
|
import java.util.Stack;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import org.apache.james.mime4j.BodyDescriptor;
|
import org.apache.james.mime4j.BodyDescriptor;
|
||||||
import org.apache.james.mime4j.ContentHandler;
|
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.BodyPart;
|
||||||
import com.android.email.mail.Message;
|
import com.android.email.mail.Message;
|
||||||
import com.android.email.mail.MessagingException;
|
import com.android.email.mail.MessagingException;
|
||||||
|
import com.android.email.mail.Multipart;
|
||||||
import com.android.email.mail.Part;
|
import com.android.email.mail.Part;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -46,12 +48,17 @@ import com.android.email.mail.Part;
|
||||||
*/
|
*/
|
||||||
public class MimeMessage extends Message {
|
public class MimeMessage extends Message {
|
||||||
protected MimeHeader mHeader = new MimeHeader();
|
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[] mFrom;
|
||||||
protected Address[] mTo;
|
protected Address[] mTo;
|
||||||
protected Address[] mCc;
|
protected Address[] mCc;
|
||||||
protected Address[] mBcc;
|
protected Address[] mBcc;
|
||||||
protected Address[] mReplyTo;
|
protected Address[] mReplyTo;
|
||||||
protected Date mSentDate;
|
protected Date mSentDate;
|
||||||
|
|
||||||
// In MIME, en_US-like date format should be used. In other words "MMM" should be encoded to
|
// 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).
|
// "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
|
// 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 Body mBody;
|
||||||
protected int mSize;
|
protected int mSize;
|
||||||
|
|
||||||
|
// regex that matches content id surrounded by "<>" optionally.
|
||||||
|
private static final Pattern REMOVE_OPTIONAL_BRACKETS = Pattern.compile("^<?([^>]+)>?$");
|
||||||
|
|
||||||
public MimeMessage() {
|
public MimeMessage() {
|
||||||
/*
|
/*
|
||||||
* Every new messages gets a Message-ID
|
* Every new messages gets a Message-ID
|
||||||
|
@ -100,12 +110,16 @@ public class MimeMessage extends Message {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void parse(InputStream in) throws IOException, MessagingException {
|
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();
|
mHeader.clear();
|
||||||
mBody = null;
|
|
||||||
mBcc = null;
|
|
||||||
mTo = null;
|
|
||||||
mFrom = null;
|
mFrom = null;
|
||||||
|
mTo = null;
|
||||||
|
mCc = null;
|
||||||
|
mBcc = null;
|
||||||
|
mReplyTo = null;
|
||||||
mSentDate = null;
|
mSentDate = null;
|
||||||
|
mBody = null;
|
||||||
|
|
||||||
MimeStreamParser parser = new MimeStreamParser();
|
MimeStreamParser parser = new MimeStreamParser();
|
||||||
parser.setContentHandler(new MimeMessageBuilder());
|
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 {
|
public String getMimeType() throws MessagingException {
|
||||||
return MimeUtility.getHeaderParameter(getContentType(), null);
|
return MimeUtility.getHeaderParameter(getContentType(), null);
|
||||||
}
|
}
|
||||||
|
|
|
@ -126,13 +126,9 @@ public class MimeUtility {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
String[] header = part.getHeader("Content-ID");
|
String cid = part.getContentId();
|
||||||
if (header != null) {
|
if (contentId.equals(cid)) {
|
||||||
for (String s : header) {
|
return part;
|
||||||
if (s.equals(contentId)) {
|
|
||||||
return part;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,15 @@
|
||||||
|
|
||||||
package com.android.email.mail.store;
|
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.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
|
@ -24,14 +33,6 @@ import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Locale;
|
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 {
|
public class ImapResponseParser {
|
||||||
// DEBUG ONLY - Always check in as "false"
|
// DEBUG ONLY - Always check in as "false"
|
||||||
private static boolean DEBUG_LOG_RAW_STREAM = false;
|
private static boolean DEBUG_LOG_RAW_STREAM = false;
|
||||||
|
@ -43,11 +44,11 @@ public class ImapResponseParser {
|
||||||
PeekableInputStream mIn;
|
PeekableInputStream mIn;
|
||||||
InputStream mActiveLiteral;
|
InputStream mActiveLiteral;
|
||||||
|
|
||||||
public ImapResponseParser(PeekableInputStream in) {
|
public ImapResponseParser(InputStream in) {
|
||||||
if (DEBUG_LOG_RAW_STREAM && Config.LOGD && Email.DEBUG) {
|
if (DEBUG_LOG_RAW_STREAM && Config.LOGD && Email.DEBUG) {
|
||||||
in = new LoggingInputStream(in);
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -856,6 +856,7 @@ public class ImapStore extends Store {
|
||||||
if (bs.get(2) instanceof ImapList) {
|
if (bs.get(2) instanceof ImapList) {
|
||||||
bodyParams = bs.getList(2);
|
bodyParams = bs.getList(2);
|
||||||
}
|
}
|
||||||
|
String cid = bs.getString(3);
|
||||||
String encoding = bs.getString(5);
|
String encoding = bs.getString(5);
|
||||||
int size = bs.getNumber(6);
|
int size = bs.getNumber(6);
|
||||||
|
|
||||||
|
@ -941,6 +942,12 @@ public class ImapStore extends Store {
|
||||||
* to parse the body.
|
* to parse the body.
|
||||||
*/
|
*/
|
||||||
part.setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, encoding);
|
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) {
|
if (part instanceof ImapMessage) {
|
||||||
((ImapMessage) part).setSize(size);
|
((ImapMessage) part).setSize(size);
|
||||||
|
@ -1084,8 +1091,6 @@ public class ImapStore extends Store {
|
||||||
private int mNextCommandTag;
|
private int mNextCommandTag;
|
||||||
|
|
||||||
public void open() throws IOException, MessagingException {
|
public void open() throws IOException, MessagingException {
|
||||||
PeekableInputStream mIn;
|
|
||||||
|
|
||||||
if (mTransport != null && mTransport.isOpen()) {
|
if (mTransport != null && mTransport.isOpen()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1101,8 +1106,7 @@ public class ImapStore extends Store {
|
||||||
mTransport.open();
|
mTransport.open();
|
||||||
mTransport.setSoTimeout(MailTransport.SOCKET_READ_TIMEOUT);
|
mTransport.setSoTimeout(MailTransport.SOCKET_READ_TIMEOUT);
|
||||||
|
|
||||||
mIn = new PeekableInputStream(mTransport.getInputStream());
|
mParser = new ImapResponseParser(mTransport.getInputStream());
|
||||||
mParser = new ImapResponseParser(mIn);
|
|
||||||
|
|
||||||
// BANNER
|
// BANNER
|
||||||
mParser.readResponse();
|
mParser.readResponse();
|
||||||
|
@ -1119,8 +1123,7 @@ public class ImapStore extends Store {
|
||||||
|
|
||||||
mTransport.reopenTls();
|
mTransport.reopenTls();
|
||||||
mTransport.setSoTimeout(MailTransport.SOCKET_READ_TIMEOUT);
|
mTransport.setSoTimeout(MailTransport.SOCKET_READ_TIMEOUT);
|
||||||
mIn = new PeekableInputStream(mTransport.getInputStream());
|
mParser = new ImapResponseParser(mTransport.getInputStream());
|
||||||
mParser = new ImapResponseParser(mIn);
|
|
||||||
} else if (mTransport.getSecurity() ==
|
} else if (mTransport.getSecurity() ==
|
||||||
Transport.CONNECTION_SECURITY_TLS_REQUIRED) {
|
Transport.CONNECTION_SECURITY_TLS_REQUIRED) {
|
||||||
if (Config.LOGD && Email.DEBUG) {
|
if (Config.LOGD && Email.DEBUG) {
|
||||||
|
|
|
@ -76,10 +76,11 @@ public class LocalStore extends Store {
|
||||||
* ---------- ---------- -----
|
* ---------- ---------- -----
|
||||||
* 18 pre-1.0 Development versions. No upgrade path.
|
* 18 pre-1.0 Development versions. No upgrade path.
|
||||||
* 18 1.0, 1.1 1.0 Release version.
|
* 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 };
|
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);
|
mDb = SQLiteDatabase.openOrCreateDatabase(mPath, null);
|
||||||
int oldVersion = mDb.getVersion();
|
int oldVersion = mDb.getVersion();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO we should have more sophisticated way to upgrade database.
|
||||||
|
*/
|
||||||
if (oldVersion != DB_VERSION) {
|
if (oldVersion != DB_VERSION) {
|
||||||
if (Config.LOGV) {
|
if (Config.LOGV) {
|
||||||
Log.v(Email.LOG_TAG, String.format("Upgrading database from %d to %d",
|
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("DROP TABLE IF EXISTS attachments");
|
||||||
mDb.execSQL("CREATE TABLE attachments (id INTEGER PRIMARY KEY, message_id INTEGER,"
|
mDb.execSQL("CREATE TABLE attachments (id INTEGER PRIMARY KEY, message_id INTEGER,"
|
||||||
+ "store_data TEXT, content_uri TEXT, size INTEGER, name TEXT,"
|
+ "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("DROP TABLE IF EXISTS pending_commands");
|
||||||
mDb.execSQL("CREATE TABLE 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.execSQL("CREATE TRIGGER delete_message BEFORE DELETE ON messages BEGIN DELETE FROM attachments WHERE old.id = message_id; END;");
|
||||||
mDb.setVersion(DB_VERSION);
|
mDb.setVersion(DB_VERSION);
|
||||||
}
|
}
|
||||||
else if (oldVersion < 19) {
|
else {
|
||||||
/**
|
if (oldVersion < 19) {
|
||||||
* Upgrade 18 to 19: add message_id to messages table
|
/**
|
||||||
*/
|
* Upgrade 18 to 19: add message_id to messages table
|
||||||
mDb.execSQL("ALTER TABLE messages ADD COLUMN message_id TEXT;");
|
*/
|
||||||
mDb.setVersion(DB_VERSION);
|
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) {
|
if (mDb.getVersion() != DB_VERSION) {
|
||||||
|
@ -523,7 +537,8 @@ public class LocalStore extends Store {
|
||||||
"name",
|
"name",
|
||||||
"mime_type",
|
"mime_type",
|
||||||
"store_data",
|
"store_data",
|
||||||
"content_uri" },
|
"content_uri",
|
||||||
|
"content_id" },
|
||||||
"message_id = ?",
|
"message_id = ?",
|
||||||
new String[] { Long.toString(localMessage.mId) },
|
new String[] { Long.toString(localMessage.mId) },
|
||||||
null,
|
null,
|
||||||
|
@ -537,6 +552,7 @@ public class LocalStore extends Store {
|
||||||
String type = cursor.getString(3);
|
String type = cursor.getString(3);
|
||||||
String storeData = cursor.getString(4);
|
String storeData = cursor.getString(4);
|
||||||
String contentUri = cursor.getString(5);
|
String contentUri = cursor.getString(5);
|
||||||
|
String contentId = cursor.getString(6);
|
||||||
Body body = null;
|
Body body = null;
|
||||||
if (contentUri != null) {
|
if (contentUri != null) {
|
||||||
body = new LocalAttachmentBody(Uri.parse(contentUri), mContext);
|
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",
|
String.format("attachment;\n filename=\"%s\";\n size=%d",
|
||||||
name,
|
name,
|
||||||
size));
|
size));
|
||||||
|
bp.setHeader(MimeHeader.HEADER_CONTENT_ID, contentId);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* HEADER_ANDROID_ATTACHMENT_STORE_DATA is a custom header we add to that
|
* 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), ',');
|
MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA), ',');
|
||||||
|
|
||||||
String name = MimeUtility.getHeaderParameter(attachment.getContentType(), "name");
|
String name = MimeUtility.getHeaderParameter(attachment.getContentType(), "name");
|
||||||
|
String contentId = attachment.getContentId();
|
||||||
|
|
||||||
if (attachmentId == -1) {
|
if (attachmentId == -1) {
|
||||||
ContentValues cv = new ContentValues();
|
ContentValues cv = new ContentValues();
|
||||||
|
@ -933,6 +951,7 @@ public class LocalStore extends Store {
|
||||||
cv.put("size", size);
|
cv.put("size", size);
|
||||||
cv.put("name", name);
|
cv.put("name", name);
|
||||||
cv.put("mime_type", attachment.getMimeType());
|
cv.put("mime_type", attachment.getMimeType());
|
||||||
|
cv.put("content_id", contentId);
|
||||||
|
|
||||||
attachmentId = mDb.insert("attachments", "message_id", cv);
|
attachmentId = mDb.insert("attachments", "message_id", cv);
|
||||||
}
|
}
|
||||||
|
@ -940,6 +959,7 @@ public class LocalStore extends Store {
|
||||||
ContentValues cv = new ContentValues();
|
ContentValues cv = new ContentValues();
|
||||||
cv.put("content_uri", contentUri != null ? contentUri.toString() : null);
|
cv.put("content_uri", contentUri != null ? contentUri.toString() : null);
|
||||||
cv.put("size", size);
|
cv.put("size", size);
|
||||||
|
cv.put("content_id", contentId);
|
||||||
mDb.update(
|
mDb.update(
|
||||||
"attachments",
|
"attachments",
|
||||||
cv,
|
cv,
|
||||||
|
|
|
@ -29,6 +29,7 @@ import com.android.email.mail.Store;
|
||||||
import com.android.email.mail.Transport;
|
import com.android.email.mail.Transport;
|
||||||
import com.android.email.mail.Folder.OpenMode;
|
import com.android.email.mail.Folder.OpenMode;
|
||||||
import com.android.email.mail.internet.MimeMessage;
|
import com.android.email.mail.internet.MimeMessage;
|
||||||
|
import com.android.email.mail.transport.LoggingInputStream;
|
||||||
import com.android.email.mail.transport.MailTransport;
|
import com.android.email.mail.transport.MailTransport;
|
||||||
|
|
||||||
import android.util.Config;
|
import android.util.Config;
|
||||||
|
@ -46,6 +47,7 @@ public class Pop3Store extends Store {
|
||||||
// All flags defining debug or development code settings must be FALSE
|
// All flags defining debug or development code settings must be FALSE
|
||||||
// when code is checked in or released.
|
// when code is checked in or released.
|
||||||
private static boolean DEBUG_FORCE_SINGLE_LINE_UIDL = false;
|
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 };
|
private static final Flag[] PERMANENT_FLAGS = { Flag.DELETED };
|
||||||
|
|
||||||
|
@ -718,7 +720,11 @@ public class Pop3Store extends Store {
|
||||||
}
|
}
|
||||||
if (response != null) {
|
if (response != null) {
|
||||||
try {
|
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) {
|
catch (MessagingException me) {
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,16 +21,25 @@ import com.android.email.Email;
|
||||||
import com.android.email.MessagingController;
|
import com.android.email.MessagingController;
|
||||||
import com.android.email.Preferences;
|
import com.android.email.Preferences;
|
||||||
import com.android.email.R;
|
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.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
import android.test.ActivityInstrumentationTestCase2;
|
import android.test.ActivityInstrumentationTestCase2;
|
||||||
import android.test.suitebuilder.annotation.MediumTest;
|
import android.test.suitebuilder.annotation.MediumTest;
|
||||||
import android.test.suitebuilder.annotation.Suppress;
|
import android.test.suitebuilder.annotation.Suppress;
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.webkit.WebView;
|
import android.webkit.WebView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
@ -58,6 +67,7 @@ public class MessageViewTests
|
||||||
private TextView mToView;
|
private TextView mToView;
|
||||||
private TextView mSubjectView;
|
private TextView mSubjectView;
|
||||||
private WebView mMessageContentView;
|
private WebView mMessageContentView;
|
||||||
|
private Context mContext;
|
||||||
|
|
||||||
public MessageViewTests() {
|
public MessageViewTests() {
|
||||||
super("com.android.email", MessageView.class);
|
super("com.android.email", MessageView.class);
|
||||||
|
@ -66,13 +76,13 @@ public class MessageViewTests
|
||||||
@Override
|
@Override
|
||||||
protected void setUp() throws Exception {
|
protected void setUp() throws Exception {
|
||||||
super.setUp();
|
super.setUp();
|
||||||
Context context = getInstrumentation().getTargetContext();
|
mContext = getInstrumentation().getTargetContext();
|
||||||
Account[] accounts = Preferences.getPreferences(context).getAccounts();
|
Account[] accounts = Preferences.getPreferences(mContext).getAccounts();
|
||||||
if (accounts.length > 0)
|
if (accounts.length > 0)
|
||||||
{
|
{
|
||||||
// This depends on getDefaultAccount() to auto-assign the default account, if necessary
|
// This depends on getDefaultAccount() to auto-assign the default account, if necessary
|
||||||
mAccount = Preferences.getPreferences(context).getDefaultAccount();
|
mAccount = Preferences.getPreferences(mContext).getDefaultAccount();
|
||||||
Email.setServicesEnabled(context);
|
Email.setServicesEnabled(mContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
// configure a mock controller
|
// configure a mock controller
|
||||||
|
@ -93,6 +103,9 @@ public class MessageViewTests
|
||||||
mToView = (TextView) a.findViewById(R.id.to);
|
mToView = (TextView) a.findViewById(R.id.to);
|
||||||
mSubjectView = (TextView) a.findViewById(R.id.subject);
|
mSubjectView = (TextView) a.findViewById(R.id.subject);
|
||||||
mMessageContentView = (WebView) a.findViewById(R.id.message_content);
|
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.forward);
|
||||||
a.handleMenuItem(R.id.mark_as_unread);
|
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
|
* Mock Messaging controller, so we can drive its callbacks. This probably should be
|
||||||
|
|
|
@ -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)
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,6 +17,8 @@
|
||||||
package com.android.email.mail.internet;
|
package com.android.email.mail.internet;
|
||||||
|
|
||||||
import com.android.email.mail.MessagingException;
|
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;
|
import android.test.suitebuilder.annotation.SmallTest;
|
||||||
|
|
||||||
|
@ -95,4 +97,23 @@ public class MimeMessageTest extends TestCase {
|
||||||
message2.setMessageId(testId2);
|
message2.setMessageId(testId2);
|
||||||
assertEquals("set and get Message-ID", testId2, message2.getMessageId());
|
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());
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -16,7 +16,13 @@
|
||||||
|
|
||||||
package com.android.email.mail.internet;
|
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.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;
|
import android.test.suitebuilder.annotation.SmallTest;
|
||||||
|
|
||||||
|
@ -35,7 +41,46 @@ public class MimeUtilityTest extends TestCase {
|
||||||
// TODO: tests for foldAndEncode(String s)
|
// TODO: tests for foldAndEncode(String s)
|
||||||
// TODO: tests for getHeaderParameter(String header, String name)
|
// TODO: tests for getHeaderParameter(String header, String name)
|
||||||
// TODO: tests for findFirstPartByMimeType(Part part, String mimeType)
|
// 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) */
|
/** Tests for getTextFromPart(Part part) */
|
||||||
public void testGetTextFromPartContentTypeCase() throws MessagingException {
|
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 decodeBody(InputStream in, String contentTransferEncoding)
|
||||||
// TODO: tests for collectParts(Part part, ArrayList<Part> viewables, ArrayList<Part> attachments)
|
// TODO: tests for collectParts(Part part, ArrayList<Part> viewables, ArrayList<Part> attachments)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,6 @@ package com.android.email.mail.store;
|
||||||
|
|
||||||
import com.android.email.mail.Address;
|
import com.android.email.mail.Address;
|
||||||
import com.android.email.mail.Message;
|
import com.android.email.mail.Message;
|
||||||
import com.android.email.mail.MessageRetrievalListener;
|
|
||||||
import com.android.email.mail.MessagingException;
|
import com.android.email.mail.MessagingException;
|
||||||
import com.android.email.mail.Folder.OpenMode;
|
import com.android.email.mail.Folder.OpenMode;
|
||||||
import com.android.email.mail.Message.RecipientType;
|
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.MimeMessage;
|
||||||
import com.android.email.mail.internet.TextBody;
|
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.AndroidTestCase;
|
||||||
import android.test.mock.MockApplication;
|
|
||||||
import android.test.suitebuilder.annotation.SmallTest;
|
import android.test.suitebuilder.annotation.SmallTest;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a series of unit tests for the LocalStore class.
|
* 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 String mLocalStoreUri = null;
|
||||||
private LocalStore mStore = null;
|
private LocalStore mStore = null;
|
||||||
private LocalStore.LocalFolder mFolder = null;
|
private LocalStore.LocalFolder mFolder = null;
|
||||||
|
private File mCacheDir;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setup code. We generate a lightweight LocalStore and LocalStore.LocalFolder.
|
* 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");
|
mFolder = (LocalStore.LocalFolder) mStore.getFolder("TEST");
|
||||||
|
|
||||||
// This is needed for parsing mime messages
|
// 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;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ import com.android.email.mail.Folder.FolderType;
|
||||||
import com.android.email.mail.Folder.OpenMode;
|
import com.android.email.mail.Folder.OpenMode;
|
||||||
import com.android.email.mail.Message.RecipientType;
|
import com.android.email.mail.Message.RecipientType;
|
||||||
import com.android.email.mail.internet.BinaryTempFileBody;
|
import com.android.email.mail.internet.BinaryTempFileBody;
|
||||||
|
import com.android.email.mail.internet.MimeMessage;
|
||||||
import com.android.email.mail.transport.MockTransport;
|
import com.android.email.mail.transport.MockTransport;
|
||||||
|
|
||||||
import android.test.AndroidTestCase;
|
import android.test.AndroidTestCase;
|
||||||
|
@ -529,6 +530,16 @@ public class Pop3StoreUnitTests extends AndroidTestCase {
|
||||||
fp.add(FetchProfile.Item.ENVELOPE);
|
fp.add(FetchProfile.Item.ENVELOPE);
|
||||||
mFolder.fetch(messages, fp, null);
|
mFolder.fetch(messages, fp, null);
|
||||||
assertEquals(PER_MESSAGE_SIZE, messages[0].getSize());
|
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
|
// now try fetching the message
|
||||||
setupSingleMessage(mockTransport, 1, false);
|
setupSingleMessage(mockTransport, 1, false);
|
||||||
|
@ -586,6 +597,20 @@ public class Pop3StoreUnitTests extends AndroidTestCase {
|
||||||
assertEquals(1, from.length);
|
assertEquals(1, from.length);
|
||||||
assertEquals("Jones@Registry.Org", from[0].getAddress());
|
assertEquals("Jones@Registry.Org", from[0].getAddress());
|
||||||
assertNull(from[0].getPersonal());
|
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
|
// TODO date
|
||||||
|
|
||||||
|
@ -649,6 +674,10 @@ public class Pop3StoreUnitTests extends AndroidTestCase {
|
||||||
* Date: 26 Aug 76 1429 EDT
|
* Date: 26 Aug 76 1429 EDT
|
||||||
* From: Jones@Registry.Org
|
* From: Jones@Registry.Org
|
||||||
* To: Smith@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 transport the mock transport to preload
|
||||||
* @param msgNum the message number to expect and return
|
* @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, "Date: 26 Aug 76 1429 EDT");
|
||||||
transport.expect(null, "From: Jones@Registry.Org");
|
transport.expect(null, "From: Jones@Registry.Org");
|
||||||
transport.expect(null, "To: Smith@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, "");
|
||||||
transport.expect(null, ".");
|
transport.expect(null, ".");
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue