1
0
mirror of https://github.com/moparisthebest/k-9 synced 2024-08-13 17:03:48 -04:00

Merge remote branch 'k-9/master'

This commit is contained in:
João Pedro Taveira 2011-06-02 14:39:58 +01:00
commit 503b88e2fd
69 changed files with 3015 additions and 1009 deletions

View File

@ -1,10 +1,33 @@
LOCAL_PATH:= $(call my-dir) LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS) include $(CLEAR_VARS)
LOCAL_STATIC_JAVA_LIBRARIES += libcore
LOCAL_STATIC_JAVA_LIBRARIES += libdom
LOCAL_STATIC_JAVA_LIBRARIES += libio
LOCAL_STATIC_JAVA_LIBRARIES += libjutf
LOCAL_STATIC_JAVA_LIBRARIES += libjzlib
LOCAL_MODULE_TAGS := eng LOCAL_MODULE_TAGS := eng
LOCAL_SRC_FILES := $(call all-subdir-java-files) LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_SDK_VERSION := current
LOCAL_PACKAGE_NAME := Email LOCAL_PACKAGE_NAME := Email
include $(BUILD_PACKAGE) include $(BUILD_PACKAGE)
##################################################
include $(CLEAR_VARS)
LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES += libcore:libs/apache-mime4j-core-0.7-SNAPSHOT.jar
LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES += libdom:libs/apache-mime4j-dom-0.7-SNAPSHOT.jar
LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES += libio:libs/commons-io-2.0.1.jar
LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES += libjutf:libs/jutf7-1.0.1-SNAPSHOT.jar
LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES += libjzlib:libs/jzlib-1.0.7.jar
include $(BUILD_MULTI_PREBUILT)
# Use the folloing include to make our test apk.
include $(call all-makefiles-under,$(LOCAL_PATH))

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest <manifest
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="13010" android:versionCode="14001"
android:versionName="3.709" package="com.fsck.k9" android:versionName="3.900" package="com.fsck.k9"
> >
<uses-sdk <uses-sdk
android:minSdkVersion="3" android:minSdkVersion="3"
@ -18,10 +18,6 @@
<uses-permission android:name="android.permission.READ_CONTACTS"/> <uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS"/> <uses-permission android:name="android.permission.READ_SYNC_SETTINGS"/>
<!-- Needed to get the owner name which is used when the first mail account is created -->
<uses-permission android:name="android.permission.READ_OWNER_DATA"/>
<uses-permission android:name="android.permission.GET_ACCOUNTS"/>
<!-- Needed to mark a contact as contacted --> <!-- Needed to mark a contact as contacted -->
<uses-permission android:name="android.permission.WRITE_CONTACTS"/> <uses-permission android:name="android.permission.WRITE_CONTACTS"/>
@ -319,7 +315,7 @@
android:enabled="true" android:enabled="true"
> >
<intent-filter> <intent-filter>
<!-- <!--
android.intent.action.MEDIA_MOUNTED android.intent.action.MEDIA_MOUNTED
* Broadcast Action: External media is present and mounted at its mount point. * Broadcast Action: External media is present and mounted at its mount point.
@ -329,9 +325,9 @@ android.intent.action.MEDIA_MOUNTED
--> -->
<action android:name="android.intent.action.MEDIA_MOUNTED"/> <action android:name="android.intent.action.MEDIA_MOUNTED"/>
<!-- <!--
MEDIA_EJECT and MEDIA_UNMOUNTED are not defined here: they have to be dynamically registered MEDIA_EJECT and MEDIA_UNMOUNTED are not defined here: they have to be dynamically registered
otherwise it would make K-9 start at the wrong time otherwise it would make K-9 start at the wrong time
--> -->

View File

@ -2,7 +2,7 @@
<body bgcolor="white"> <body bgcolor="white">
<table width="100%" height="100%"> <table width="100%" height="100%">
<tr> <tr>
<td align="center" valign="center"> <td align="center" valign="middle">
<font color="gray">Downloading...</font> <font color="gray">Downloading...</font>
<br/> <br/>
<br/> <br/>

View File

@ -2,7 +2,7 @@
<body bgcolor="white"> <body bgcolor="white">
<table width="100%" height="100%"> <table width="100%" height="100%">
<tr> <tr>
<td align="center" valign="center"> <td align="center" valign="middle">
<font color="gray">No text</font> <font color="gray">No text</font>
</td> </td>
</tr> </tr>

View File

@ -2,7 +2,7 @@
<body bgcolor="white"> <body bgcolor="white">
<table width="100%" height="100%"> <table width="100%" height="100%">
<tr> <tr>
<td align="center" valign="center"> <td align="center" valign="middle">
<font color="gray">Loading...</font> <font color="gray">Loading...</font>
<br/> <br/>
<br/> <br/>

View File

@ -3,7 +3,6 @@
<component name="FacetManager"> <component name="FacetManager">
<facet type="android" name="Android"> <facet type="android" name="Android">
<configuration> <configuration>
<option name="PLATFORM_NAME" value="Android 2.3.1 Platform" />
<option name="GEN_FOLDER_RELATIVE_PATH_APT" value="/gen" /> <option name="GEN_FOLDER_RELATIVE_PATH_APT" value="/gen" />
<option name="GEN_FOLDER_RELATIVE_PATH_AIDL" value="/gen" /> <option name="GEN_FOLDER_RELATIVE_PATH_AIDL" value="/gen" />
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/AndroidManifest.xml" /> <option name="MANIFEST_FILE_RELATIVE_PATH" value="/AndroidManifest.xml" />
@ -17,7 +16,6 @@
<option name="USE_CUSTOM_COMPILER_MANIFEST" value="false" /> <option name="USE_CUSTOM_COMPILER_MANIFEST" value="false" />
<option name="CUSTOM_COMPILER_MANIFEST" value="" /> <option name="CUSTOM_COMPILER_MANIFEST" value="" />
<option name="APK_PATH" value="" /> <option name="APK_PATH" value="" />
<option name="ADD_ANDROID_LIBRARY" value="true" />
<option name="LIBRARY_PROJECT" value="false" /> <option name="LIBRARY_PROJECT" value="false" />
<option name="RUN_PROCESS_RESOURCES_MAVEN_TASK" value="true" /> <option name="RUN_PROCESS_RESOURCES_MAVEN_TASK" value="true" />
<option name="GENERATE_UNSIGNED_APK" value="false" /> <option name="GENERATE_UNSIGNED_APK" value="false" />
@ -79,33 +77,6 @@
<SOURCES /> <SOURCES />
</library> </library>
</orderEntry> </orderEntry>
<orderEntry type="module-library" scope="PROVIDED">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/compile-only-libs/commons-logging-1.1.1.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
<orderEntry type="module-library" scope="PROVIDED">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/compile-only-libs/bcprov-jdk15-143.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
<orderEntry type="module-library" scope="PROVIDED">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/compile-only-libs/commons-codec-1.3.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
<orderEntry type="module-library" scope="TEST"> <orderEntry type="module-library" scope="TEST">
<library> <library>
<CLASSES> <CLASSES>

View File

@ -152,7 +152,6 @@
<TextView <TextView
android:id="@+id/userId" android:id="@+id/userId"
android:text=""
android:ellipsize="end" android:ellipsize="end"
android:textAppearance="?android:attr/textAppearanceSmall" android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="@android:color/primary_text_light" android:textColor="@android:color/primary_text_light"
@ -161,7 +160,6 @@
<TextView <TextView
android:id="@+id/userIdRest" android:id="@+id/userIdRest"
android:text=""
android:textSize="10sp" android:textSize="10sp"
android:ellipsize="end" android:ellipsize="end"
android:textColor="@android:color/primary_text_light" android:textColor="@android:color/primary_text_light"
@ -239,6 +237,17 @@
android:textColor="@android:color/primary_text_light" android:textColor="@android:color/primary_text_light"
android:textAppearance="?android:attr/textAppearanceMedium" /> android:textAppearance="?android:attr/textAppearanceMedium" />
<Button
android:id="@+id/quoted_text_show"
android:text="@string/message_compose_show_quoted_text_action"
android:textSize="16dip"
android:padding="0dip"
android:layout_gravity="right"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true" />
<!-- Quoted text bar --> <!-- Quoted text bar -->
<RelativeLayout <RelativeLayout
android:id="@+id/quoted_text_bar" android:id="@+id/quoted_text_bar"

View File

@ -83,7 +83,7 @@
android:layout_marginRight="0dip" android:layout_marginRight="0dip"
android:singleLine="false" android:singleLine="false"
android:bufferType="spannable" android:bufferType="spannable"
android:textColor="?android:attr/textColorTertiary" android:textColor="?android:attr/textColorPrimary"
android:textAppearance="?android:attr/textAppearanceSmall" /> android:textAppearance="?android:attr/textAppearanceSmall" />
</RelativeLayout> </RelativeLayout>

View File

@ -344,7 +344,7 @@ Benvingut a la configuració del K-9. El K-9 és un client de codi obert per An
<string name="global_settings_confirm_action_archive">Arxiva</string> <string name="global_settings_confirm_action_archive">Arxiva</string>
<string name="global_settings_confirm_action_delete">Esborra (només la vista del missatge)</string> <string name="global_settings_confirm_action_delete">Esborra (només la vista del missatge)</string>
<string name="global_settings_confirm_action_spam">Brossa</string> <string name="global_settings_confirm_action_spam">Brossa</string>
<!-- NEW: <string name="global_settings_confirm_action_mark_all_as_read">Mark all as read</string>--> <string name="global_settings_confirm_action_mark_all_as_read">Maca\'ls tots com a llegits</string>
<string name="global_settings_confirm_action_send">Envia</string> <string name="global_settings_confirm_action_send">Envia</string>
<string name="global_settings_privacy_mode_title">Bloca notificacions</string> <string name="global_settings_privacy_mode_title">Bloca notificacions</string>
@ -1001,7 +1001,7 @@ Benvingut a la configuració del K-9. El K-9 és un client de codi obert per An
<!-- APG related --> <!-- APG related -->
<string name="error_activity_not_found">No sha trobat cap aplicació idònia per a aquesta acció.</string> <string name="error_activity_not_found">No sha trobat cap aplicació idònia per a aquesta acció.</string>
<string name="error_apg_version_not_supported">Versió APG instal lada no suportada.</string> <string name="error_apg_version_not_supported">Versió APG instal·lada no suportada.</string>
<string name="btn_crypto_sign">Inicia</string> <string name="btn_crypto_sign">Inicia</string>
<string name="btn_encrypt">Encripta</string> <string name="btn_encrypt">Encripta</string>
<string name="btn_decrypt">Desencripta</string> <string name="btn_decrypt">Desencripta</string>
@ -1025,10 +1025,10 @@ Benvingut a la configuració del K-9. El K-9 és un client de codi obert per An
<string name="dialog_confirm_delete_confirm_button">Esborra</string> <string name="dialog_confirm_delete_confirm_button">Esborra</string>
<string name="dialog_confirm_delete_cancel_button">No esborris</string> <string name="dialog_confirm_delete_cancel_button">No esborris</string>
<!-- NEW: <string name="dialog_confirm_spam_title">Confirm move to spam folder</string>--> <string name="dialog_confirm_spam_title">Confirma moure\'l carpeta brossa</string>
<!-- NEW: <string name="dialog_confirm_spam_message">Do you really want to move this message to the spam folder?</string>--> <string name="dialog_confirm_spam_message">Realment vols moure aquest missatge a la carpeta brossa?</string>
<!-- NEW: <string name="dialog_confirm_spam_confirm_button">Yes</string>--> <string name="dialog_confirm_spam_confirm_button"></string>
<!-- NEW: <string name="dialog_confirm_spam_cancel_button">No</string>--> <string name="dialog_confirm_spam_cancel_button">No</string>
<string name="dialog_attachment_progress_title">S\'està descarregant adjunt</string> <string name="dialog_attachment_progress_title">S\'està descarregant adjunt</string>
@ -1038,6 +1038,9 @@ Benvingut a la configuració del K-9. El K-9 és un client de codi obert per An
<string name="messagelist_sent_cc_me_sigil"></string> <string name="messagelist_sent_cc_me_sigil"></string>
<string name="error_unable_to_connect">No es pot connectar.</string> <string name="error_unable_to_connect">No es pot connectar.</string>
<!-- NEW: <string name="account_unavailable">Account \"<xliff:g id="account">%s</xliff:g>\" is unavailable; check storage</string>--> <string name="account_unavailable">Compte \"<xliff:g id="account">%s</xliff:g>\" no és disponible; comprova emmagatzematge</string>
<string name="settings_attachment_default_path">Desa adjunts a...</string>
<string name="attachment_save_title">Desa adjunt</string>
<string name="attachment_save_desc">No s\'ha trobat l\'arxiu al navegador. On vols desar l\'adjunt?</string>
</resources> </resources>

View File

@ -1047,4 +1047,7 @@ Vítejte v nastavení pošty K-9 Mail. K-9 je open source poštovní klient pro
<!-- NEW: <string name="account_unavailable">Account \"<xliff:g id="account">%s</xliff:g>\" is unavailable; check storage</string>--> <!-- NEW: <string name="account_unavailable">Account \"<xliff:g id="account">%s</xliff:g>\" is unavailable; check storage</string>-->
<!-- NEW: <string name="settings_attachment_default_path">Save attachments to...</string>-->
<!-- NEW: <string name="attachment_save_title">Save attachment</string>-->
<!-- NEW: <string name="attachment_save_desc">No file browser found. Where would you like to save this attachment?</string>-->
</resources> </resources>

View File

@ -56,7 +56,7 @@
<string name="reply_action">Antworten</string> <string name="reply_action">Antworten</string>
<string name="reply_all_action">Allen antworten</string> <string name="reply_all_action">Allen antworten</string>
<string name="delete_action">Löschen</string> <string name="delete_action">Löschen</string>
<string name="archive_action">Sichern</string> <string name="archive_action">Archivieren</string>
<string name="spam_action">Spam</string> <string name="spam_action">Spam</string>
<string name="delete_all_action">Ordner leeren</string> <string name="delete_all_action">Ordner leeren</string>
<string name="forward_action">Weiterleiten</string> <string name="forward_action">Weiterleiten</string>
@ -341,7 +341,7 @@ Willkommen zum \"K-9 Mail\"-Setup. K-9 ist eine quelloffene E-Mail-Anwendung fü
<string name="global_settings_confirm_action_archive">Archivieren</string> <string name="global_settings_confirm_action_archive">Archivieren</string>
<string name="global_settings_confirm_action_delete">Löschen (nur in Nachrichtenansicht)</string> <string name="global_settings_confirm_action_delete">Löschen (nur in Nachrichtenansicht)</string>
<string name="global_settings_confirm_action_spam">Spam</string> <string name="global_settings_confirm_action_spam">Spam</string>
<!-- NEW: <string name="global_settings_confirm_action_mark_all_as_read">Mark all as read</string>--> <string name="global_settings_confirm_action_mark_all_as_read">Alle als gelesen markieren</string>
<string name="global_settings_confirm_action_send">Senden</string> <string name="global_settings_confirm_action_send">Senden</string>
<string name="global_settings_privacy_mode_title">Vertrauliche Benachrichtigung</string> <string name="global_settings_privacy_mode_title">Vertrauliche Benachrichtigung</string>
@ -688,7 +688,7 @@ Willkommen zum \"K-9 Mail\"-Setup. K-9 ist eine quelloffene E-Mail-Anwendung fü
<string name="account_settings_add_account_label">Eine weiteres Konto hinzufügen</string> <string name="account_settings_add_account_label">Eine weiteres Konto hinzufügen</string>
<string name="account_settings_description_label">Kontoname</string> <string name="account_settings_description_label">Kontoname</string>
<string name="account_settings_name_label">Ihr Name</string> <string name="account_settings_name_label">Ihr Name</string>
<string name="notifications_title">Benachrichtigungseinstellungen</string> <string name="notifications_title">Benachrichtigungen</string>
<string name="account_settings_ring_summary">Klingeln bei neuer Nachricht</string> <string name="account_settings_ring_summary">Klingeln bei neuer Nachricht</string>
<string name="account_settings_vibrate_enable">Vibration</string> <string name="account_settings_vibrate_enable">Vibration</string>
<string name="account_settings_vibrate_summary">Vibration bei neuer Nachricht</string> <string name="account_settings_vibrate_summary">Vibration bei neuer Nachricht</string>
@ -869,16 +869,16 @@ Willkommen zum \"K-9 Mail\"-Setup. K-9 ist eine quelloffene E-Mail-Anwendung fü
<string name="date_format_common">dd.MMM yyyy</string> <string name="date_format_common">dd.MMM yyyy</string>
<string name="date_format_iso8601">yyyy-MM-dd</string> <string name="date_format_iso8601">yyyy-MM-dd</string>
<string name="batch_ops">Mehrfachauswahl</string> <string name="batch_ops">Gruppenoperationen</string>
<string name="batch_delete_op">Ausgewählte löschen</string> <string name="batch_delete_op">Gewählte löschen</string>
<string name="batch_mark_read_op">Ausgewählte als gelesen markieren</string> <string name="batch_mark_read_op">Gewählte als gelesen markieren</string>
<string name="batch_mark_unread_op">Ausgewählte als ungelesen markieren</string> <string name="batch_mark_unread_op">Gewählte als ungelesen markieren</string>
<string name="batch_flag_op">Ausgewählte als wichtig markieren</string> <string name="batch_flag_op">Gewählte als wichtig markieren</string>
<string name="batch_unflag_op">Markierung bei Ausgewählten entfernen</string> <string name="batch_unflag_op">Markierung bei Gewählten entfernen</string>
<string name="batch_archive_op">Ausgewählte sichern</string> <string name="batch_archive_op">Gewählte sichern</string>
<string name="batch_spam_op">Ausgewählte als Spam markieren</string> <string name="batch_spam_op">Gewählte als Spam markieren</string>
<string name="batch_move_op">Ausgewählte verschieben</string> <string name="batch_move_op">Gewählte verschieben</string>
<string name="batch_copy_op">Ausgewählte kopieren</string> <string name="batch_copy_op">Gewählte kopieren</string>
<string name="batch_flag_mode">Stern-Modus</string> <string name="batch_flag_mode">Stern-Modus</string>
<string name="batch_select_mode">Auswahl-Modus</string> <string name="batch_select_mode">Auswahl-Modus</string>
<string name="batch_plain_mode">Normaler Modus</string> <string name="batch_plain_mode">Normaler Modus</string>
@ -1022,12 +1022,12 @@ Willkommen zum \"K-9 Mail\"-Setup. K-9 ist eine quelloffene E-Mail-Anwendung fü
<string name="dialog_confirm_delete_confirm_button">Löschen</string> <string name="dialog_confirm_delete_confirm_button">Löschen</string>
<string name="dialog_confirm_delete_cancel_button">Nicht löschen</string> <string name="dialog_confirm_delete_cancel_button">Nicht löschen</string>
<!-- NEW: <string name="dialog_confirm_spam_title">Confirm move to spam folder</string>--> <string name="dialog_confirm_spam_title">Als Spam markieren</string>
<!-- NEW: <string name="dialog_confirm_spam_message">Do you really want to move this message to the spam folder?</string>--> <string name="dialog_confirm_spam_message">Wollen Sie diese Nachricht in den Spam-Ordner verschieben?</string>
<!-- NEW: <string name="dialog_confirm_spam_confirm_button">Yes</string>--> <string name="dialog_confirm_spam_confirm_button">Ja</string>
<!-- NEW: <string name="dialog_confirm_spam_cancel_button">No</string>--> <string name="dialog_confirm_spam_cancel_button">Nein</string>
<!-- NEW: <string name="dialog_attachment_progress_title">Downloading attachment</string>--> <string name="dialog_attachment_progress_title">Anhang wird heruntergeladen</string>
<string name="debug_logging_enabled">Debug-Meldungen werden mit Hilfe des Android-Logging-Systems aufgezeichnet.</string> <string name="debug_logging_enabled">Debug-Meldungen werden mit Hilfe des Android-Logging-Systems aufgezeichnet.</string>
@ -1037,4 +1037,7 @@ Willkommen zum \"K-9 Mail\"-Setup. K-9 ist eine quelloffene E-Mail-Anwendung fü
<string name="account_unavailable">Konto \"<xliff:g id="account">%s</xliff:g>\" ist nicht verfügbar; Bitte SD-Karte prüfen.</string> <string name="account_unavailable">Konto \"<xliff:g id="account">%s</xliff:g>\" ist nicht verfügbar; Bitte SD-Karte prüfen.</string>
<string name="settings_attachment_default_path">Anhang speichern unter...</string>
<string name="attachment_save_title">Anhang speichern</string>
<string name="attachment_save_desc">Es wurde kein Dateimanager gefunden. Wo soll der Anhang abgelegt werden?</string>
</resources> </resources>

View File

@ -1037,4 +1037,7 @@ Bienvenido a la Configuración de K-9. K-9 es un cliente de correo OpenSource pa
<!-- NEW: <string name="account_unavailable">Account \"<xliff:g id="account">%s</xliff:g>\" is unavailable; check storage</string>--> <!-- NEW: <string name="account_unavailable">Account \"<xliff:g id="account">%s</xliff:g>\" is unavailable; check storage</string>-->
<!-- NEW: <string name="settings_attachment_default_path">Save attachments to...</string>-->
<!-- NEW: <string name="attachment_save_title">Save attachment</string>-->
<!-- NEW: <string name="attachment_save_desc">No file browser found. Where would you like to save this attachment?</string>-->
</resources> </resources>

View File

@ -1034,4 +1034,7 @@ Tervetuloa K-9 Mail asennukseen.  K-9 on avoimen lähdekoodin sähköpostiasiak
<!-- NEW: <string name="account_unavailable">Account \"<xliff:g id="account">%s</xliff:g>\" is unavailable; check storage</string>--> <!-- NEW: <string name="account_unavailable">Account \"<xliff:g id="account">%s</xliff:g>\" is unavailable; check storage</string>-->
<!-- NEW: <string name="settings_attachment_default_path">Save attachments to...</string>-->
<!-- NEW: <string name="attachment_save_title">Save attachment</string>-->
<!-- NEW: <string name="attachment_save_desc">No file browser found. Where would you like to save this attachment?</string>-->
</resources> </resources>

View File

@ -1037,4 +1037,7 @@ Benvido á Configuración de K-9. K-9 é un cliente de correo OpenSource para An
<string name="account_unavailable">A conta \"<xliff:g id="account">%s</xliff:g>\" non está dispoñible; verifica o almacenamento</string> <string name="account_unavailable">A conta \"<xliff:g id="account">%s</xliff:g>\" non está dispoñible; verifica o almacenamento</string>
<!-- NEW: <string name="settings_attachment_default_path">Save attachments to...</string>-->
<!-- NEW: <string name="attachment_save_title">Save attachment</string>-->
<!-- NEW: <string name="attachment_save_desc">No file browser found. Where would you like to save this attachment?</string>-->
</resources> </resources>

View File

@ -1041,4 +1041,7 @@ Benvenuto nella configurazione della posta di K-9. K-9 è un client di posta ope
<!-- NEW: <string name="account_unavailable">Account \"<xliff:g id="account">%s</xliff:g>\" is unavailable; check storage</string>--> <!-- NEW: <string name="account_unavailable">Account \"<xliff:g id="account">%s</xliff:g>\" is unavailable; check storage</string>-->
<!-- NEW: <string name="settings_attachment_default_path">Save attachments to...</string>-->
<!-- NEW: <string name="attachment_save_title">Save attachment</string>-->
<!-- NEW: <string name="attachment_save_desc">No file browser found. Where would you like to save this attachment?</string>-->
</resources> </resources>

View File

@ -23,7 +23,7 @@
<string name="accounts_title">アカウント一覧</string> <string name="accounts_title">アカウント一覧</string>
<string name="advanced">拡張機能</string> <string name="advanced">拡張機能</string>
<string name="folder_list_title"><xliff:g id="account">%s</xliff:g> </string> <string name="folder_list_title"><xliff:g id="account">%s</xliff:g> </string>
<string name="shortcuts_title">K-9 Accounts</string> <string name="shortcuts_title">K-9 アカウント</string>
<string name="message_list_title"><xliff:g id="account">%s</xliff:g>:<xliff:g id="folder">%s</xliff:g> </string> <string name="message_list_title"><xliff:g id="account">%s</xliff:g>:<xliff:g id="folder">%s</xliff:g> </string>
@ -133,7 +133,7 @@
<string name="status_loading_folder">(取込中 <xliff:g id="folder">%s</xliff:g><xliff:g id="progress">%s</xliff:g>)</string> <string name="status_loading_folder">(取込中 <xliff:g id="folder">%s</xliff:g><xliff:g id="progress">%s</xliff:g>)</string>
<string name="status_loading_more">メール取込中\u2026</string> <string name="status_loading_more">メール取込中\u2026</string>
<string name="status_network_error">接続エラー</string> <string name="status_network_error">接続エラー</string>
<string name="status_invalid_id_error">新着メールなし</string> <string name="status_invalid_id_error">メッセージが見つかりません</string>
<string name="status_error">エラー</string> <string name="status_error">エラー</string>
<string name="status_sending">送信\u2026</string> <string name="status_sending">送信\u2026</string>
@ -188,13 +188,13 @@
<string name="send_failure_subject">メール送信に失敗しました</string> <string name="send_failure_subject">メール送信に失敗しました</string>
<string name="send_failure_body_abbrev"><xliff:g id="errorFolder">%s</xliff:g> フォルダ詳細を確認してください</string> <string name="send_failure_body_abbrev"><xliff:g id="errorFolder">%s</xliff:g> フォルダ詳細を確認してください</string>
<string name="send_failure_body_fmt">K-9 はメール送信中にトラブルが発生しました. <string name="send_failure_body_fmt">メール送信中に問題が発生しました。
メッセージが送信されたかどうかについては、トラブルに応じるため不明です. 問題の性質により、メッセージが送信されたかどうかがわかりません。
送信宛先は、メールを既に受信している場合があります. 受信者は、そのメッセージを既に受信しているかもしれません。
\u000a\u000a送信済みトレイにトラブルで発生したメールを格納します. \u000a\u000a問題が発生したメールには送信トレイにスターが付いています。
フラグ解除されたメールを再送信することができます. スターを取ることで、メールを再送することができます。
送信済みトレイを長く押下することで「メール送信」メニューを表示させて送信することができます.\u000A\u000a 他のメッセージを送信するには、「メール送信」メニューを表示させるために送信トレイを長押ししてください。\u000A\u000a
<xliff:g id="errorFolder">%s</xliff:g> フォルダには失敗したエラーメッセージが含まれています.</string> <xliff:g id="errorFolder">%s</xliff:g> フォルダにはその問題に関するエラーメッセージが含まれています。</string>
<string name="alert_header">K-9 警告</string> <string name="alert_header">K-9 警告</string>
<string name="no_connection_alert">送信ネットワークのリソース不足のため同期処理中断</string> <string name="no_connection_alert">送信ネットワークのリソース不足のため同期処理中断</string>
@ -231,7 +231,7 @@ K-9 Mail セットアップにようこそ。\nK-9 は標準のAndroidメール
<string name="debug_enable_debug_logging_title">拡張デバッグログ</string> <string name="debug_enable_debug_logging_title">拡張デバッグログ</string>
<string name="debug_enable_debug_logging_summary">追加診断情報ログ</string> <string name="debug_enable_debug_logging_summary">追加診断情報ログ</string>
<string name="debug_enable_sensitive_logging_title">詳細情報ログ</string> <string name="debug_enable_sensitive_logging_title">詳細情報ログ</string>
<string name="debug_enable_sensitive_logging_summary">ログにパスワードが表示されるかもしれません</string> <string name="debug_enable_sensitive_logging_summary">ログにパスワードが表示される</string>
<string name="message_header_mua">K-9 for Android </string> <string name="message_header_mua">K-9 for Android </string>
@ -259,16 +259,16 @@ K-9 Mail セットアップにようこそ。\nK-9 は標準のAndroidメール
<string name="message_compose_content_hint">本文</string> <string name="message_compose_content_hint">本文</string>
<string name="message_compose_quote_header_separator">-------- 元メール --------</string> <string name="message_compose_quote_header_separator">-------- 元メール --------</string>
<string name="message_compose_quote_header_subject">件名:</string> <string name="message_compose_quote_header_subject">件名:</string>
<!-- NEW: <string name="message_compose_quote_header_send_date">Sent:</string>--> <string name="message_compose_quote_header_send_date">送信日:</string>
<string name="message_compose_quote_header_from">送信者:</string> <string name="message_compose_quote_header_from">送信者:</string>
<string name="message_compose_quote_header_to">宛先:</string> <string name="message_compose_quote_header_to">宛先:</string>
<string name="message_compose_quote_header_cc">CC:</string> <string name="message_compose_quote_header_cc">CC:</string>
<string name="message_compose_reply_header_fmt"><xliff:g id="sender">%s</xliff:g> wrote:\n\n</string> <string name="message_compose_reply_header_fmt"><xliff:g id="sender">%s</xliff:g> wrote:\n\n</string>
<string name="message_compose_quoted_text_label">テキスト引用</string> <string name="message_compose_quoted_text_label">テキスト引用</string>
<string name="message_compose_error_no_recipients">少なくとも1つの受信者を追加する必要があります</string> <string name="message_compose_error_no_recipients">少なくとも1つの受信者を追加する必要があります</string>
<!-- NEW: <string name="error_contact_address_not_found">No email address could be found.</string>--> <string name="error_contact_address_not_found">メールアドレスが登録されていません</string>
<string name="message_compose_downloading_attachments_toast">一部の添付ファイルをダウンロードしていません.このメールが送信される前に自動的にダウンロードされます.</string> <string name="message_compose_downloading_attachments_toast">一部の添付ファイルをダウンロードしていません。このメールが送信される前に自動的にダウンロードされます。</string>
<string name="message_compose_attachments_skipped_toast">ダウンロードしていないため、一部の添付ファイルを転送することはできません</string> <string name="message_compose_attachments_skipped_toast">ダウンロードしていないため、一部の添付ファイルを転送することはできません</string>
@ -293,9 +293,9 @@ K-9 Mail セットアップにようこそ。\nK-9 は標準のAndroidメール
<string name="message_view_download_remainder">すべてダウンロード</string> <string name="message_view_download_remainder">すべてダウンロード</string>
<!-- NOTE: The following message refers to strings with id 'account_setup_incoming_save_all_headers_label' and 'account_setup_incoming_title' --> <!-- NOTE: The following message refers to strings with id 'account_setup_incoming_save_all_headers_label' and 'account_setup_incoming_title' -->
<string name="message_additional_headers_not_downloaded">一部のヘッダしか保存されていませんヘッダをすべて保存するためには、アカウント設定の受信メールサーバ設定で「すべてのヘッダを端末に保存」をチェックしてください.</string> <string name="message_additional_headers_not_downloaded">一部のヘッダしか保存されていませんヘッダをすべて保存するためには、アカウント設定の受信メールサーバ設定で「ヘッダのダウンロード」をチェックしてください。</string>
<string name="message_no_additional_headers_available">すべてのヘッダをダウンロードしましたが、表示すべき追加ヘッダはありませんでした</string> <string name="message_no_additional_headers_available">すべてのヘッダをダウンロードしましたが、表示すべき追加ヘッダはありませんでした</string>
<string name="message_additional_headers_retrieval_failed">追加ヘッダをデータベースまたはメールサーバから取得できませんでした</string> <string name="message_additional_headers_retrieval_failed">追加ヘッダをデータベースまたはメールサーバから取得できませんでした</string>
<string name="mailbox_select_dlg_title">フォルダ</string> <string name="mailbox_select_dlg_title">フォルダ</string>
<string name="mailbox_select_dlg_new_mailbox_action">新しいフォルダ</string> <string name="mailbox_select_dlg_new_mailbox_action">新しいフォルダ</string>
@ -318,7 +318,7 @@ K-9 Mail セットアップにようこそ。\nK-9 は標準のAndroidメール
<string name="global_settings_flag_label">スターを表示</string> <string name="global_settings_flag_label">スターを表示</string>
<string name="global_settings_flag_summary">スターは印を付けたメッセージを示しま</string> <string name="global_settings_flag_summary">スターは印の付いたメッセージを示</string>
<string name="global_settings_checkbox_label">複数選択チェックボックス</string> <string name="global_settings_checkbox_label">複数選択チェックボックス</string>
<string name="global_settings_checkbox_summary">複数選択チェックボックス常時表示</string> <string name="global_settings_checkbox_summary">複数選択チェックボックス常時表示</string>
<string name="global_settings_touchable_label">メッセージプレビュー</string> <string name="global_settings_touchable_label">メッセージプレビュー</string>
@ -333,7 +333,7 @@ K-9 Mail セットアップにようこそ。\nK-9 は標準のAndroidメール
<string name="global_settings_registered_name_color_changed">連絡先の名前の場合は色を付ける</string> <string name="global_settings_registered_name_color_changed">連絡先の名前の場合は色を付ける</string>
<string name="global_settings_messageview_fixedwidth_label">固定幅フォント</string> <string name="global_settings_messageview_fixedwidth_label">固定幅フォント</string>
<string name="global_settings_messageview_fixedwidth_summary">プレーンテキストメッセージを表示する際、固定幅フォントを利用します.</string> <string name="global_settings_messageview_fixedwidth_summary">プレーンテキストメッセージの表示に固定幅フォントを利用</string>
<string name="global_settings_messageview_return_to_list_label">削除後メッセージ一覧へ戻る</string> <string name="global_settings_messageview_return_to_list_label">削除後メッセージ一覧へ戻る</string>
<string name="global_settings_messageview_return_to_list_summary">メッセージの削除後、メッセージ一覧に戻る</string> <string name="global_settings_messageview_return_to_list_summary">メッセージの削除後、メッセージ一覧に戻る</string>
@ -342,7 +342,7 @@ K-9 Mail セットアップにようこそ。\nK-9 は標準のAndroidメール
<string name="global_settings_confirm_action_archive">アーカイブ</string> <string name="global_settings_confirm_action_archive">アーカイブ</string>
<string name="global_settings_confirm_action_delete">削除(メッセージ表示画面のみ)</string> <string name="global_settings_confirm_action_delete">削除(メッセージ表示画面のみ)</string>
<string name="global_settings_confirm_action_spam">迷惑メール</string> <string name="global_settings_confirm_action_spam">迷惑メール</string>
<!-- NEW: <string name="global_settings_confirm_action_mark_all_as_read">Mark all as read</string>--> <string name="global_settings_confirm_action_mark_all_as_read">すべて既読にする</string>
<string name="global_settings_confirm_action_send">送信</string> <string name="global_settings_confirm_action_send">送信</string>
<string name="global_settings_privacy_mode_title">スクリーンロック時の通知</string> <string name="global_settings_privacy_mode_title">スクリーンロック時の通知</string>
@ -350,7 +350,7 @@ K-9 Mail セットアップにようこそ。\nK-9 は標準のAndroidメール
<string name="quiet_time">夜間時間帯</string> <string name="quiet_time">夜間時間帯</string>
<string name="quiet_time_description">夜間での各種通知や表示を抑制する時間帯を設定します.</string> <string name="quiet_time_description">夜間での各種通知や表示を抑制する時間帯を設定</string>
<string name="quiet_time_starts">開始時刻</string> <string name="quiet_time_starts">開始時刻</string>
<string name="quiet_time_ends">終了時刻</string> <string name="quiet_time_ends">終了時刻</string>
@ -491,7 +491,7 @@ K-9 Mail セットアップにようこそ。\nK-9 は標準のAndroidメール
<string name="push_poll_on_connect_label">プッシュ接続時の同期</string> <string name="push_poll_on_connect_label">プッシュ接続時の同期</string>
<string name="account_setup_options_enable_push_label">このアカウントにプッシュメールを有効化</string> <string name="account_setup_options_enable_push_label">このアカウントにプッシュメールを有効化</string>
<string name="account_setup_options_enable_push_summary">メールサーバがサポートしていれば新しいメッセージは即座に表示されます.当オプションはパフォーマンスが改善もしくは低下します.</string> <string name="account_setup_options_enable_push_summary">メールサーバがサポートしていれば新しいメッセージは即座に表示されます。当オプションはパフォーマンスが改善もしくは低下します。</string>
<string name="idle_refresh_period_label">IMAP IDLEプッシュ接続のリフレッシュ</string> <string name="idle_refresh_period_label">IMAP IDLEプッシュ接続のリフレッシュ</string>
<string name="idle_refresh_period_1min">1分毎</string> <string name="idle_refresh_period_1min">1分毎</string>
<string name="idle_refresh_period_2min">2分毎</string> <string name="idle_refresh_period_2min">2分毎</string>
@ -584,7 +584,7 @@ K-9 Mail セットアップにようこそ。\nK-9 は標準のAndroidメール
<string name="account_settings_crypto_app_none">なし</string> <string name="account_settings_crypto_app_none">なし</string>
<string name="account_settings_crypto_app_not_available">利用不可</string> <string name="account_settings_crypto_app_not_available">利用不可</string>
<string name="account_settings_crypto_auto_signature">自動署名</string> <string name="account_settings_crypto_auto_signature">自動署名</string>
<string name="account_settings_crypto_auto_signature_summary">このアカウントのE-Mailアドレスから署名の鍵を自動的に決定する</string> <string name="account_settings_crypto_auto_signature_summary">このアカウントのE-Mailアドレスから署名の鍵を自動的に決定する</string>
<string name="account_settings_mail_check_frequency_label">同期フォルダの同期間隔</string> <string name="account_settings_mail_check_frequency_label">同期フォルダの同期間隔</string>
<string name="account_settings_second_class_check_frequency_label">2nd クラス自動受信間隔</string> <string name="account_settings_second_class_check_frequency_label">2nd クラス自動受信間隔</string>
@ -600,7 +600,7 @@ K-9 Mail セットアップにようこそ。\nK-9 は標準のAndroidメール
<string name="account_settings_mail_display_count_label">一度に表示するメール数</string> <string name="account_settings_mail_display_count_label">一度に表示するメール数</string>
<string name="account_settings_autodownload_message_size_label">ダウンロードするメッセージの上限</string> <string name="account_settings_autodownload_message_size_label">ダウンロードするメッセージサイズの上限</string>
<string name="account_settings_autodownload_message_size_1">1Kb</string> <string name="account_settings_autodownload_message_size_1">1Kb</string>
<string name="account_settings_autodownload_message_size_2">2Kb</string> <string name="account_settings_autodownload_message_size_2">2Kb</string>
<string name="account_settings_autodownload_message_size_4">4Kb</string> <string name="account_settings_autodownload_message_size_4">4Kb</string>
@ -841,7 +841,7 @@ K-9 Mail セットアップにようこそ。\nK-9 は標準のAndroidメール
<string name="settings_messageview_mobile_layout_label">1列レイアウト</string> <string name="settings_messageview_mobile_layout_label">1列レイアウト</string>
<string name="settings_messageview_mobile_layout_summary">小さい画面用にHTMLメッセージを再構成</string> <string name="settings_messageview_mobile_layout_summary">小さい画面用にHTMLメッセージを再構成</string>
<string name="settings_messageview_zoom_controls_label">ズーム制御</string> <string name="settings_messageview_zoom_controls_label">ズーム制御</string>
<string name="settings_messageview_zoom_controls_summary">デバイスが対応するならば、ズームウィジェットやピンチズームを有効にしま</string> <string name="settings_messageview_zoom_controls_summary">デバイスが対応するならば、ズームウィジェットやピンチズームを有効にす</string>
@ -910,13 +910,13 @@ K-9 Mail セットアップにようこそ。\nK-9 は標準のAndroidメール
<string name="start_integrated_inbox_summary">起動後に統合フォルダを表示する</string> <string name="start_integrated_inbox_summary">起動後に統合フォルダを表示する</string>
<string name="measure_accounts_title">アカウントのサイズ表示</string> <string name="measure_accounts_title">アカウントのサイズ表示</string>
<string name="measure_accounts_summary">表示を早くしたい場合はチェックをはずしてくださ</string> <string name="measure_accounts_summary">表示を速くしたい場合はチェックしな</string>
<string name="count_search_title">検索結果の件数表示</string> <string name="count_search_title">検索結果の件数表示</string>
<string name="count_search_summary">表示を早くしたい場合はチェックをはずしてくださ</string> <string name="count_search_summary">表示を速くしたい場合はチェックしな</string>
<!-- NEW: <string name="hide_special_accounts_title">Hide special accounts</string>--> <string name="hide_special_accounts_title">特殊なアカウントを隠す</string>
<!-- NEW: <string name="hide_special_accounts_summary">Hide the unified inbox and all messages accounts</string>--> <string name="hide_special_accounts_summary">統合フォルダと全メッセージを隠す</string>
<string name="search_title"><xliff:g id="search_name">%s</xliff:g> <xliff:g id="modifier">%s</xliff:g></string> <string name="search_title"><xliff:g id="search_name">%s</xliff:g> <xliff:g id="modifier">%s</xliff:g></string>
<string name="flagged_modifier"> - スター</string> <string name="flagged_modifier"> - スター</string>
@ -1018,12 +1018,12 @@ K-9 Mail セットアップにようこそ。\nK-9 は標準のAndroidメール
<string name="dialog_confirm_delete_confirm_button">削除する</string> <string name="dialog_confirm_delete_confirm_button">削除する</string>
<string name="dialog_confirm_delete_cancel_button">削除しない</string> <string name="dialog_confirm_delete_cancel_button">削除しない</string>
<!-- NEW: <string name="dialog_confirm_spam_title">Confirm move to spam folder</string>--> <string name="dialog_confirm_spam_title">迷惑メールフォルダへの移動の確認</string>
<!-- NEW: <string name="dialog_confirm_spam_message">Do you really want to move this message to the spam folder?</string>--> <string name="dialog_confirm_spam_message">本当にこのメッセージを迷惑メールフォルダに移動しますか?</string>
<!-- NEW: <string name="dialog_confirm_spam_confirm_button">Yes</string>--> <string name="dialog_confirm_spam_confirm_button">はい</string>
<!-- NEW: <string name="dialog_confirm_spam_cancel_button">No</string>--> <string name="dialog_confirm_spam_cancel_button">いいえ</string>
<!-- NEW: <string name="dialog_attachment_progress_title">Downloading attachment</string>--> <string name="dialog_attachment_progress_title">添付ファイルをダウンロードしています</string>
<string name="debug_logging_enabled">Android のログにデバッグ用のログを出力するように設定しました。</string> <string name="debug_logging_enabled">Android のログにデバッグ用のログを出力するように設定しました。</string>
@ -1031,6 +1031,9 @@ K-9 Mail セットアップにようこそ。\nK-9 は標準のAndroidメール
<string name="messagelist_sent_cc_me_sigil">\u203a</string> <string name="messagelist_sent_cc_me_sigil">\u203a</string>
<string name="error_unable_to_connect">接続できません</string> <string name="error_unable_to_connect">接続できません</string>
<!-- NEW: <string name="account_unavailable">Account \"<xliff:g id="account">%s</xliff:g>\" is unavailable; check storage</string>--> <string name="account_unavailable">アカウント \"<xliff:g id="account">%s</xliff:g>\" は利用できません。ストレージを確認してください。</string>
<string name="settings_attachment_default_path">添付ファイルの保存先</string>
<string name="attachment_save_title">添付ファイルの保存先</string>
<string name="attachment_save_desc">ファイルブラウザがインストールされていません。添付ファイルの保存場所を直接入力してください。</string>
</resources> </resources>

1043
res/values-ko/strings.xml Normal file

File diff suppressed because it is too large Load Diff

View File

@ -3,14 +3,14 @@
<string name="app_name">K-9 Mail</string> <string name="app_name">K-9 Mail</string>
<string name="beta_app_name">K-9 Mail BETA</string> <string name="beta_app_name">K-9 Mail BETA</string>
<string name="app_authors">Google, The K-9 Dog Walkers.</string> <string name="app_authors">Google, The K-9 Dog Walkers.</string>
<!-- NEW: <string name="app_copyright_fmt">Copyright 2008-<xliff:g>%s</xliff:g> The K-9 Dog Walkers. Portions Copyright 2006-<xliff:g>%s</xliff:g> the Android Open Source Project.</string>--> <string name="app_copyright_fmt">Copyright 2008-<xliff:g>%s</xliff:g> The K-9 Dog Walkers. Portions Copyright 2006-<xliff:g>%s</xliff:g> the Android Open Source Project.</string>
<!-- NEW: <string name="app_license">Licensed under the Apache License, Version 2.0.</string>--> <string name="app_license">Licensed under the Apache License, Version 2.0.</string>
<string name="app_authors_fmt">Auteurs: <xliff:g id="app_authors">%s</xliff:g></string> <string name="app_authors_fmt">Auteurs: <xliff:g id="app_authors">%s</xliff:g></string>
<string name="app_revision_url">http://code.google.com/p/k9mail/wiki/ReleaseNotes</string> <string name="app_revision_url">http://code.google.com/p/k9mail/wiki/ReleaseNotes</string>
<string name="app_revision_fmt">Revisie Informatie: <xliff:g id="app_revision_url">%s</xliff:g></string> <string name="app_revision_fmt">Revisie Informatie: <xliff:g id="app_revision_url">%s</xliff:g></string>
<string name="app_webpage_url">http://code.google.com/p/k9mail/</string> <string name="app_webpage_url">http://code.google.com/p/k9mail/</string>
<!-- NEW: <string name="app_libraries">We\'re using the following third-party libraries: <xliff:g id="app_libraries_list">%s</xliff:g></string>--> <string name="app_libraries">De volgende externe bibliotheken worden gebruikt: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<!-- NEW: <string name="app_emoji_icons">Emoji icons: <xliff:g id="app_emoji_icons_link">%s</xliff:g></string>--> <string name="app_emoji_icons">Emoji icons: <xliff:g id="app_emoji_icons_link">%s</xliff:g></string>
<string name="read_attachment_label">Lees Email bijlage</string> <string name="read_attachment_label">Lees Email bijlage</string>
<string name="read_attachment_desc">Sta deze applicatie toe de bijlage van emails te lezen.</string> <string name="read_attachment_desc">Sta deze applicatie toe de bijlage van emails te lezen.</string>
@ -42,15 +42,15 @@
<string name="folder_progress">\u0020<xliff:g id="completed">%s</xliff:g>/<xliff:g id="total">%s</xliff:g></string> <string name="folder_progress">\u0020<xliff:g id="completed">%s</xliff:g>/<xliff:g id="total">%s</xliff:g></string>
<string name="status_next_poll">\u0020(Volgende poll @ <xliff:g id="nexttime">%s</xliff:g>)</string> <string name="status_next_poll">\u0020(Volgende poll @ <xliff:g id="nexttime">%s</xliff:g>)</string>
<!-- NEW: <string name="status_syncing_off">\u0020(Syncing disabled)</string>--> <string name="status_syncing_off">\u0020(Synchroniseren uitgeschakeld)</string>
<!-- Actions will be used as buttons and in menu items --> <!-- Actions will be used as buttons and in menu items -->
<string name="next_action">Volgende</string> <!-- Used as part of a multi-step process --> <string name="next_action">Volgende</string> <!-- Used as part of a multi-step process -->
<!-- NEW: <string name="previous_action">Previous</string>--> <!-- Used as part of a multi-step process --> <string name="previous_action">Vorige</string> <!-- Used as part of a multi-step process -->
<string name="okay_action">OK</string> <!-- User to confirm acceptance of dialog boxes, warnings, errors, etc. --> <string name="okay_action">OK</string> <!-- User to confirm acceptance of dialog boxes, warnings, errors, etc. -->
<string name="cancel_action">Annuleer</string> <string name="cancel_action">Annuleer</string>
<string name="send_action">Verzenden</string> <string name="send_action">Verzenden</string>
<!-- NEW: <string name="send_again_action">Send Again</string>--> <string name="send_again_action">Opnieuw Verzenden</string>
<string name="select_action">Selecteren</string> <string name="select_action">Selecteren</string>
<string name="deselect_action">Annuleer selectie</string> <string name="deselect_action">Annuleer selectie</string>
<string name="reply_action">Antwoorden</string> <string name="reply_action">Antwoorden</string>
@ -117,7 +117,7 @@
<string name="dump_settings_action">Gooi instellingen weg</string> <string name="dump_settings_action">Gooi instellingen weg</string>
<string name="empty_trash_action">Prullenbak legen</string> <string name="empty_trash_action">Prullenbak legen</string>
<string name="expunge_action">Wissen</string> <string name="expunge_action">Wissen</string>
<!-- NEW: <string name="clear_local_folder_action">Clear local messages</string>--> <string name="clear_local_folder_action">Lokale berichten wissen</string>
<string name="set_sort_action">Kies sortering</string> <string name="set_sort_action">Kies sortering</string>
<string name="reverse_sort_action">Omgekeerd sorteren</string> <string name="reverse_sort_action">Omgekeerd sorteren</string>
<string name="about_action">Over</string> <string name="about_action">Over</string>
@ -127,7 +127,7 @@
<string name="folder_context_menu_title">Map opties</string> <string name="folder_context_menu_title">Map opties</string>
<string name="general_no_subject">(Geen onderwerp)</string> <!-- Shown in place of the subject when a message has no subject. Showing this in parentheses is customary. --> <string name="general_no_subject">(Geen onderwerp)</string> <!-- Shown in place of the subject when a message has no subject. Showing this in parentheses is customary. -->
<!-- NEW: <string name="general_no_date">No date</string>--> <string name="general_no_date">Geen datum</string>
<string name="general_no_sender">Geen afzender</string> <string name="general_no_sender">Geen afzender</string>
<string name="status_loading">Polling</string> <string name="status_loading">Polling</string>
<string name="status_loading_folder">(Poll <xliff:g id="folder">%s</xliff:g><xliff:g id="progress">%s</xliff:g>)</string> <string name="status_loading_folder">(Poll <xliff:g id="folder">%s</xliff:g><xliff:g id="progress">%s</xliff:g>)</string>
@ -259,14 +259,14 @@ Welkom bij K-9 Mail setup. K-9 is een open source mail cliënt voor Android, ge
<string name="message_compose_content_hint">Bericht tekst</string> <string name="message_compose_content_hint">Bericht tekst</string>
<string name="message_compose_quote_header_separator">-------- Origineel bericht --------</string> <string name="message_compose_quote_header_separator">-------- Origineel bericht --------</string>
<string name="message_compose_quote_header_subject">Subject:</string> <string name="message_compose_quote_header_subject">Subject:</string>
<!-- NEW: <string name="message_compose_quote_header_send_date">Sent:</string>--> <string name="message_compose_quote_header_send_date">Verzonden:</string>
<string name="message_compose_quote_header_from">From:</string> <string name="message_compose_quote_header_from">From:</string>
<string name="message_compose_quote_header_to">To:</string> <string name="message_compose_quote_header_to">To:</string>
<string name="message_compose_quote_header_cc">CC:</string> <string name="message_compose_quote_header_cc">CC:</string>
<string name="message_compose_reply_header_fmt"><xliff:g id="sender">%s</xliff:g> wrote:\n\n</string> <string name="message_compose_reply_header_fmt"><xliff:g id="sender">%s</xliff:g> wrote:\n\n</string>
<string name="message_compose_quoted_text_label">Ge-quote tekst</string> <string name="message_compose_quoted_text_label">Ge-quote tekst</string>
<string name="message_compose_error_no_recipients">Minimaal 1 ontvanger kiezen.</string> <string name="message_compose_error_no_recipients">Minimaal 1 ontvanger kiezen.</string>
<!-- NEW: <string name="error_contact_address_not_found">No email address could be found.</string>--> <string name="error_contact_address_not_found">Geen email adres gevonden.</string>
<string name="message_compose_downloading_attachments_toast">Sommige bijlage zijn niet gedownload. Deze worden automatisch gedownload voor dat dit bericht is verzonden.</string> <string name="message_compose_downloading_attachments_toast">Sommige bijlage zijn niet gedownload. Deze worden automatisch gedownload voor dat dit bericht is verzonden.</string>
<string name="message_compose_attachments_skipped_toast">Sommige bijlagen kunnen niet worden doorgestuurd, omdat ze niet zijn gedownload.</string> <string name="message_compose_attachments_skipped_toast">Sommige bijlagen kunnen niet worden doorgestuurd, omdat ze niet zijn gedownload.</string>
@ -324,11 +324,11 @@ Welkom bij K-9 Mail setup. K-9 is een open source mail cliënt voor Android, ge
<string name="global_settings_checkbox_summary">Laat altijd multi-selecteer selectieboxen zien</string> <string name="global_settings_checkbox_summary">Laat altijd multi-selecteer selectieboxen zien</string>
<string name="global_settings_touchable_label">Berichten voorbeelden</string> <string name="global_settings_touchable_label">Berichten voorbeelden</string>
<string name="global_settings_touchable_summary">Ruimere lijst items met bericht voorbeelden</string> <string name="global_settings_touchable_summary">Ruimere lijst items met bericht voorbeelden</string>
<!-- NEW: <string name="global_settings_preview_lines_label">Preview lines</string>--> <string name="global_settings_preview_lines_label">Preview regels</string>
<!-- NEW: <string name="global_settings_show_correspondent_names_label">Show correspondent names</string>--> <string name="global_settings_show_correspondent_names_label">Toon naam bij bericht</string>
<!-- NEW: <string name="global_settings_show_correspondent_names_summary">Show correspondent names rather than their email addresses</string>--> <string name="global_settings_show_correspondent_names_summary">Geef bij voorkeur naam van afzender/geadresseerde weer i.p.v. email adres</string>
<!-- NEW: <string name="global_settings_show_contact_name_label">Show contact names</string>--> <string name="global_settings_show_contact_name_label">Toon naam uit contactenlijst</string>
<!-- NEW: <string name="global_settings_show_contact_name_summary">Use recipient names from Contacts when available</string>--> <string name="global_settings_show_contact_name_summary">Geef de naam weer uit het adresboek</string>
<string name="global_settings_registered_name_color_label">Kleuren contacten</string> <string name="global_settings_registered_name_color_label">Kleuren contacten</string>
<string name="global_settings_registered_name_color_default">Niet kleuren van namen in uw lijst met contactpersonen</string> <string name="global_settings_registered_name_color_default">Niet kleuren van namen in uw lijst met contactpersonen</string>
<string name="global_settings_registered_name_color_changed">Kleuren van namen in uw lijst met contactpersonen</string> <string name="global_settings_registered_name_color_changed">Kleuren van namen in uw lijst met contactpersonen</string>
@ -343,17 +343,17 @@ Welkom bij K-9 Mail setup. K-9 is een open source mail cliënt voor Android, ge
<string name="global_settings_confirm_action_archive">Archief</string> <string name="global_settings_confirm_action_archive">Archief</string>
<string name="global_settings_confirm_action_delete">Verwijder (alleen berichten bekijken)</string> <string name="global_settings_confirm_action_delete">Verwijder (alleen berichten bekijken)</string>
<string name="global_settings_confirm_action_spam">Spam</string> <string name="global_settings_confirm_action_spam">Spam</string>
<!-- NEW: <string name="global_settings_confirm_action_mark_all_as_read">Mark all as read</string>--> <string name="global_settings_confirm_action_mark_all_as_read">Markeer alles als gelezen</string>
<string name="global_settings_confirm_action_send">Verzonden</string> <string name="global_settings_confirm_action_send">Verzonden</string>
<string name="global_settings_privacy_mode_title">Lock-screen meldingen</string> <string name="global_settings_privacy_mode_title">Lock-screen meldingen</string>
<string name="global_settings_privacy_mode_summary">Niet weergegeven onderwerp van het bericht in de notificatie bar als het systeem is vergrendeld</string> <string name="global_settings_privacy_mode_summary">Niet weergegeven onderwerp van het bericht in de notificatie bar als het systeem is vergrendeld</string>
<!-- NEW: <string name="quiet_time">Quiet Time</string>--> <string name="quiet_time">Stilteperiode</string>
<!-- NEW: <string name="quiet_time_description">Disable ringing, buzzing and flashing at night</string>--> <string name="quiet_time_description">Schakel beltoon, vibratie en leds uit gedurende de nacht</string>
<!-- NEW: <string name="quiet_time_starts">Quiet Time starts</string>--> <string name="quiet_time_starts">Stilteperiode start</string>
<!-- NEW: <string name="quiet_time_ends">Quiet Time ends</string>--> <string name="quiet_time_ends">Stilteperiode eindigt</string>
<string name="account_setup_basics_title">Een nieuwe account instellen</string> <string name="account_setup_basics_title">Een nieuwe account instellen</string>
@ -370,8 +370,8 @@ Welkom bij K-9 Mail setup. K-9 is een open source mail cliënt voor Android, ge
<string name="account_setup_check_settings_retr_info_msg">Ophalen account informatie\u2026</string> <string name="account_setup_check_settings_retr_info_msg">Ophalen account informatie\u2026</string>
<string name="account_setup_check_settings_check_incoming_msg">Controleren van inkomende serverinstellingen\u2026</string> <string name="account_setup_check_settings_check_incoming_msg">Controleren van inkomende serverinstellingen\u2026</string>
<string name="account_setup_check_settings_check_outgoing_msg">Controleren van uitgaande serverinstellingen\u2026</string> <string name="account_setup_check_settings_check_outgoing_msg">Controleren van uitgaande serverinstellingen\u2026</string>
<!-- NEW: <string name="account_setup_check_settings_authenticate">Authenticating\u2026</string>--> <string name="account_setup_check_settings_authenticate">Authenticatie\u2026</string>
<!-- NEW: <string name="account_setup_check_settings_fetch">Fetching account settings\u2026</string>--> <string name="account_setup_check_settings_fetch">Accountinstellingen worden opgehaald\u2026</string>
<string name="account_setup_check_settings_finishing_msg">Afronden\u2026</string> <string name="account_setup_check_settings_finishing_msg">Afronden\u2026</string>
<string name="account_setup_check_settings_canceling_msg">Annuleren\u2026</string> <string name="account_setup_check_settings_canceling_msg">Annuleren\u2026</string>
@ -417,10 +417,10 @@ Welkom bij K-9 Mail setup. K-9 is een open source mail cliënt voor Android, ge
<string name="account_setup_incoming_save_all_headers_title">Downloaden van email headers</string> <string name="account_setup_incoming_save_all_headers_title">Downloaden van email headers</string>
<string name="account_setup_incoming_save_all_headers_label">Sla alle headers lokaal op</string> <string name="account_setup_incoming_save_all_headers_label">Sla alle headers lokaal op</string>
<!-- NEW: <string name="local_storage_provider_external_label">External storage (SD card)</string>--> <string name="local_storage_provider_external_label">Externe opslag (SD kaart)</string>
<!-- NEW: <string name="local_storage_provider_internal_label">Regular internal storage</string>--> <string name="local_storage_provider_internal_label">Reguliere interne opslag</string>
<!-- NEW: <string name="local_storage_provider_samsunggalaxy_label">%1$s additional internal storage</string>--> <string name="local_storage_provider_samsunggalaxy_label">%1$s extra interne opslag</string>
<!-- NEW: <string name="local_storage_provider_label">Storage location</string>--> <string name="local_storage_provider_label">Opslag locatie</string>
<string name="account_setup_expunge_policy_label">Wissen berichten</string> <string name="account_setup_expunge_policy_label">Wissen berichten</string>
<string name="account_setup_expunge_policy_immediately">Onmiddellijk na verwijderen of verplaatsen</string> <string name="account_setup_expunge_policy_immediately">Onmiddellijk na verwijderen of verplaatsen</string>
@ -517,7 +517,7 @@ Welkom bij K-9 Mail setup. K-9 is een open source mail cliënt voor Android, ge
<string name="account_setup_options_mail_display_count_250">250 berichten</string> <string name="account_setup_options_mail_display_count_250">250 berichten</string>
<string name="account_setup_options_mail_display_count_500">500 berichten</string> <string name="account_setup_options_mail_display_count_500">500 berichten</string>
<string name="account_setup_options_mail_display_count_1000">1000 berichten</string> <string name="account_setup_options_mail_display_count_1000">1000 berichten</string>
<!-- NEW: <string name="account_setup_options_mail_display_count_all">all messages</string>--> <string name="account_setup_options_mail_display_count_all">alle berichten</string>
<string name="move_copy_cannot_copy_unsynced_message">Kan bericht niet kopiëren of verplaatsen omdat deze niet gesynchroniseerd is met de server</string> <string name="move_copy_cannot_copy_unsynced_message">Kan bericht niet kopiëren of verplaatsen omdat deze niet gesynchroniseerd is met de server</string>
@ -529,7 +529,7 @@ Welkom bij K-9 Mail setup. K-9 is een open source mail cliënt voor Android, ge
<string name="account_setup_failed_dlg_edit_details_action">Aanpassen details</string> <string name="account_setup_failed_dlg_edit_details_action">Aanpassen details</string>
<string name="account_setup_failed_dlg_continue_action">Doorgaan</string> <string name="account_setup_failed_dlg_continue_action">Doorgaan</string>
<!-- NEW: <string name="account_settings_push_advanced_title">Advanced</string>--> <string name="account_settings_push_advanced_title">Geavanceerd</string>
<string name="account_settings_title_fmt">Algemene instellingen</string> <string name="account_settings_title_fmt">Algemene instellingen</string>
<string name="account_settings_default">Standaard account</string> <string name="account_settings_default">Standaard account</string>
<string name="account_settings_default_label">Standaard account</string> <string name="account_settings_default_label">Standaard account</string>
@ -544,8 +544,8 @@ Welkom bij K-9 Mail setup. K-9 is een open source mail cliënt voor Android, ge
<string name="account_settings_notify_self_summary">Notificatie ook voor mail verzonden vanaf een identiteit</string> <string name="account_settings_notify_self_summary">Notificatie ook voor mail verzonden vanaf een identiteit</string>
<string name="account_settings_notification_opens_unread_label">Notificatie opent ongelezen berichten</string> <string name="account_settings_notification_opens_unread_label">Notificatie opent ongelezen berichten</string>
<string name="account_settings_notification_opens_unread_summary">Zoekt voor ongelezen berichten wanneer Notificatie is geopend</string> <string name="account_settings_notification_opens_unread_summary">Zoekt voor ongelezen berichten wanneer Notificatie is geopend</string>
<!-- NEW: <string name="account_settings_notification_unread_count_label">Show unread count</string>--> <string name="account_settings_notification_unread_count_label">Toon aantal ongelezen</string>
<!-- NEW: <string name="account_settings_notification_unread_count_summary">Show the number of unread messages in the notification bar.</string>--> <string name="account_settings_notification_unread_count_summary">Toon het aantal ongelezen berichten in de \'notification bar\'.</string>
<string name="account_settings_hide_buttons_label">Scroll navigatie knoppen</string> <string name="account_settings_hide_buttons_label">Scroll navigatie knoppen</string>
<string name="account_settings_hide_buttons_never">Nooit</string> <string name="account_settings_hide_buttons_never">Nooit</string>
@ -566,16 +566,16 @@ Welkom bij K-9 Mail setup. K-9 is een open source mail cliënt voor Android, ge
<string name="account_settings_reply_after_quote_label">Antwoorden na quote</string> <string name="account_settings_reply_after_quote_label">Antwoorden na quote</string>
<string name="account_settings_reply_after_quote_summary">Wanneer u antwoord op berichten, zal het originele bericht boven uw antwoord staan.</string> <string name="account_settings_reply_after_quote_summary">Wanneer u antwoord op berichten, zal het originele bericht boven uw antwoord staan.</string>
<!-- NEW: <string name="account_settings_message_format_label">Message Format</string>--> <string name="account_settings_message_format_label">Berichtopmaak</string>
<!-- NEW: <string name="account_settings_message_format_text">Plain Text (images and formatting will be removed)</string>--> <string name="account_settings_message_format_text">Platte Tekst (plaatjes en formattering worden verwijderd)</string>
<!-- NEW: <string name="account_settings_message_format_html">HTML (images and formatting are preserved)</string>--> <string name="account_settings_message_format_html">HTML (plaatjes en formattering blijven behouden)</string>
<!-- NEW: <string name="account_settings_quote_style_label">Reply quoting style</string>--> <string name="account_settings_quote_style_label">Quotestijl bij antwoorden</string>
<!-- NEW: <string name="account_settings_quote_style_prefix">Prefix (like Gmail, Pine)</string>--> <string name="account_settings_quote_style_prefix">Prefix (zoals Gmail, Pine)</string>
<!-- NEW: <string name="account_settings_quote_style_header">Header (like Outlook, Yahoo!, Hotmail)</string>--> <string name="account_settings_quote_style_header">Header (zoals Outlook, Yahoo!, Hotmail)</string>
<!-- NEW: <string name="account_settings_general_title">General settings</string>--> <string name="account_settings_general_title">Algemeen</string>
<!-- NEW: <string name="account_settings_display_prefs_title">Display</string>--> <string name="account_settings_display_prefs_title">Scherm</string>
<string name="account_settings_sync">Synchroniseren van mappen</string> <string name="account_settings_sync">Synchroniseren van mappen</string>
<string name="account_settings_folders">Mappen</string> <string name="account_settings_folders">Mappen</string>
<string name="account_settings_message_lists">Lijst berichten</string> <string name="account_settings_message_lists">Lijst berichten</string>
@ -591,7 +591,7 @@ Welkom bij K-9 Mail setup. K-9 is een open source mail cliënt voor Android, ge
<string name="account_settings_mail_check_frequency_label">Mappen poll controleer frequentie</string> <string name="account_settings_mail_check_frequency_label">Mappen poll controleer frequentie</string>
<string name="account_settings_second_class_check_frequency_label">2e klasse controleer frequentie</string> <string name="account_settings_second_class_check_frequency_label">2e klasse controleer frequentie</string>
<!-- NEW: <string name="account_settings_storage_title">Storage</string>--> <string name="account_settings_storage_title">Opslag</string>
<string name="account_settings_color_label">Account kleur</string> <string name="account_settings_color_label">Account kleur</string>
@ -752,7 +752,7 @@ Welkom bij K-9 Mail setup. K-9 is een open source mail cliënt voor Android, ge
<string name="choose_identity">Kies identiteit</string> <string name="choose_identity">Kies identiteit</string>
<string name="choose_identity_title">Kies identiteit</string> <string name="choose_identity_title">Kies identiteit</string>
<string name="choose_account_title">Kies account/identiteit</string> <string name="choose_account_title">Kies account/identiteit</string>
<!-- NEW: <string name="send_as">Send as</string>--> <string name="send_as">Verzenden als</string>
<string name="no_identities">Ga naar Account Instellingen -&gt; Beheer identiteiten om identiteiten aan te maken</string> <string name="no_identities">Ga naar Account Instellingen -&gt; Beheer identiteiten om identiteiten aan te maken</string>
@ -794,7 +794,7 @@ Welkom bij K-9 Mail setup. K-9 is een open source mail cliënt voor Android, ge
<string name="provider_note_live">Alleen sommige \"Plus\" accounts staan POP access <string name="provider_note_live">Alleen sommige \"Plus\" accounts staan POP access
toe om verbinding te krijgen met dit programma. Als het niet mogelijk is om in te loggen met de juiste gebruikersnaam en wachtwoord, heb je misschien geen betaalde \"Plus\" account. Start de webbrowser om de toegang tot deze e-mailaccount te krijgen.</string> toe om verbinding te krijgen met dit programma. Als het niet mogelijk is om in te loggen met de juiste gebruikersnaam en wachtwoord, heb je misschien geen betaalde \"Plus\" account. Start de webbrowser om de toegang tot deze e-mailaccount te krijgen.</string>
<!-- NEW: <string name="provider_note_yahoojp">If you would like to use POP3 for this provider, You should permit to use POP3 on Yahoo mail settings page.</string>--> <string name="provider_note_yahoojp">Als je POP3 wilt gebruiken, moet je het geebruik van POP3 activeren op de Yahoo mail settings pagina.</string>
<string name="account_setup_failed_dlg_invalid_certificate_title">Onbekend Certificaat</string> <string name="account_setup_failed_dlg_invalid_certificate_title">Onbekend Certificaat</string>
<string name="account_setup_failed_dlg_invalid_certificate_accept">Accepteer Sleutel</string> <string name="account_setup_failed_dlg_invalid_certificate_accept">Accepteer Sleutel</string>
@ -831,21 +831,21 @@ Welkom bij K-9 Mail setup. K-9 is een open source mail cliënt voor Android, ge
<string name="setting_theme_dark">Donker</string> <string name="setting_theme_dark">Donker</string>
<string name="setting_theme_light">Licht</string> <string name="setting_theme_light">Licht</string>
<string name="display_preferences">Algemene instellingen</string> <string name="display_preferences">Algemene instellingen</string>
<!-- NEW: <string name="global_preferences">Global</string>--> <string name="global_preferences">Globaal</string>
<string name="debug_preferences">Verwijderen van fouten</string> <string name="debug_preferences">Verwijderen van fouten</string>
<!-- NEW: <string name="privacy_preferences">Privacy</string>--> <string name="privacy_preferences">Privacy</string>
<!-- NEW: <string name="network_preferences">Network</string>--> <string name="network_preferences">Netwerk</string>
<!-- NEW: <string name="interaction_preferences">Interaction</string>--> <string name="interaction_preferences">Interactie</string>
<string name="accountlist_preferences">Account Lijst</string> <string name="accountlist_preferences">Account Lijst</string>
<string name="messagelist_preferences">Berichten Lijst</string> <string name="messagelist_preferences">Berichten Lijst</string>
<string name="messageview_preferences">Berichten</string> <string name="messageview_preferences">Berichten</string>
<string name="settings_theme_label">Thema</string> <string name="settings_theme_label">Thema</string>
<string name="settings_language_label">Taal</string> <string name="settings_language_label">Taal</string>
<!-- NEW: <string name="settings_messageview_mobile_layout_label">Single-column layout</string>--> <string name="settings_messageview_mobile_layout_label">1-kolom layout</string>
<!-- NEW: <string name="settings_messageview_mobile_layout_summary">Reformat HTML messages for smaller screens</string>--> <string name="settings_messageview_mobile_layout_summary">Herschik HTML berichten voor kleinere schermen</string>
<!-- NEW: <string name="settings_messageview_zoom_controls_label">System zoom controls</string>--> <string name="settings_messageview_zoom_controls_label">Apparaat zoom</string>
<!-- NEW: <string name="settings_messageview_zoom_controls_summary">Enable zoom widgets or pinch-zoom if your device supports it</string>--> <string name="settings_messageview_zoom_controls_summary">Gebruik zoom widgets of pinch-zoom als het apparaat dat ondersteunt</string>
@ -899,8 +899,8 @@ Welkom bij K-9 Mail setup. K-9 is een open source mail cliënt voor Android, ge
<string name="gestures_title">Gebaren</string> <string name="gestures_title">Gebaren</string>
<string name="gestures_summary">Accepteer gebaren sturing</string> <string name="gestures_summary">Accepteer gebaren sturing</string>
<!-- NEW: <string name="compact_layouts_title">Compact layouts</string>--> <string name="compact_layouts_title">Compacte layout</string>
<!-- NEW: <string name="compact_layouts_summary">Adjust layouts to display more on each page</string>--> <string name="compact_layouts_summary">Pas layout aan zodat er meer op een pagina past</string>
<string name="volume_navigation_title">Volume op/neer navigatie</string> <string name="volume_navigation_title">Volume op/neer navigatie</string>
<string name="volume_navigation_summary">Spring tussen items door gebruik van de volumeknoppen</string> <string name="volume_navigation_summary">Spring tussen items door gebruik van de volumeknoppen</string>
@ -919,8 +919,8 @@ Welkom bij K-9 Mail setup. K-9 is een open source mail cliënt voor Android, ge
<string name="count_search_title">Tel zoek resultaten</string> <string name="count_search_title">Tel zoek resultaten</string>
<string name="count_search_summary">Zet uit voor sneller beeldscherm</string> <string name="count_search_summary">Zet uit voor sneller beeldscherm</string>
<!-- NEW: <string name="hide_special_accounts_title">Hide special accounts</string>--> <string name="hide_special_accounts_title">Verberg speciale accounts</string>
<!-- NEW: <string name="hide_special_accounts_summary">Hide the unified inbox and all messages accounts</string>--> <string name="hide_special_accounts_summary">Verberg de gecombineerde inbox and alle berichtaccounts</string>
<string name="search_title"><xliff:g id="search_name">%s</xliff:g> <xliff:g id="modifier">%s</xliff:g></string> <string name="search_title"><xliff:g id="search_name">%s</xliff:g> <xliff:g id="modifier">%s</xliff:g></string>
<string name="flagged_modifier"> - Starred</string> <string name="flagged_modifier"> - Starred</string>
@ -960,7 +960,7 @@ Welkom bij K-9 Mail setup. K-9 is een open source mail cliënt voor Android, ge
<string name="font_size_message_list_subject">Bericht onderwerp</string> <string name="font_size_message_list_subject">Bericht onderwerp</string>
<string name="font_size_message_list_sender">bericht afzender</string> <string name="font_size_message_list_sender">bericht afzender</string>
<string name="font_size_message_list_date">Bericht datum</string> <string name="font_size_message_list_date">Bericht datum</string>
<!-- NEW: <string name="font_size_message_list_preview">Preview</string>--> <string name="font_size_message_list_preview">Preview</string>
<string name="font_size_message_view">Beeld berichten</string> <string name="font_size_message_view">Beeld berichten</string>
<string name="font_size_message_view_sender">bericht afzender</string> <string name="font_size_message_view_sender">bericht afzender</string>
@ -1022,19 +1022,22 @@ Welkom bij K-9 Mail setup. K-9 is een open source mail cliënt voor Android, ge
<string name="dialog_confirm_delete_confirm_button">Verwijder</string> <string name="dialog_confirm_delete_confirm_button">Verwijder</string>
<string name="dialog_confirm_delete_cancel_button">Niet verwijderen</string> <string name="dialog_confirm_delete_cancel_button">Niet verwijderen</string>
<!-- NEW: <string name="dialog_confirm_spam_title">Confirm move to spam folder</string>--> <string name="dialog_confirm_spam_title">Bevestig verplaatsing naar spam map</string>
<!-- NEW: <string name="dialog_confirm_spam_message">Do you really want to move this message to the spam folder?</string>--> <string name="dialog_confirm_spam_message">Wil je dit bericht echt verplaatsen naar de spam map?</string>
<!-- NEW: <string name="dialog_confirm_spam_confirm_button">Yes</string>--> <string name="dialog_confirm_spam_confirm_button">Ja</string>
<!-- NEW: <string name="dialog_confirm_spam_cancel_button">No</string>--> <string name="dialog_confirm_spam_cancel_button">Nee</string>
<!-- NEW: <string name="dialog_attachment_progress_title">Downloading attachment</string>--> <string name="dialog_attachment_progress_title">Bijlage wordt opgehaald</string>
<string name="debug_logging_enabled">Debug logging van Android logging systeem ingeschakeld</string> <string name="debug_logging_enabled">Debug logging van Android logging systeem ingeschakeld</string>
<!-- NEW: <string name="messagelist_sent_to_me_sigil">»</string>--> <string name="messagelist_sent_to_me_sigil">»</string> <!-- why is this language dependent? -->
<!-- NEW: <string name="messagelist_sent_cc_me_sigil"></string>--> <string name="messagelist_sent_cc_me_sigil"></string>
<!-- NEW: <string name="error_unable_to_connect">Unable to connect.</string>--> <string name="error_unable_to_connect">Kan geen verbinding maken.</string>
<!-- NEW: <string name="account_unavailable">Account \"<xliff:g id="account">%s</xliff:g>\" is unavailable; check storage</string>--> <string name="account_unavailable">Account \"<xliff:g id="account">%s</xliff:g>\" is niet beschikbaar; controleer opslag</string>
<string name="settings_attachment_default_path">Sla bijlagen op naar...</string>
<string name="attachment_save_title">Sla bijlage op</string>
<string name="attachment_save_desc">Geen bestandsverkenner gevonden. Waar wil je deze bijlage opslaan?</string>
</resources> </resources>

View File

@ -1049,4 +1049,7 @@ Witaj w K-9 Mail, darmowym programie pocztowym dla systemu Android. Najistotniej
<string name="account_unavailable">Konto \"<xliff:g id="account">%s</xliff:g>\" jest niedostępne; sprawdź pamięc</string> <string name="account_unavailable">Konto \"<xliff:g id="account">%s</xliff:g>\" jest niedostępne; sprawdź pamięc</string>
<!-- NEW: <string name="settings_attachment_default_path">Save attachments to...</string>-->
<!-- NEW: <string name="attachment_save_title">Save attachment</string>-->
<!-- NEW: <string name="attachment_save_desc">No file browser found. Where would you like to save this attachment?</string>-->
</resources> </resources>

View File

@ -1034,4 +1034,7 @@ Bem-vindo à configuração do K-9 Mail. K-9 é um cliente de e-mail com código
<!-- NEW: <string name="account_unavailable">Account \"<xliff:g id="account">%s</xliff:g>\" is unavailable; check storage</string>--> <!-- NEW: <string name="account_unavailable">Account \"<xliff:g id="account">%s</xliff:g>\" is unavailable; check storage</string>-->
<!-- NEW: <string name="settings_attachment_default_path">Save attachments to...</string>-->
<!-- NEW: <string name="attachment_save_title">Save attachment</string>-->
<!-- NEW: <string name="attachment_save_desc">No file browser found. Where would you like to save this attachment?</string>-->
</resources> </resources>

View File

@ -1031,4 +1031,7 @@
<!-- NEW: <string name="account_unavailable">Account \"<xliff:g id="account">%s</xliff:g>\" is unavailable; check storage</string>--> <!-- NEW: <string name="account_unavailable">Account \"<xliff:g id="account">%s</xliff:g>\" is unavailable; check storage</string>-->
<!-- NEW: <string name="settings_attachment_default_path">Save attachments to...</string>-->
<!-- NEW: <string name="attachment_save_title">Save attachment</string>-->
<!-- NEW: <string name="attachment_save_desc">No file browser found. Where would you like to save this attachment?</string>-->
</resources> </resources>

View File

@ -1040,4 +1040,7 @@ Välkommen till installationen av K-9 E-post. K-9 är en e-postklient med öppen
<!-- NEW: <string name="account_unavailable">Account \"<xliff:g id="account">%s</xliff:g>\" is unavailable; check storage</string>--> <!-- NEW: <string name="account_unavailable">Account \"<xliff:g id="account">%s</xliff:g>\" is unavailable; check storage</string>-->
<!-- NEW: <string name="settings_attachment_default_path">Save attachments to...</string>-->
<!-- NEW: <string name="attachment_save_title">Save attachment</string>-->
<!-- NEW: <string name="attachment_save_desc">No file browser found. Where would you like to save this attachment?</string>-->
</resources> </resources>

View File

@ -1021,4 +1021,7 @@
<!-- NEW: <string name="account_unavailable">Account \"<xliff:g id="account">%s</xliff:g>\" is unavailable; check storage</string>--> <!-- NEW: <string name="account_unavailable">Account \"<xliff:g id="account">%s</xliff:g>\" is unavailable; check storage</string>-->
<!-- NEW: <string name="settings_attachment_default_path">Save attachments to...</string>-->
<!-- NEW: <string name="attachment_save_title">Save attachment</string>-->
<!-- NEW: <string name="attachment_save_desc">No file browser found. Where would you like to save this attachment?</string>-->
</resources> </resources>

View File

@ -501,6 +501,7 @@
<item>zh_CN</item> <item>zh_CN</item>
<item>fi</item> <item>fi</item>
<item>sv</item> <item>sv</item>
<item>ko</item>
</string-array> </string-array>
<string-array name="settings_theme_entries"> <string-array name="settings_theme_entries">

View File

@ -270,7 +270,7 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin
<string name="error_contact_address_not_found">No email address could be found.</string> <string name="error_contact_address_not_found">No email address could be found.</string>
<string name="message_compose_downloading_attachments_toast">Some attachments were not downloaded. They will be downloaded automatically before this message is sent.</string> <string name="message_compose_downloading_attachments_toast">Some attachments were not downloaded. They will be downloaded automatically before this message is sent.</string>
<string name="message_compose_attachments_skipped_toast">Some attachments cannot be forwarded because they have not been downloaded.</string> <string name="message_compose_attachments_skipped_toast">Some attachments cannot be forwarded because they have not been downloaded.</string>
<string name="message_compose_show_quoted_text_action">Quote message</string>
<string name="message_view_from_format">From: <xliff:g id="name">%s</xliff:g> &lt;<xliff:g id="email">%s</xliff:g>&gt;</string> <string name="message_view_from_format">From: <xliff:g id="name">%s</xliff:g> &lt;<xliff:g id="email">%s</xliff:g>&gt;</string>
@ -564,6 +564,9 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin
<string name="account_settings_composition">Sending mail</string> <string name="account_settings_composition">Sending mail</string>
<string name="account_settings_default_quoted_text_shown_label">Quote original message when replying</string>
<string name="account_settings_default_quoted_text_shown_summary">When replying to messages, the original message is in your reply.</string>
<string name="account_settings_reply_after_quote_label">Reply after quoted text</string> <string name="account_settings_reply_after_quote_label">Reply after quoted text</string>
<string name="account_settings_reply_after_quote_summary">When replying to messages, the original message will appear above your reply.</string> <string name="account_settings_reply_after_quote_summary">When replying to messages, the original message will appear above your reply.</string>
@ -800,6 +803,11 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin
<string name="provider_note_yahoojp">If you would like to use POP3 for this provider, You should permit to use POP3 on Yahoo mail settings page.</string> <string name="provider_note_yahoojp">If you would like to use POP3 for this provider, You should permit to use POP3 on Yahoo mail settings page.</string>
<string name="provider_note_naver">If you would like to use IMAP or POP3 for this provider, You should permit to use IMAP or POP3 on Naver mail settings page.</string>
<string name="provider_note_hanmail">If you would like to use IMAP or POP3 for this provider, You should permit to use IMAP or POP3 on Hanmail(Daum) mail settings page.</string>
<string name="provider_note_paran">If you would like to use IMAP or POP3 for this provider, You should permit to use IMAP or POP3 on Paran mail settings page.</string>
<string name="provider_note_nate">If you would like to use IMAP or POP3 for this provider, You should permit to use IMAP or POP3 on Nate mail settings page.</string>
<string name="account_setup_failed_dlg_invalid_certificate_title">Unrecognized Certificate</string> <string name="account_setup_failed_dlg_invalid_certificate_title">Unrecognized Certificate</string>
<string name="account_setup_failed_dlg_invalid_certificate_accept">Accept Key</string> <string name="account_setup_failed_dlg_invalid_certificate_accept">Accept Key</string>
<string name="account_setup_failed_dlg_invalid_certificate_reject">Reject Key</string> <string name="account_setup_failed_dlg_invalid_certificate_reject">Reject Key</string>
@ -1041,4 +1049,8 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin
<string name="account_unavailable">Account \"<xliff:g id="account">%s</xliff:g>\" is unavailable; check storage</string> <string name="account_unavailable">Account \"<xliff:g id="account">%s</xliff:g>\" is unavailable; check storage</string>
<string name="settings_attachment_default_path">Save attachments to...</string>
<string name="attachment_save_title">Save attachment</string>
<string name="attachment_save_desc">No file browser found. Where would you like to save this attachment?</string>
</resources> </resources>

View File

@ -239,6 +239,13 @@
android:entries="@array/account_settings_quote_style_entries" android:entries="@array/account_settings_quote_style_entries"
android:entryValues="@array/account_settings_quote_style_values" /> android:entryValues="@array/account_settings_quote_style_values" />
<CheckBoxPreference
android:persistent="false"
android:key="default_quoted_text_shown"
android:title="@string/account_settings_default_quoted_text_shown_label"
android:defaultValue="true"
android:summary="@string/account_settings_default_quoted_text_shown_summary" />
<CheckBoxPreference <CheckBoxPreference
android:persistent="false" android:persistent="false"
android:key="reply_after_quote" android:key="reply_after_quote"

View File

@ -231,7 +231,6 @@
android:dialogTitle="@string/global_settings_confirm_actions_title" android:dialogTitle="@string/global_settings_confirm_actions_title"
android:positiveButtonText="@android:string/ok" android:positiveButtonText="@android:string/ok"
android:negativeButtonText="@android:string/cancel" /> android:negativeButtonText="@android:string/cancel" />
</PreferenceScreen> </PreferenceScreen>
<PreferenceScreen <PreferenceScreen
@ -284,6 +283,11 @@
android:title="@string/misc_preferences_attachment_title" android:title="@string/misc_preferences_attachment_title"
android:summary="@string/misc_preferences_attachment_description" /> android:summary="@string/misc_preferences_attachment_description" />
<Preference
android:persistent="false"
android:title="@string/settings_attachment_default_path"
android:key="attachment_default_path"
android:summary="- PATH - set by activty -"/>
</PreferenceScreen> </PreferenceScreen>
<PreferenceScreen <PreferenceScreen

View File

@ -276,6 +276,33 @@
<outgoing uri="smtp://smtp.mail.yahoo.co.jp:587" username="$user" /> <outgoing uri="smtp://smtp.mail.yahoo.co.jp:587" username="$user" />
</provider> </provider>
<!-- Korean -->
<provider id="naver" label="Naver" domain="naver.com"
note="@string/provider_note_naver">
<incoming uri="imap+ssl://imap.naver.com" username="$user" />
<outgoing uri="smtp+tls://smtp.naver.com:587" username="$user" />
</provider>
<provider id="hanmail" label="Hanmail" domain="hanmail.net"
note="@string/provider_note_hanmail">
<incoming uri="imap+ssl://imap.hanmail.net" username="$user" />
<outgoing uri="smtp+ssl://smtp.hanmail.net" username="$user" />
</provider>
<provider id="daum" label="Hanmail" domain="daum.net"
note="@string/provider_note_hanmail">
<incoming uri="imap+ssl://imap.hanmail.net" username="$user" />
<outgoing uri="smtp+ssl://smtp.hanmail.net" username="$user" />
</provider>
<provider id="paran" label="Paran" domain="paran.com"
note="@string/provider_note_paran">
<incoming uri="imap+ssl://imap.paran.com" username="$email" />
<outgoing uri="smtp+tls://smtp.paran.com" username="$email" />
</provider>
<provider id="nate" label="Nate" domain="nate.com"
note="@string/provider_note_nate">
<incoming uri="imap+ssl://imap.nate.com" username="$user" />
<outgoing uri="smtp+tls://smtp.mail.nate.com" username="$user" />
</provider>
<!-- Developers' vanity providers --> <!-- Developers' vanity providers -->
<provider id="fsck.com" label="Jesse's personal mail" domain="fsck.com" > <provider id="fsck.com" label="Jesse's personal mail" domain="fsck.com" >
<incoming uri="imap+ssl://fsck.com" username="$user" /> <incoming uri="imap+ssl://fsck.com" username="$user" />

View File

@ -33,6 +33,16 @@ import java.util.concurrent.ConcurrentHashMap;
* and delete itself given a Preferences to work with. Each account is defined by a UUID. * and delete itself given a Preferences to work with. Each account is defined by a UUID.
*/ */
public class Account implements BaseAccount { public class Account implements BaseAccount {
/**
* Default value for the inbox folder (never changes for POP3 and IMAP)
*/
public static final String INBOX = "INBOX";
/**
* This local folder is used to store messages to be sent.
*/
public static final String OUTBOX = "OUTBOX";
public static final String EXPUNGE_IMMEDIATELY = "EXPUNGE_IMMEDIATELY"; public static final String EXPUNGE_IMMEDIATELY = "EXPUNGE_IMMEDIATELY";
public static final String EXPUNGE_MANUALLY = "EXPUNGE_MANUALLY"; public static final String EXPUNGE_MANUALLY = "EXPUNGE_MANUALLY";
public static final String EXPUNGE_ON_POLL = "EXPUNGE_ON_POLL"; public static final String EXPUNGE_ON_POLL = "EXPUNGE_ON_POLL";
@ -50,6 +60,7 @@ public class Account implements BaseAccount {
private static final MessageFormat DEFAULT_MESSAGE_FORMAT = MessageFormat.HTML; private static final MessageFormat DEFAULT_MESSAGE_FORMAT = MessageFormat.HTML;
private static final QuoteStyle DEFAULT_QUOTE_STYLE = QuoteStyle.PREFIX; private static final QuoteStyle DEFAULT_QUOTE_STYLE = QuoteStyle.PREFIX;
private static final String DEFAULT_QUOTE_PREFIX = ">"; private static final String DEFAULT_QUOTE_PREFIX = ">";
private static final boolean DEFAULT_QUOTED_TEXT_SHOWN = true;
private static final boolean DEFAULT_REPLY_AFTER_QUOTE = false; private static final boolean DEFAULT_REPLY_AFTER_QUOTE = false;
/** /**
@ -80,6 +91,7 @@ public class Account implements BaseAccount {
private long mLatestOldMessageSeenTime; private long mLatestOldMessageSeenTime;
private boolean mNotifyNewMail; private boolean mNotifyNewMail;
private boolean mNotifySelfNewMail; private boolean mNotifySelfNewMail;
private String mInboxFolderName;
private String mDraftsFolderName; private String mDraftsFolderName;
private String mSentFolderName; private String mSentFolderName;
private String mTrashFolderName; private String mTrashFolderName;
@ -115,6 +127,7 @@ public class Account implements BaseAccount {
private MessageFormat mMessageFormat; private MessageFormat mMessageFormat;
private QuoteStyle mQuoteStyle; private QuoteStyle mQuoteStyle;
private String mQuotePrefix; private String mQuotePrefix;
private boolean mDefaultQuotedTextShown;
private boolean mReplyAfterQuote; private boolean mReplyAfterQuote;
private boolean mSyncRemoteDeletions; private boolean mSyncRemoteDeletions;
private String mCryptoApp; private String mCryptoApp;
@ -180,7 +193,8 @@ public class Account implements BaseAccount {
mEnableMoveButtons = false; mEnableMoveButtons = false;
mIsSignatureBeforeQuotedText = false; mIsSignatureBeforeQuotedText = false;
mExpungePolicy = EXPUNGE_IMMEDIATELY; mExpungePolicy = EXPUNGE_IMMEDIATELY;
mAutoExpandFolderName = "INBOX"; mAutoExpandFolderName = INBOX;
mInboxFolderName = INBOX;
mMaxPushFolders = 10; mMaxPushFolders = 10;
mChipColor = (new Random()).nextInt(0xffffff) + 0xff000000; mChipColor = (new Random()).nextInt(0xffffff) + 0xff000000;
goToUnreadMessageSearch = false; goToUnreadMessageSearch = false;
@ -191,6 +205,7 @@ public class Account implements BaseAccount {
mMessageFormat = DEFAULT_MESSAGE_FORMAT; mMessageFormat = DEFAULT_MESSAGE_FORMAT;
mQuoteStyle = DEFAULT_QUOTE_STYLE; mQuoteStyle = DEFAULT_QUOTE_STYLE;
mQuotePrefix = DEFAULT_QUOTE_PREFIX; mQuotePrefix = DEFAULT_QUOTE_PREFIX;
mDefaultQuotedTextShown = DEFAULT_QUOTED_TEXT_SHOWN;
mReplyAfterQuote = DEFAULT_REPLY_AFTER_QUOTE; mReplyAfterQuote = DEFAULT_REPLY_AFTER_QUOTE;
mSyncRemoteDeletions = true; mSyncRemoteDeletions = true;
mCryptoApp = Apg.NAME; mCryptoApp = Apg.NAME;
@ -246,6 +261,7 @@ public class Account implements BaseAccount {
mNotifySelfNewMail = prefs.getBoolean(mUuid + ".notifySelfNewMail", true); mNotifySelfNewMail = prefs.getBoolean(mUuid + ".notifySelfNewMail", true);
mNotifySync = prefs.getBoolean(mUuid + ".notifyMailCheck", false); mNotifySync = prefs.getBoolean(mUuid + ".notifyMailCheck", false);
mDeletePolicy = prefs.getInt(mUuid + ".deletePolicy", 0); mDeletePolicy = prefs.getInt(mUuid + ".deletePolicy", 0);
mInboxFolderName = prefs.getString(mUuid + ".inboxFolderName", INBOX);
mDraftsFolderName = prefs.getString(mUuid + ".draftsFolderName", "Drafts"); mDraftsFolderName = prefs.getString(mUuid + ".draftsFolderName", "Drafts");
mSentFolderName = prefs.getString(mUuid + ".sentFolderName", "Sent"); mSentFolderName = prefs.getString(mUuid + ".sentFolderName", "Sent");
mTrashFolderName = prefs.getString(mUuid + ".trashFolderName", "Trash"); mTrashFolderName = prefs.getString(mUuid + ".trashFolderName", "Trash");
@ -263,6 +279,7 @@ public class Account implements BaseAccount {
mMessageFormat = MessageFormat.valueOf(prefs.getString(mUuid + ".messageFormat", DEFAULT_MESSAGE_FORMAT.name())); mMessageFormat = MessageFormat.valueOf(prefs.getString(mUuid + ".messageFormat", DEFAULT_MESSAGE_FORMAT.name()));
mQuoteStyle = QuoteStyle.valueOf(prefs.getString(mUuid + ".quoteStyle", DEFAULT_QUOTE_STYLE.name())); mQuoteStyle = QuoteStyle.valueOf(prefs.getString(mUuid + ".quoteStyle", DEFAULT_QUOTE_STYLE.name()));
mQuotePrefix = prefs.getString(mUuid + ".quotePrefix", DEFAULT_QUOTE_PREFIX); mQuotePrefix = prefs.getString(mUuid + ".quotePrefix", DEFAULT_QUOTE_PREFIX);
mDefaultQuotedTextShown = prefs.getBoolean(mUuid + ".defaultQuotedTextShown", DEFAULT_QUOTED_TEXT_SHOWN);
mReplyAfterQuote = prefs.getBoolean(mUuid + ".replyAfterQuote", DEFAULT_REPLY_AFTER_QUOTE); mReplyAfterQuote = prefs.getBoolean(mUuid + ".replyAfterQuote", DEFAULT_REPLY_AFTER_QUOTE);
for (String type : networkTypes) { for (String type : networkTypes) {
Boolean useCompression = prefs.getBoolean(mUuid + ".useCompression." + type, Boolean useCompression = prefs.getBoolean(mUuid + ".useCompression." + type,
@ -270,8 +287,7 @@ public class Account implements BaseAccount {
compressionMap.put(type, useCompression); compressionMap.put(type, useCompression);
} }
mAutoExpandFolderName = prefs.getString(mUuid + ".autoExpandFolderName", mAutoExpandFolderName = prefs.getString(mUuid + ".autoExpandFolderName", INBOX);
"INBOX");
mAccountNumber = prefs.getInt(mUuid + ".accountNumber", 0); mAccountNumber = prefs.getInt(mUuid + ".accountNumber", 0);
@ -485,6 +501,7 @@ public class Account implements BaseAccount {
editor.putBoolean(mUuid + ".notifySelfNewMail", mNotifySelfNewMail); editor.putBoolean(mUuid + ".notifySelfNewMail", mNotifySelfNewMail);
editor.putBoolean(mUuid + ".notifyMailCheck", mNotifySync); editor.putBoolean(mUuid + ".notifyMailCheck", mNotifySync);
editor.putInt(mUuid + ".deletePolicy", mDeletePolicy); editor.putInt(mUuid + ".deletePolicy", mDeletePolicy);
editor.putString(mUuid + ".inboxFolderName", mInboxFolderName);
editor.putString(mUuid + ".draftsFolderName", mDraftsFolderName); editor.putString(mUuid + ".draftsFolderName", mDraftsFolderName);
editor.putString(mUuid + ".sentFolderName", mSentFolderName); editor.putString(mUuid + ".sentFolderName", mSentFolderName);
editor.putString(mUuid + ".trashFolderName", mTrashFolderName); editor.putString(mUuid + ".trashFolderName", mTrashFolderName);
@ -514,6 +531,7 @@ public class Account implements BaseAccount {
editor.putString(mUuid + ".messageFormat", mMessageFormat.name()); editor.putString(mUuid + ".messageFormat", mMessageFormat.name());
editor.putString(mUuid + ".quoteStyle", mQuoteStyle.name()); editor.putString(mUuid + ".quoteStyle", mQuoteStyle.name());
editor.putString(mUuid + ".quotePrefix", mQuotePrefix); editor.putString(mUuid + ".quotePrefix", mQuotePrefix);
editor.putBoolean(mUuid + ".defaultQuotedTextShown", mDefaultQuotedTextShown);
editor.putBoolean(mUuid + ".replyAfterQuote", mReplyAfterQuote); editor.putBoolean(mUuid + ".replyAfterQuote", mReplyAfterQuote);
editor.putString(mUuid + ".cryptoApp", mCryptoApp); editor.putString(mUuid + ".cryptoApp", mCryptoApp);
editor.putBoolean(mUuid + ".cryptoAutoSignature", mCryptoAutoSignature); editor.putBoolean(mUuid + ".cryptoAutoSignature", mCryptoAutoSignature);
@ -762,7 +780,7 @@ public class Account implements BaseAccount {
public boolean isSpecialFolder(String folderName) { public boolean isSpecialFolder(String folderName) {
if (folderName != null && (folderName.equalsIgnoreCase(K9.INBOX) || if (folderName != null && (folderName.equalsIgnoreCase(getInboxFolderName()) ||
folderName.equals(getTrashFolderName()) || folderName.equals(getTrashFolderName()) ||
folderName.equals(getDraftsFolderName()) || folderName.equals(getDraftsFolderName()) ||
folderName.equals(getArchiveFolderName()) || folderName.equals(getArchiveFolderName()) ||
@ -824,7 +842,7 @@ public class Account implements BaseAccount {
} }
public synchronized String getOutboxFolderName() { public synchronized String getOutboxFolderName() {
return K9.OUTBOX; return OUTBOX;
} }
public synchronized String getAutoExpandFolderName() { public synchronized String getAutoExpandFolderName() {
@ -1270,6 +1288,14 @@ public class Account implements BaseAccount {
mQuotePrefix = quotePrefix; mQuotePrefix = quotePrefix;
} }
public synchronized boolean isDefaultQuotedTextShown() {
return mDefaultQuotedTextShown;
}
public synchronized void setDefaultQuotedTextShown(boolean shown) {
mDefaultQuotedTextShown = shown;
}
public synchronized boolean isReplyAfterQuote() { public synchronized boolean isReplyAfterQuote() {
return mReplyAfterQuote; return mReplyAfterQuote;
} }
@ -1303,6 +1329,15 @@ public class Account implements BaseAccount {
public void setCryptoAutoSignature(boolean cryptoAutoSignature) { public void setCryptoAutoSignature(boolean cryptoAutoSignature) {
mCryptoAutoSignature = cryptoAutoSignature; mCryptoAutoSignature = cryptoAutoSignature;
} }
public String getInboxFolderName() {
return mInboxFolderName;
}
public void setInboxFolderName(String mInboxFolderName) {
this.mInboxFolderName = mInboxFolderName;
}
public synchronized boolean syncRemoteDeletions() { public synchronized boolean syncRemoteDeletions() {
return mSyncRemoteDeletions; return mSyncRemoteDeletions;
} }

View File

@ -2,6 +2,7 @@
package com.fsck.k9; package com.fsck.k9;
import java.io.File; import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -18,9 +19,11 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManager.NameNotFoundException;
import android.net.Uri; import android.net.Uri;
import android.os.Environment;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.text.format.Time; import android.text.format.Time;
import android.text.style.AbsoluteSizeSpan;
import android.util.Log; import android.util.Log;
import com.fsck.k9.activity.MessageCompose; import com.fsck.k9.activity.MessageCompose;
@ -65,6 +68,10 @@ public class K9 extends Application {
*/ */
private static List<ApplicationAware> observers = new ArrayList<ApplicationAware>(); private static List<ApplicationAware> observers = new ArrayList<ApplicationAware>();
/**
* @see K9#createAbsoluteSizeSpan(int)
*/
private static Constructor<AbsoluteSizeSpan> sAbsoluteSizeSpanConstructor;
public enum BACKGROUND_OPS { public enum BACKGROUND_OPS {
WHEN_CHECKED, ALWAYS, NEVER, WHEN_CHECKED_AUTO_SYNC WHEN_CHECKED, ALWAYS, NEVER, WHEN_CHECKED_AUTO_SYNC
@ -143,7 +150,6 @@ public class K9 extends Application {
public static boolean ENABLE_ERROR_FOLDER = true; public static boolean ENABLE_ERROR_FOLDER = true;
public static String ERROR_FOLDER_NAME = "K9mail-errors"; public static String ERROR_FOLDER_NAME = "K9mail-errors";
private static boolean mAnimations = true; private static boolean mAnimations = true;
private static boolean mConfirmDelete = false; private static boolean mConfirmDelete = false;
@ -177,7 +183,7 @@ public class K9 extends Application {
private static String mQuietTimeStarts = null; private static String mQuietTimeStarts = null;
private static String mQuietTimeEnds = null; private static String mQuietTimeEnds = null;
private static boolean compactLayouts = false; private static boolean compactLayouts = false;
private static String mAttachmentDefaultPath = "";
private static boolean useGalleryBugWorkaround = false; private static boolean useGalleryBugWorkaround = false;
@ -210,17 +216,6 @@ public class K9 extends Application {
public static final String[] UNACCEPTABLE_ATTACHMENT_DOWNLOAD_TYPES = new String[] { public static final String[] UNACCEPTABLE_ATTACHMENT_DOWNLOAD_TYPES = new String[] {
}; };
/**
* The special name "INBOX" is used throughout the application to mean "Whatever folder
* the server refers to as the user's Inbox. Placed here to ease use.
*/
public static final String INBOX = "INBOX";
/**
* This local folder is used to store messages to be sent.
*/
public static final String OUTBOX = "OUTBOX";
/** /**
* For use when displaying that no folder is selected * For use when displaying that no folder is selected
*/ */
@ -452,7 +447,7 @@ public class K9 extends Application {
editor.putBoolean("keyguardPrivacy", mKeyguardPrivacy); editor.putBoolean("keyguardPrivacy", mKeyguardPrivacy);
editor.putBoolean("compactLayouts", compactLayouts); editor.putBoolean("compactLayouts", compactLayouts);
editor.putString("attachmentdefaultpath", mAttachmentDefaultPath);
fontSizes.save(editor); fontSizes.save(editor);
} }
@ -507,7 +502,7 @@ public class K9 extends Application {
mKeyguardPrivacy = sprefs.getBoolean("keyguardPrivacy", false); mKeyguardPrivacy = sprefs.getBoolean("keyguardPrivacy", false);
compactLayouts = sprefs.getBoolean("compactLayouts", false); compactLayouts = sprefs.getBoolean("compactLayouts", false);
mAttachmentDefaultPath = sprefs.getString("attachmentdefaultpath", Environment.getExternalStorageDirectory().toString());
fontSizes.load(sprefs); fontSizes.load(sprefs);
try { try {
@ -942,11 +937,11 @@ public class K9 extends Application {
} }
public static boolean confirmSpam() { public static boolean confirmSpam() {
return mConfirmSpam; return mConfirmSpam;
} }
public static void setConfirmSpam(final boolean confirm) { public static void setConfirmSpam(final boolean confirm) {
mConfirmSpam = confirm; mConfirmSpam = confirm;
} }
public static boolean confirmMarkAllAsRead() { public static boolean confirmMarkAllAsRead() {
@ -995,4 +990,55 @@ public class K9 extends Application {
} }
} }
public static String getAttachmentDefaultPath() {
return mAttachmentDefaultPath;
}
public static void setAttachmentDefaultPath(String attachmentDefaultPath) {
K9.mAttachmentDefaultPath = attachmentDefaultPath;
}
/**
* Creates an {@link AbsoluteSizeSpan} object.
*
* <p>
* Android versions prior to 2.0 don't support the constructor with two parameters
* ({@link AbsoluteSizeSpan#AbsoluteSizeSpan(int, boolean)}). So we have to perform some
* reflection magic to dynamically load the new constructor on devices that support it.
* For devices with old Android versions we just use the size as pixels (instead of dip).
* </p>
*
* @param size This is used as the {@code size} parameter for the AbsoluteSizeSpan constructor.
* @return a AbsoluteSizeSpan object with the specified text size.
*/
public static AbsoluteSizeSpan createAbsoluteSizeSpan(int size) {
if (Integer.parseInt(android.os.Build.VERSION.SDK) < 5) {
// For Android 1.5/1.6 simply use the constructor with only the size parameter.
// Yes, that will most likely look wrong!
return new AbsoluteSizeSpan(size);
}
if (sAbsoluteSizeSpanConstructor == null) {
try {
sAbsoluteSizeSpanConstructor = AbsoluteSizeSpan.class.getConstructor(int.class, boolean.class);
} catch (Exception e) {
Log.e(K9.LOG_TAG, "Couldn't get the AbsoluteSizeSpan(int, boolean) constructor", e);
// Fallback
return new AbsoluteSizeSpan(size);
}
}
AbsoluteSizeSpan result;
try {
result = sAbsoluteSizeSpanConstructor.newInstance(size, true);
} catch (Exception e) {
Log.e(K9.LOG_TAG, "Couldn't call the AbsoluteSizeSpan(int, boolean) constructor", e);
// Fallback
result = new AbsoluteSizeSpan(size);
}
return result;
}
} }

View File

@ -457,66 +457,66 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC
switch (id) { switch (id) {
case DIALOG_REMOVE_ACCOUNT: case DIALOG_REMOVE_ACCOUNT:
return ConfirmationDialog.create(this, id, return ConfirmationDialog.create(this, id,
R.string.account_delete_dlg_title, R.string.account_delete_dlg_title,
getString(R.string.account_delete_dlg_instructions_fmt, getString(R.string.account_delete_dlg_instructions_fmt,
mSelectedContextAccount.getDescription()), mSelectedContextAccount.getDescription()),
R.string.okay_action, R.string.okay_action,
R.string.cancel_action, R.string.cancel_action,
new Runnable() { new Runnable() {
@Override @Override
public void run() { public void run() {
if (mSelectedContextAccount instanceof Account) { if (mSelectedContextAccount instanceof Account) {
Account realAccount = (Account)mSelectedContextAccount; Account realAccount = (Account)mSelectedContextAccount;
try { try {
realAccount.getLocalStore().delete(); realAccount.getLocalStore().delete();
} catch (Exception e) { } catch (Exception e) {
// Ignore, this may lead to localStores on sd-cards that are // Ignore, this may lead to localStores on sd-cards that are
// currently not inserted to be left // currently not inserted to be left
}
MessagingController.getInstance(getApplication())
.notifyAccountCancel(Accounts.this, realAccount);
Preferences.getPreferences(Accounts.this).deleteAccount(realAccount);
K9.setServicesEnabled(Accounts.this);
refresh();
}
} }
}); MessagingController.getInstance(getApplication())
.notifyAccountCancel(Accounts.this, realAccount);
Preferences.getPreferences(Accounts.this).deleteAccount(realAccount);
K9.setServicesEnabled(Accounts.this);
refresh();
}
}
});
case DIALOG_CLEAR_ACCOUNT: case DIALOG_CLEAR_ACCOUNT:
return ConfirmationDialog.create(this, id, return ConfirmationDialog.create(this, id,
R.string.account_clear_dlg_title, R.string.account_clear_dlg_title,
getString(R.string.account_clear_dlg_instructions_fmt, getString(R.string.account_clear_dlg_instructions_fmt,
mSelectedContextAccount.getDescription()), mSelectedContextAccount.getDescription()),
R.string.okay_action, R.string.okay_action,
R.string.cancel_action, R.string.cancel_action,
new Runnable() { new Runnable() {
@Override @Override
public void run() { public void run() {
if (mSelectedContextAccount instanceof Account) { if (mSelectedContextAccount instanceof Account) {
Account realAccount = (Account)mSelectedContextAccount; Account realAccount = (Account)mSelectedContextAccount;
mHandler.workingAccount(realAccount, R.string.clearing_account); mHandler.workingAccount(realAccount, R.string.clearing_account);
MessagingController.getInstance(getApplication()).clear(realAccount, null); MessagingController.getInstance(getApplication()).clear(realAccount, null);
} }
} }
}); });
case DIALOG_RECREATE_ACCOUNT: case DIALOG_RECREATE_ACCOUNT:
return ConfirmationDialog.create(this, id, return ConfirmationDialog.create(this, id,
R.string.account_recreate_dlg_title, R.string.account_recreate_dlg_title,
getString(R.string.account_recreate_dlg_instructions_fmt, getString(R.string.account_recreate_dlg_instructions_fmt,
mSelectedContextAccount.getDescription()), mSelectedContextAccount.getDescription()),
R.string.okay_action, R.string.okay_action,
R.string.cancel_action, R.string.cancel_action,
new Runnable() { new Runnable() {
@Override @Override
public void run() { public void run() {
if (mSelectedContextAccount instanceof Account) { if (mSelectedContextAccount instanceof Account) {
Account realAccount = (Account)mSelectedContextAccount; Account realAccount = (Account)mSelectedContextAccount;
mHandler.workingAccount(realAccount, R.string.recreating_account); mHandler.workingAccount(realAccount, R.string.recreating_account);
MessagingController.getInstance(getApplication()).recreate(realAccount, null); MessagingController.getInstance(getApplication()).recreate(realAccount, null);
} }
} }
}); });
} }
return super.onCreateDialog(id); return super.onCreateDialog(id);
} }

View File

@ -6,7 +6,6 @@ import android.content.Context;
import com.fsck.k9.Account; import com.fsck.k9.Account;
import com.fsck.k9.AccountStats; import com.fsck.k9.AccountStats;
import com.fsck.k9.K9;
import com.fsck.k9.R; import com.fsck.k9.R;
import com.fsck.k9.controller.MessagingListener; import com.fsck.k9.controller.MessagingListener;
import com.fsck.k9.service.MailService; import com.fsck.k9.service.MailService;
@ -35,7 +34,7 @@ public class ActivityListener extends MessagingListener {
if (mLoadingFolderName != null || mLoadingHeaderFolderName != null) { if (mLoadingFolderName != null || mLoadingHeaderFolderName != null) {
String displayName = mLoadingFolderName; String displayName = mLoadingFolderName;
if (K9.INBOX.equalsIgnoreCase(displayName)) { if ((mAccount != null) && (mAccount.getInboxFolderName() != null) && mAccount.getInboxFolderName().equalsIgnoreCase(displayName)) {
displayName = context.getString(R.string.special_mailbox_name_inbox); displayName = context.getString(R.string.special_mailbox_name_inbox);
} else if ((mAccount != null) && mAccount.getOutboxFolderName().equals(displayName)) { } else if ((mAccount != null) && mAccount.getOutboxFolderName().equals(displayName)) {
displayName = context.getString(R.string.special_mailbox_name_outbox); displayName = context.getString(R.string.special_mailbox_name_outbox);

View File

@ -100,6 +100,7 @@ public class ChooseFolder extends K9ListActivity {
setListAdapter(mAdapter); setListAdapter(mAdapter);
mMode = mAccount.getFolderTargetMode();
MessagingController.getInstance(getApplication()).listFolders(mAccount, false, mListener); MessagingController.getInstance(getApplication()).listFolders(mAccount, false, mListener);
@ -248,7 +249,8 @@ public class ChooseFolder extends K9ListActivity {
String name = folder.getName(); String name = folder.getName();
// Inbox needs to be compared case-insensitively // Inbox needs to be compared case-insensitively
if (hideCurrentFolder && (name.equals(mFolder) || (K9.INBOX.equalsIgnoreCase(mFolder) && K9.INBOX.equalsIgnoreCase(name)))) { if (hideCurrentFolder && (name.equals(mFolder) ||
(mAccount.getInboxFolderName().equalsIgnoreCase(mFolder) && mAccount.getInboxFolderName().equalsIgnoreCase(name)))) {
continue; continue;
} }
try { try {
@ -282,10 +284,10 @@ public class ChooseFolder extends K9ListActivity {
if (K9.FOLDER_NONE.equalsIgnoreCase(bName)) { if (K9.FOLDER_NONE.equalsIgnoreCase(bName)) {
return 1; return 1;
} }
if (K9.INBOX.equalsIgnoreCase(aName)) { if (mAccount.getInboxFolderName().equalsIgnoreCase(aName)) {
return -1; return -1;
} }
if (K9.INBOX.equalsIgnoreCase(bName)) { if (mAccount.getInboxFolderName().equalsIgnoreCase(bName)) {
return 1; return 1;
} }
@ -298,7 +300,7 @@ public class ChooseFolder extends K9ListActivity {
mAdapter.clear(); mAdapter.clear();
int position = 0; int position = 0;
for (String name : localFolders) { for (String name : localFolders) {
if (K9.INBOX.equalsIgnoreCase(name)) { if (mAccount.getInboxFolderName().equalsIgnoreCase(name)) {
mAdapter.add(getString(R.string.special_mailbox_name_inbox)); mAdapter.add(getString(R.string.special_mailbox_name_inbox));
heldInbox = name; heldInbox = name;
} else if (!K9.ERROR_FOLDER_NAME.equals(name) && !account.getOutboxFolderName().equals(name)) { } else if (!K9.ERROR_FOLDER_NAME.equals(name) && !account.getOutboxFolderName().equals(name)) {
@ -315,7 +317,7 @@ public class ChooseFolder extends K9ListActivity {
selectedFolder = position; selectedFolder = position;
} }
} else if (name.equals(mFolder) || } else if (name.equals(mFolder) ||
(K9.INBOX.equalsIgnoreCase(mFolder) && K9.INBOX.equalsIgnoreCase(name))) { (mAccount.getInboxFolderName().equalsIgnoreCase(mFolder) && mAccount.getInboxFolderName().equalsIgnoreCase(name))) {
selectedFolder = position; selectedFolder = position;
} }
position++; position++;

View File

@ -20,27 +20,27 @@ public class ConfirmationDialog {
* @return A confirmation dialog with the supplied arguments * @return A confirmation dialog with the supplied arguments
*/ */
public static Dialog create(final Activity activity, final int dialogId, final int title, public static Dialog create(final Activity activity, final int dialogId, final int title,
final String message, final int confirmButton, final int cancelButton, final String message, final int confirmButton, final int cancelButton,
final Runnable action) { final Runnable action) {
final AlertDialog.Builder builder = new AlertDialog.Builder(activity); final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setTitle(title); builder.setTitle(title);
builder.setMessage(message); builder.setMessage(message);
builder.setPositiveButton(confirmButton, builder.setPositiveButton(confirmButton,
new DialogInterface.OnClickListener() { new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
activity.dismissDialog(dialogId); activity.dismissDialog(dialogId);
action.run(); action.run();
} }
}); });
builder.setNegativeButton(cancelButton, builder.setNegativeButton(cancelButton,
new DialogInterface.OnClickListener() { new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
activity.dismissDialog(dialogId); activity.dismissDialog(dialogId);
} }
}); });
return builder.create(); return builder.create();
} }
@ -58,10 +58,10 @@ public class ConfirmationDialog {
* @see #create(Activity,int,int,String,int,int,Runnable) * @see #create(Activity,int,int,String,int,int,Runnable)
*/ */
public static Dialog create(final Activity activity, final int dialogId, final int title, public static Dialog create(final Activity activity, final int dialogId, final int title,
final int message, final int confirmButton, final int cancelButton, final int message, final int confirmButton, final int cancelButton,
final Runnable action) { final Runnable action) {
return create(activity, dialogId, title, activity.getString(message), confirmButton, return create(activity, dialogId, title, activity.getString(message), confirmButton,
cancelButton, action); cancelButton, action);
} }
} }

View File

@ -97,7 +97,7 @@ public class FolderInfoHolder implements Comparable<FolderInfoHolder> {
this.status = truncateStatus(folder.getStatus()); this.status = truncateStatus(folder.getStatus());
if (this.name.equalsIgnoreCase(K9.INBOX)) { if (this.name.equalsIgnoreCase(account.getInboxFolderName())) {
this.displayName = context.getString(R.string.special_mailbox_name_inbox); this.displayName = context.getString(R.string.special_mailbox_name_inbox);
} else { } else {
this.displayName = folder.getName(); this.displayName = folder.getName();

View File

@ -487,6 +487,7 @@ public class FolderList extends K9ListActivity {
} }
} }
onRefresh(!REFRESH_REMOTE);
} }
@ -641,10 +642,12 @@ public class FolderList extends K9ListActivity {
private void markAllAsRead() { private void markAllAsRead() {
try { try {
MessagingController.getInstance(getApplication()) MessagingController.getInstance(getApplication())
.markAllMessagesRead(mAccount, mSelectedContextFolder.name); .markAllMessagesRead(mAccount, mSelectedContextFolder.name);
mSelectedContextFolder.unreadMessageCount = 0; mSelectedContextFolder.unreadMessageCount = 0;
mHandler.dataChanged(); mHandler.dataChanged();
} catch (Exception e) { /* Ignore */ } } catch (Exception e) {
/* Ignore */
}
} }
@Override @Override
@ -652,17 +655,17 @@ public class FolderList extends K9ListActivity {
switch (id) { switch (id) {
case DIALOG_MARK_ALL_AS_READ: case DIALOG_MARK_ALL_AS_READ:
return ConfirmationDialog.create(this, id, return ConfirmationDialog.create(this, id,
R.string.mark_all_as_read_dlg_title, R.string.mark_all_as_read_dlg_title,
getString(R.string.mark_all_as_read_dlg_instructions_fmt, getString(R.string.mark_all_as_read_dlg_instructions_fmt,
mSelectedContextFolder.displayName), mSelectedContextFolder.displayName),
R.string.okay_action, R.string.okay_action,
R.string.cancel_action, R.string.cancel_action,
new Runnable() { new Runnable() {
@Override @Override
public void run() { public void run() {
markAllAsRead(); markAllAsRead();
} }
}); });
} }
return super.onCreateDialog(id); return super.onCreateDialog(id);

View File

@ -1,6 +1,6 @@
package com.fsck.k9.activity; package com.fsck.k9.activity;
import java.io.File; import java.io.File;
import java.io.Serializable; import java.io.Serializable;
import java.util.*; import java.util.*;
@ -38,6 +38,7 @@ import android.view.View.OnFocusChangeListener;
import android.view.Window; import android.view.Window;
import android.webkit.WebView; import android.webkit.WebView;
import android.widget.AutoCompleteTextView.Validator; import android.widget.AutoCompleteTextView.Validator;
import android.widget.Button;
import android.widget.CheckBox; import android.widget.CheckBox;
import android.widget.EditText; import android.widget.EditText;
import android.widget.ImageButton; import android.widget.ImageButton;
@ -89,8 +90,8 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
"com.fsck.k9.activity.MessageCompose.ccShown"; "com.fsck.k9.activity.MessageCompose.ccShown";
private static final String STATE_KEY_BCC_SHOWN = private static final String STATE_KEY_BCC_SHOWN =
"com.fsck.k9.activity.MessageCompose.bccShown"; "com.fsck.k9.activity.MessageCompose.bccShown";
private static final String STATE_KEY_QUOTED_TEXT_SHOWN = private static final String STATE_KEY_QUOTED_TEXT_MODE =
"com.fsck.k9.activity.MessageCompose.quotedTextShown"; "com.fsck.k9.activity.MessageCompose.QuotedTextShown";
private static final String STATE_KEY_SOURCE_MESSAGE_PROCED = private static final String STATE_KEY_SOURCE_MESSAGE_PROCED =
"com.fsck.k9.activity.MessageCompose.stateKeySourceMessageProced"; "com.fsck.k9.activity.MessageCompose.stateKeySourceMessageProced";
private static final String STATE_KEY_DRAFT_UID = private static final String STATE_KEY_DRAFT_UID =
@ -161,6 +162,13 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
*/ */
private boolean mSourceMessageProcessed = false; private boolean mSourceMessageProcessed = false;
private enum QuotedTextMode {
NONE,
SHOW,
HIDE
};
private QuotedTextMode mQuotedTextMode = QuotedTextMode.NONE;
private TextView mFromView; private TextView mFromView;
private LinearLayout mCcWrapper; private LinearLayout mCcWrapper;
@ -172,6 +180,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
private EditText mSignatureView; private EditText mSignatureView;
private EditText mMessageContentView; private EditText mMessageContentView;
private LinearLayout mAttachments; private LinearLayout mAttachments;
private Button mQuotedTextShow;
private View mQuotedTextBar; private View mQuotedTextBar;
private ImageButton mQuotedTextEdit; private ImageButton mQuotedTextEdit;
private ImageButton mQuotedTextDelete; private ImageButton mQuotedTextDelete;
@ -391,6 +400,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
mMessageContentView = (EditText)findViewById(R.id.message_content); mMessageContentView = (EditText)findViewById(R.id.message_content);
mMessageContentView.getInputExtras(true).putBoolean("allowEmoji", true); mMessageContentView.getInputExtras(true).putBoolean("allowEmoji", true);
mAttachments = (LinearLayout)findViewById(R.id.attachments); mAttachments = (LinearLayout)findViewById(R.id.attachments);
mQuotedTextShow = (Button)findViewById(R.id.quoted_text_show);
mQuotedTextBar = findViewById(R.id.quoted_text_bar); mQuotedTextBar = findViewById(R.id.quoted_text_bar);
mQuotedTextEdit = (ImageButton)findViewById(R.id.quoted_text_edit); mQuotedTextEdit = (ImageButton)findViewById(R.id.quoted_text_edit);
mQuotedTextDelete = (ImageButton)findViewById(R.id.quoted_text_delete); mQuotedTextDelete = (ImageButton)findViewById(R.id.quoted_text_delete);
@ -467,11 +477,10 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
* We set this to invisible by default. Other methods will turn it back on if it's * We set this to invisible by default. Other methods will turn it back on if it's
* needed. * needed.
*/ */
mQuotedTextBar.setVisibility(View.GONE);
mQuotedText.setVisibility(View.GONE);
mQuotedHTML.setVisibility(View.GONE);
mQuotedTextEdit.setVisibility(View.GONE);
showOrHideQuotedText(QuotedTextMode.NONE);
mQuotedTextShow.setOnClickListener(this);
mQuotedTextEdit.setOnClickListener(this); mQuotedTextEdit.setOnClickListener(this);
mQuotedTextDelete.setOnClickListener(this); mQuotedTextDelete.setOnClickListener(this);
@ -801,7 +810,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
outState.putParcelableArrayList(STATE_KEY_ATTACHMENTS, attachments); outState.putParcelableArrayList(STATE_KEY_ATTACHMENTS, attachments);
outState.putBoolean(STATE_KEY_CC_SHOWN, mCcView.getVisibility() == View.VISIBLE); outState.putBoolean(STATE_KEY_CC_SHOWN, mCcView.getVisibility() == View.VISIBLE);
outState.putBoolean(STATE_KEY_BCC_SHOWN, mBccView.getVisibility() == View.VISIBLE); outState.putBoolean(STATE_KEY_BCC_SHOWN, mBccView.getVisibility() == View.VISIBLE);
outState.putBoolean(STATE_KEY_QUOTED_TEXT_SHOWN, mQuotedTextBar.getVisibility() == View.VISIBLE); outState.putSerializable(STATE_KEY_QUOTED_TEXT_MODE, mQuotedTextMode);
outState.putBoolean(STATE_KEY_SOURCE_MESSAGE_PROCED, mSourceMessageProcessed); outState.putBoolean(STATE_KEY_SOURCE_MESSAGE_PROCED, mSourceMessageProcessed);
outState.putString(STATE_KEY_DRAFT_UID, mDraftUid); outState.putString(STATE_KEY_DRAFT_UID, mDraftUid);
outState.putSerializable(STATE_IDENTITY, mIdentity); outState.putSerializable(STATE_IDENTITY, mIdentity);
@ -829,17 +838,13 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
: View.GONE); : View.GONE);
mBccWrapper.setVisibility(savedInstanceState mBccWrapper.setVisibility(savedInstanceState
.getBoolean(STATE_KEY_BCC_SHOWN) ? View.VISIBLE : View.GONE); .getBoolean(STATE_KEY_BCC_SHOWN) ? View.VISIBLE : View.GONE);
if (mMessageFormat == MessageFormat.HTML) { showOrHideQuotedText((QuotedTextMode)savedInstanceState.getSerializable(STATE_KEY_QUOTED_TEXT_MODE));
if (mQuotedTextMode != QuotedTextMode.NONE && mMessageFormat == MessageFormat.HTML) {
mQuotedHtmlContent = (InsertableHtmlContent) savedInstanceState.getSerializable(STATE_KEY_HTML_QUOTE); mQuotedHtmlContent = (InsertableHtmlContent) savedInstanceState.getSerializable(STATE_KEY_HTML_QUOTE);
mQuotedTextBar.setVisibility(savedInstanceState.getBoolean(STATE_KEY_QUOTED_TEXT_SHOWN) ? View.VISIBLE : View.GONE);
mQuotedHTML.setVisibility(savedInstanceState.getBoolean(STATE_KEY_QUOTED_TEXT_SHOWN) ? View.VISIBLE : View.GONE);
if (mQuotedHtmlContent != null && mQuotedHtmlContent.getQuotedContent() != null) { if (mQuotedHtmlContent != null && mQuotedHtmlContent.getQuotedContent() != null) {
mQuotedHTML.loadDataWithBaseURL("http://", mQuotedHtmlContent.getQuotedContent(), "text/html", "utf-8", null); mQuotedHTML.loadDataWithBaseURL("http://", mQuotedHtmlContent.getQuotedContent(), "text/html", "utf-8", null);
mQuotedTextEdit.setVisibility(View.VISIBLE);
} }
} else {
mQuotedTextBar.setVisibility(savedInstanceState.getBoolean(STATE_KEY_QUOTED_TEXT_SHOWN) ? View.VISIBLE : View.GONE);
mQuotedText.setVisibility(savedInstanceState.getBoolean(STATE_KEY_QUOTED_TEXT_SHOWN) ? View.VISIBLE : View.GONE);
} }
mDraftUid = savedInstanceState.getString(STATE_KEY_DRAFT_UID); mDraftUid = savedInstanceState.getString(STATE_KEY_DRAFT_UID);
mIdentity = (Identity)savedInstanceState.getSerializable(STATE_IDENTITY); mIdentity = (Identity)savedInstanceState.getSerializable(STATE_IDENTITY);
@ -904,10 +909,27 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
String text = mMessageContentView.getText().toString(); String text = mMessageContentView.getText().toString();
boolean discardQuotedText = false;
if (!isDraft && !mQuotedTextMode.equals(QuotedTextMode.SHOW)) {
discardQuotedText = true;
}
if (discardQuotedText) {
if (!isDraft) {
text = appendSignature(text);
}
// Build the body.
TextBody body = new TextBody(text);
body.setComposedMessageLength(text.length());
body.setComposedMessageOffset(0);
return body;
}
// Handle HTML separate from the rest of the text content. HTML mode doesn't allow signature after the quoted // Handle HTML separate from the rest of the text content. HTML mode doesn't allow signature after the quoted
// text, nor does it allow reply after quote. Users who want that functionality will need to stick with text // text, nor does it allow reply after quote. Users who want that functionality will need to stick with text
// mode. // mode.
if (mMessageFormat == MessageFormat.HTML) { else if (mMessageFormat == MessageFormat.HTML) {
// Add the signature. // Add the signature.
if (!isDraft) { if (!isDraft) {
text = appendSignature(text); text = appendSignature(text);
@ -917,10 +939,6 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
if (K9.DEBUG && mQuotedHtmlContent != null) if (K9.DEBUG && mQuotedHtmlContent != null)
Log.d(K9.LOG_TAG, "insertable: " + mQuotedHtmlContent.toDebugString()); Log.d(K9.LOG_TAG, "insertable: " + mQuotedHtmlContent.toDebugString());
if (mQuotedHtmlContent != null) { if (mQuotedHtmlContent != null) {
// Remove the quoted part if it's no longer visible.
if (mQuotedTextBar.getVisibility() != View.VISIBLE) {
mQuotedHtmlContent.clearQuotedContent();
}
// Set the insertion location based upon our reply after quote setting. Reply after // Set the insertion location based upon our reply after quote setting. Reply after
// quote makes no sense for HEADER style replies. In addition, add some extra // quote makes no sense for HEADER style replies. In addition, add some extra
@ -965,7 +983,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
text = appendSignature(text); text = appendSignature(text);
} }
if (mQuotedTextBar.getVisibility() == View.VISIBLE) { if (mQuotedTextMode != QuotedTextMode.NONE) {
if (replyAfterQuote) { if (replyAfterQuote) {
composedMessageOffset = mQuotedText.getText().toString().length() + "\n".length(); composedMessageOffset = mQuotedText.getText().toString().length() + "\n".length();
text = mQuotedText.getText().toString() + "\n" + text; text = mQuotedText.getText().toString() + "\n" + text;
@ -1121,7 +1139,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
*/ */
bp.addHeader(MimeHeader.HEADER_CONTENT_DISPOSITION, String.format( bp.addHeader(MimeHeader.HEADER_CONTENT_DISPOSITION, String.format(
"attachment;\n filename=\"%s\";\n size=%d", "attachment;\n filename=\"%s\";\n size=%d",
EncoderUtil.encodeIfNecessary(attachment.name, EncoderUtil.encodeIfNecessary(attachment.name,
EncoderUtil.Usage.WORD_ENTITY, 7), EncoderUtil.Usage.WORD_ENTITY, 7),
attachment.size)); attachment.size));
@ -1139,7 +1157,9 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
NAME("n"), NAME("n"),
EMAIL("e"), EMAIL("e"),
// TODO - store a reference to the message being replied so we can mark it at the time of send. // TODO - store a reference to the message being replied so we can mark it at the time of send.
ORIGINAL_MESSAGE("m"); ORIGINAL_MESSAGE("m"),
CURSOR_POSITION("p"), // Where in the message your cursor was when you saved.
QUOTED_TEXT_MODE("q");
private final String value; private final String value;
@ -1202,6 +1222,10 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
uri.appendQueryParameter(IdentityField.ORIGINAL_MESSAGE.value(), mMessageReference.toIdentityString()); uri.appendQueryParameter(IdentityField.ORIGINAL_MESSAGE.value(), mMessageReference.toIdentityString());
} }
uri.appendQueryParameter(IdentityField.CURSOR_POSITION.value(), Integer.toString(mMessageContentView.getSelectionStart()));
uri.appendQueryParameter(IdentityField.QUOTED_TEXT_MODE.value(), mQuotedTextMode.name());
String k9identity = IDENTITY_VERSION_1 + uri.build().getEncodedQuery(); String k9identity = IDENTITY_VERSION_1 + uri.build().getEncodedQuery();
if (K9.DEBUG) { if (K9.DEBUG) {
@ -1276,6 +1300,9 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
if (tokens.hasMoreTokens()) { if (tokens.hasMoreTokens()) {
identity.put(IdentityField.EMAIL, Utility.base64Decode(tokens.nextToken())); identity.put(IdentityField.EMAIL, Utility.base64Decode(tokens.nextToken()));
} }
if (tokens.hasMoreTokens()) {
identity.put(IdentityField.QUOTED_TEXT_MODE, Utility.base64Decode(tokens.nextToken()));
}
} }
return identity; return identity;
@ -1652,8 +1679,12 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
mAttachments.removeView((View) view.getTag()); mAttachments.removeView((View) view.getTag());
mDraftNeedsSaving = true; mDraftNeedsSaving = true;
break; break;
case R.id.quoted_text_show:
showOrHideQuotedText(QuotedTextMode.SHOW);
mDraftNeedsSaving = true;
break;
case R.id.quoted_text_delete: case R.id.quoted_text_delete:
deleteQuotedText(); showOrHideQuotedText(QuotedTextMode.HIDE);
mDraftNeedsSaving = true; mDraftNeedsSaving = true;
break; break;
case R.id.quoted_text_edit: case R.id.quoted_text_edit:
@ -1670,15 +1701,37 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
} }
} }
/** /*
* Delete the quoted text. * Show or Hide the quoted text according to mQuotedTextMode.
*/ */
private void deleteQuotedText() { private void showOrHideQuotedText(QuotedTextMode mode) {
mQuotedTextBar.setVisibility(View.GONE); mQuotedTextMode = mode;
mQuotedText.setVisibility(View.GONE); if (mQuotedTextMode == QuotedTextMode.NONE) {
mQuotedHTML.setVisibility(View.GONE); mQuotedTextShow.setVisibility(View.GONE);
if (mQuotedHtmlContent != null) { mQuotedTextBar.setVisibility(View.GONE);
mQuotedHtmlContent.clearQuotedContent();
mQuotedText.setVisibility(View.GONE);
mQuotedHTML.setVisibility(View.GONE);
mQuotedTextEdit.setVisibility(View.GONE);
} else if (mQuotedTextMode == QuotedTextMode.SHOW) {
mQuotedTextShow.setVisibility(View.GONE);
mQuotedTextBar.setVisibility(View.VISIBLE);
if (mMessageFormat == MessageFormat.HTML) {
mQuotedText.setVisibility(View.GONE);
mQuotedHTML.setVisibility(View.VISIBLE);
mQuotedTextEdit.setVisibility(View.VISIBLE);
} else {
mQuotedText.setVisibility(View.VISIBLE);
mQuotedHTML.setVisibility(View.GONE);
mQuotedTextEdit.setVisibility(View.GONE);
}
} else if (mQuotedTextMode == QuotedTextMode.HIDE) {
mQuotedTextShow.setVisibility(View.VISIBLE);
mQuotedTextBar.setVisibility(View.GONE);
mQuotedText.setVisibility(View.GONE);
mQuotedHTML.setVisibility(View.GONE);
mQuotedTextEdit.setVisibility(View.GONE);
} }
} }
@ -1908,7 +1961,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
} }
// Quote the message and setup the UI. // Quote the message and setup the UI.
populateUIWithQuotedMessage(); populateUIWithQuotedMessage(mAccount.isDefaultQuotedTextShown());
if (ACTION_REPLY_ALL.equals(action) || ACTION_REPLY.equals(action)) { if (ACTION_REPLY_ALL.equals(action) || ACTION_REPLY.equals(action)) {
Identity useIdentity = null; Identity useIdentity = null;
@ -1963,7 +2016,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
} }
// Quote the message and setup the UI. // Quote the message and setup the UI.
populateUIWithQuotedMessage(); populateUIWithQuotedMessage(true);
if (!mSourceMessageProcessed) { if (!mSourceMessageProcessed) {
if (!loadAttachments(message, 0)) { if (!loadAttachments(message, 0)) {
@ -1971,6 +2024,8 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
} }
} }
} else if (ACTION_EDIT_DRAFT.equals(action)) { } else if (ACTION_EDIT_DRAFT.equals(action)) {
String showQuotedTextMode = "NONE";
mDraftUid = message.getUid(); mDraftUid = message.getUid();
mSubjectView.setText(message.getSubject()); mSubjectView.setText(message.getSubject());
addAddresses(mToView, message.getRecipients(RecipientType.TO)); addAddresses(mToView, message.getRecipients(RecipientType.TO));
@ -2047,6 +2102,19 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
} }
} }
int cursorPosition = 0;
if (k9identity.containsKey(IdentityField.CURSOR_POSITION)) {
try {
cursorPosition = Integer.valueOf(k9identity.get(IdentityField.CURSOR_POSITION)).intValue();
} catch (Exception e) {
Log.e(K9.LOG_TAG, "Could not parse cursor position for MessageCompose; continuing.", e);
}
}
if (k9identity.containsKey(IdentityField.QUOTED_TEXT_MODE)) {
showQuotedTextMode = k9identity.get(IdentityField.QUOTED_TEXT_MODE);
}
mIdentity = newIdentity; mIdentity = newIdentity;
updateSignature(); updateSignature();
@ -2061,96 +2129,72 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
// Always respect the user's current composition format preference, even if the // Always respect the user's current composition format preference, even if the
// draft was saved in a different format. // draft was saved in a different format.
// TODO - The current implementation doesn't allow a user in HTML mode to edit a draft that wasn't saved with K9mail. // TODO - The current implementation doesn't allow a user in HTML mode to edit a draft that wasn't saved with K9mail.
String messageFormat = k9identity.get(IdentityField.MESSAGE_FORMAT);
if (messageFormat == null) {
// This message probably wasn't created by us. The exception is legacy
// drafts created before the advent of HTML composition. In those cases,
// we'll display the whole message (including the quoted part) in the
// composition window. If that's the case, try and convert it to text to
// match the behavior in text mode.
mMessageContentView.setText(getBodyTextFromMessage(message, MessageFormat.TEXT));
mMessageFormat = MessageFormat.TEXT;
showOrHideQuotedText(QuotedTextMode.valueOf(showQuotedTextMode));
return;
}
mMessageFormat = MessageFormat.valueOf(messageFormat);
if (mMessageFormat == MessageFormat.HTML) { if (mMessageFormat == MessageFormat.HTML) {
if (k9identity.get(IdentityField.MESSAGE_FORMAT) == null || !MessageFormat.valueOf(k9identity.get(IdentityField.MESSAGE_FORMAT)).equals(MessageFormat.HTML)) { Part part = MimeUtility.findFirstPartByMimeType(message, "text/html");
// This message probably wasn't created by us. The exception is legacy if (part != null) { // Shouldn't happen if we were the one who saved it.
// drafts created before the advent of HTML composition. In those cases, String text = MimeUtility.getTextFromPart(part);
// we'll display the whole message (including the quoted part) in the if (K9.DEBUG) {
// composition window. If that's the case, try and convert it to text to Log.d(K9.LOG_TAG, "Loading message with offset " + bodyOffset + ", length " + bodyLength + ". Text length is " + text.length() + ".");
// match the behavior in text mode. }
mMessageContentView.setText(getBodyTextFromMessage(message, MessageFormat.TEXT));
} else {
Part part = MimeUtility.findFirstPartByMimeType(message, "text/html");
if (part != null) { // Shouldn't happen if we were the one who saved it.
String text = MimeUtility.getTextFromPart(part);
if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "Loading message with offset " + bodyOffset + ", length " + bodyLength + ". Text length is " + text.length() + ".");
}
// Grab our reply text. // Grab our reply text.
String bodyText = text.substring(bodyOffset, bodyOffset + bodyLength); String bodyText = text.substring(bodyOffset, bodyOffset + bodyLength);
mMessageContentView.setText(HtmlConverter.htmlToText(bodyText)); mMessageContentView.setText(HtmlConverter.htmlToText(bodyText));
// Regenerate the quoted html without our user content in it. // Regenerate the quoted html without our user content in it.
StringBuilder quotedHTML = new StringBuilder(); StringBuilder quotedHTML = new StringBuilder();
quotedHTML.append(text.substring(0, bodyOffset)); // stuff before the reply quotedHTML.append(text.substring(0, bodyOffset)); // stuff before the reply
quotedHTML.append(text.substring(bodyOffset + bodyLength)); quotedHTML.append(text.substring(bodyOffset + bodyLength));
if (quotedHTML.length() > 0) { if (quotedHTML.length() > 0) {
mQuotedHtmlContent = new InsertableHtmlContent(); mQuotedHtmlContent = new InsertableHtmlContent();
mQuotedHtmlContent.setQuotedContent(quotedHTML); mQuotedHtmlContent.setQuotedContent(quotedHTML);
mQuotedHtmlContent.setHeaderInsertionPoint(bodyOffset); mQuotedHtmlContent.setHeaderInsertionPoint(bodyOffset);
mQuotedHTML.loadDataWithBaseURL("http://", mQuotedHtmlContent.getQuotedContent(), "text/html", "utf-8", null); mQuotedHTML.loadDataWithBaseURL("http://", mQuotedHtmlContent.getQuotedContent(), "text/html", "utf-8", null);
mQuotedHTML.setVisibility(View.VISIBLE);
mQuotedTextBar.setVisibility(View.VISIBLE);
mQuotedTextEdit.setVisibility(View.VISIBLE);
}
} }
} }
} else if (mMessageFormat == MessageFormat.TEXT) { } else if (mMessageFormat == MessageFormat.TEXT) {
MessageFormat format = k9identity.get(IdentityField.MESSAGE_FORMAT) != null Part textPart = MimeUtility.findFirstPartByMimeType(message, "text/plain");
? MessageFormat.valueOf(k9identity.get(IdentityField.MESSAGE_FORMAT)) if (textPart != null) {
: null; String text = MimeUtility.getTextFromPart(textPart);
if (format == null) { // If we had a body length (and it was valid), separate the composition from the quoted text
mMessageContentView.setText(getBodyTextFromMessage(message, MessageFormat.TEXT)); // and put them in their respective places in the UI.
} else if (format.equals(MessageFormat.HTML)) { if (bodyLength != null && bodyLength + 1 < text.length()) { // + 1 to get rid of the newline we added when saving the draft
// We are in text mode, but have an HTML message. String bodyText = text.substring(0, bodyLength);
Part htmlPart = MimeUtility.findFirstPartByMimeType(message, "text/html"); String quotedText = text.substring(bodyLength + 1, text.length());
if (htmlPart != null) { // Shouldn't happen if we were the one who saved it.
String text = MimeUtility.getTextFromPart(htmlPart);
if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "Loading message with offset " + bodyOffset + ", length " + bodyLength + ". Text length is " + text.length() + ".");
}
// Grab our reply text. mMessageContentView.setText(bodyText);
String bodyText = text.substring(bodyOffset, bodyOffset + bodyLength); mQuotedText.setText(quotedText);
mMessageContentView.setText(Html.fromHtml(bodyText).toString());
// Regenerate the quoted html without out content in it.
StringBuilder quotedHTML = new StringBuilder();
quotedHTML.append(text.substring(0, bodyOffset)); // stuff before the reply
quotedHTML.append(text.substring(bodyOffset + bodyLength));
// Convert it to text.
mQuotedText.setText(HtmlConverter.htmlToText(quotedHTML.toString()));
mQuotedTextBar.setVisibility(View.VISIBLE);
mQuotedText.setVisibility(View.VISIBLE);
} else { } else {
Log.e(K9.LOG_TAG, "Found an HTML draft but couldn't find the HTML part! Something's wrong."); mMessageContentView.setText(text);
} }
} else if (format.equals(MessageFormat.TEXT)) {
Part textPart = MimeUtility.findFirstPartByMimeType(message, "text/plain");
if (textPart != null) {
String text = MimeUtility.getTextFromPart(textPart);
// If we had a body length (and it was valid), separate the composition from the quoted text
// and put them in their respective places in the UI.
if (bodyLength != null && bodyLength + 1 < text.length()) { // + 1 to get rid of the newline we added when saving the draft
String bodyText = text.substring(0, bodyLength);
String quotedText = text.substring(bodyLength + 1, text.length());
mMessageContentView.setText(bodyText);
mQuotedText.setText(quotedText);
mQuotedTextBar.setVisibility(View.VISIBLE);
mQuotedText.setVisibility(View.VISIBLE);
mQuotedHTML.setVisibility(View.VISIBLE);
} else {
mMessageContentView.setText(text);
}
}
} else {
Log.e(K9.LOG_TAG, "Unhandled message format.");
} }
} else {
Log.e(K9.LOG_TAG, "Unhandled message format.");
} }
// Set the cursor position if we have it.
try {
mMessageContentView.setSelection(cursorPosition);
} catch (Exception e) {
Log.e(K9.LOG_TAG, "Could not set cursor position in MessageCompose; ignoring.", e);
}
showOrHideQuotedText(QuotedTextMode.valueOf(showQuotedTextMode));
} }
} catch (MessagingException me) { } catch (MessagingException me) {
/** /**
@ -2158,16 +2202,17 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
* the source message. Log it as an error, though. * the source message. Log it as an error, though.
*/ */
Log.e(K9.LOG_TAG, "Error while processing source message: ", me); Log.e(K9.LOG_TAG, "Error while processing source message: ", me);
} finally {
mSourceMessageProcessed = true;
mDraftNeedsSaving = false;
} }
mSourceMessageProcessed = true;
mDraftNeedsSaving = false;
} }
/** /**
* Build and populate the UI with the quoted message. * Build and populate the UI with the quoted message.
* @throws MessagingException * @throws MessagingException
*/ */
private void populateUIWithQuotedMessage() throws MessagingException { private void populateUIWithQuotedMessage(boolean shown) throws MessagingException {
// TODO -- I am assuming that mSourceMessageBody will always be a text part. Is this a safe assumption? // TODO -- I am assuming that mSourceMessageBody will always be a text part. Is this a safe assumption?
// Handle the original message in the reply // Handle the original message in the reply
@ -2181,20 +2226,14 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
// Load the message with the reply header. // Load the message with the reply header.
mQuotedHTML.loadDataWithBaseURL("http://", mQuotedHtmlContent.getQuotedContent(), "text/html", "utf-8", null); mQuotedHTML.loadDataWithBaseURL("http://", mQuotedHtmlContent.getQuotedContent(), "text/html", "utf-8", null);
mQuotedTextBar.setVisibility(View.VISIBLE);
mQuotedHTML.setVisibility(View.VISIBLE);
mQuotedTextEdit.setVisibility(View.VISIBLE);
mQuotedText.setVisibility(View.GONE);
} else if (mMessageFormat == MessageFormat.TEXT) { } else if (mMessageFormat == MessageFormat.TEXT) {
mQuotedText.setText(quoteOriginalTextMessage(mSourceMessage, content, mAccount.getQuoteStyle())); mQuotedText.setText(quoteOriginalTextMessage(mSourceMessage, content, mAccount.getQuoteStyle()));
}
mQuotedTextBar.setVisibility(View.VISIBLE); if (shown) {
mQuotedText.setVisibility(View.VISIBLE); showOrHideQuotedText(QuotedTextMode.SHOW);
} else {
mQuotedHtmlContent = null; showOrHideQuotedText(QuotedTextMode.HIDE);
mQuotedTextEdit.setVisibility(View.GONE);
mQuotedHTML.setVisibility(View.GONE);
} }
} }
@ -2406,10 +2445,10 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
// part). // part).
if (mSourceProcessed) { if (mSourceProcessed) {
try { try {
populateUIWithQuotedMessage(); populateUIWithQuotedMessage(true);
} catch (MessagingException e) { } catch (MessagingException e) {
// Hm, if we couldn't populate the UI after source reprocessing, let's just delete it? // Hm, if we couldn't populate the UI after source reprocessing, let's just delete it?
deleteQuotedText(); showOrHideQuotedText(QuotedTextMode.HIDE);
Log.e(K9.LOG_TAG, "Could not re-process source message; deleting quoted text to be safe.", e); Log.e(K9.LOG_TAG, "Could not re-process source message; deleting quoted text to be safe.", e);
} }
} else { } else {

View File

@ -12,13 +12,14 @@ import android.app.AlertDialog;
import android.app.Dialog; import android.app.Dialog;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.res.ColorStateList; import android.graphics.Color;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.Bundle; import android.os.Bundle;
import android.text.Spannable; import android.text.Spannable;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import android.text.style.TextAppearanceSpan; import android.text.style.ForegroundColorSpan;
import android.text.style.StyleSpan;
import android.util.Log; import android.util.Log;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.animation.Animation; import android.view.animation.Animation;
@ -491,7 +492,7 @@ public class MessageList
if (mFolderName != null) { if (mFolderName != null) {
displayName = mFolderName; displayName = mFolderName;
if (K9.INBOX.equalsIgnoreCase(displayName)) { if (mAccount.getInboxFolderName().equalsIgnoreCase(displayName)) {
displayName = getString(R.string.special_mailbox_name_inbox); displayName = getString(R.string.special_mailbox_name_inbox);
} else if (mAccount.getOutboxFolderName().equals(displayName)) { } else if (mAccount.getOutboxFolderName().equals(displayName)) {
displayName = getString(R.string.special_mailbox_name_outbox); displayName = getString(R.string.special_mailbox_name_outbox);
@ -565,9 +566,10 @@ public class MessageList
@Override @Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) { public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// Use mListView.getAdapter() to get the WrapperListAdapter that includes the footer view. if (view == mFooterView) {
if (mCurrentFolder != null && ((position + 1) == mListView.getAdapter().getCount())) { if (mCurrentFolder != null) {
mController.loadMoreMessages(mAccount, mFolderName, mAdapter.mListener); mController.loadMoreMessages(mAccount, mFolderName, mAdapter.mListener);
}
return; return;
} }
@ -726,6 +728,9 @@ public class MessageList
} }
} else { } else {
// reread the selected date format preference in case it has changed
mMessageHelper.refresh();
new Thread() { new Thread() {
@Override @Override
public void run() { public void run() {
@ -1139,7 +1144,7 @@ public class MessageList
} }
} }
private void moveToSpamFolder(MessageInfoHolder holder){ private void moveToSpamFolder(MessageInfoHolder holder) {
if (!mController.isMoveCapable(holder.message)) { if (!mController.isMoveCapable(holder.message)) {
Toast toast = Toast.makeText(this, R.string.move_copy_cannot_copy_unsynced_message, Toast.LENGTH_LONG); Toast toast = Toast.makeText(this, R.string.move_copy_cannot_copy_unsynced_message, Toast.LENGTH_LONG);
toast.show(); toast.show();
@ -1283,31 +1288,31 @@ public class MessageList
switch (id) { switch (id) {
case DIALOG_MARK_ALL_AS_READ: case DIALOG_MARK_ALL_AS_READ:
return ConfirmationDialog.create(this, id, return ConfirmationDialog.create(this, id,
R.string.mark_all_as_read_dlg_title, R.string.mark_all_as_read_dlg_title,
getString(R.string.mark_all_as_read_dlg_instructions_fmt, getString(R.string.mark_all_as_read_dlg_instructions_fmt,
mCurrentFolder.displayName), mCurrentFolder.displayName),
R.string.okay_action, R.string.okay_action,
R.string.cancel_action, R.string.cancel_action,
new Runnable() { new Runnable() {
@Override @Override
public void run() { public void run() {
markAllAsRead(); markAllAsRead();
} }
}); });
case R.id.dialog_confirm_spam: case R.id.dialog_confirm_spam:
return ConfirmationDialog.create(this, id, return ConfirmationDialog.create(this, id,
R.string.dialog_confirm_spam_title, R.string.dialog_confirm_spam_title,
R.string.dialog_confirm_spam_message, R.string.dialog_confirm_spam_message,
R.string.dialog_confirm_spam_confirm_button, R.string.dialog_confirm_spam_confirm_button,
R.string.dialog_confirm_spam_cancel_button, R.string.dialog_confirm_spam_cancel_button,
new Runnable() { new Runnable() {
@Override @Override
public void run() { public void run() {
moveToSpamFolder(mSelectedMessage); moveToSpamFolder(mSelectedMessage);
// No further need for this reference // No further need for this reference
mSelectedMessage = null; mSelectedMessage = null;
} }
}); });
} }
return super.onCreateDialog(id); return super.onCreateDialog(id);
@ -2156,13 +2161,14 @@ public class MessageList
holder.preview.setText(noSender, TextView.BufferType.SPANNABLE); holder.preview.setText(noSender, TextView.BufferType.SPANNABLE);
Spannable str = (Spannable) holder.preview.getText(); Spannable str = (Spannable) holder.preview.getText();
ColorStateList color = holder.subject.getTextColors(); str.setSpan(new StyleSpan(Typeface.NORMAL),
ColorStateList linkColor = holder.subject.getLinkTextColors();
str.setSpan(new TextAppearanceSpan(null, Typeface.NORMAL, mFontSizes.getMessageListSender(), color, linkColor),
0, 0,
noSender.length(), noSender.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
); str.setSpan(K9.createAbsoluteSizeSpan(mFontSizes.getMessageListSender()),
0,
noSender.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
} else { } else {
holder.from.setText(noSender); holder.from.setText(noSender);
holder.from.setTypeface(null, Typeface.NORMAL); holder.from.setTypeface(null, Typeface.NORMAL);
@ -2244,13 +2250,20 @@ public class MessageList
Spannable str = (Spannable)holder.preview.getText(); Spannable str = (Spannable)holder.preview.getText();
// Create a span section for the sender, and assign the correct font size and weight. // Create a span section for the sender, and assign the correct font size and weight.
ColorStateList color = holder.subject.getTextColors(); str.setSpan(new StyleSpan(senderTypeface),
ColorStateList linkColor = holder.subject.getLinkTextColors();
str.setSpan(new TextAppearanceSpan(null, senderTypeface, mFontSizes.getMessageListSender(), color, linkColor),
0, 0,
message.sender.length() + 1, message.sender.length() + 1,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
); str.setSpan(K9.createAbsoluteSizeSpan(mFontSizes.getMessageListSender()),
0,
message.sender.length() + 1,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
// set span for preview message.
str.setSpan(new ForegroundColorSpan(Color.rgb(128, 128, 128)), // How do I can specify the android.R.attr.textColorTertiary
message.sender.length() + 1,
str.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
} else { } else {
holder.from.setText(new SpannableStringBuilder(recipientSigil(message)).append(message.sender)); holder.from.setText(new SpannableStringBuilder(recipientSigil(message)).append(message.sender));

View File

@ -17,12 +17,16 @@ import com.fsck.k9.*;
import com.fsck.k9.controller.MessagingController; import com.fsck.k9.controller.MessagingController;
import com.fsck.k9.controller.MessagingListener; import com.fsck.k9.controller.MessagingListener;
import com.fsck.k9.crypto.PgpData; import com.fsck.k9.crypto.PgpData;
import com.fsck.k9.helper.FileBrowserHelper;
import com.fsck.k9.helper.FileBrowserHelper.FileBrowserFailOverCallback;
import com.fsck.k9.mail.*; import com.fsck.k9.mail.*;
import com.fsck.k9.mail.store.StorageManager; import com.fsck.k9.mail.store.StorageManager;
import com.fsck.k9.view.AttachmentView; import com.fsck.k9.view.AttachmentView;
import com.fsck.k9.view.ToggleScrollView; import com.fsck.k9.view.ToggleScrollView;
import com.fsck.k9.view.SingleMessageView; import com.fsck.k9.view.SingleMessageView;
import com.fsck.k9.view.AttachmentView.AttachmentFileDownloadCallback;
import java.io.File;
import java.util.*; import java.util.*;
public class MessageView extends K9Activity implements OnClickListener { public class MessageView extends K9Activity implements OnClickListener {
@ -33,7 +37,7 @@ public class MessageView extends K9Activity implements OnClickListener {
private static final String STATE_PGP_DATA = "pgpData"; private static final String STATE_PGP_DATA = "pgpData";
private static final int ACTIVITY_CHOOSE_FOLDER_MOVE = 1; private static final int ACTIVITY_CHOOSE_FOLDER_MOVE = 1;
private static final int ACTIVITY_CHOOSE_FOLDER_COPY = 2; private static final int ACTIVITY_CHOOSE_FOLDER_COPY = 2;
private static final int ACTIVITY_CHOOSE_DIRECTORY = 3;
private SingleMessageView mMessageView; private SingleMessageView mMessageView;
@ -61,6 +65,12 @@ public class MessageView extends K9Activity implements OnClickListener {
private MessageViewHandler mHandler = new MessageViewHandler(); private MessageViewHandler mHandler = new MessageViewHandler();
private StorageManager.StorageListener mStorageListener = new StorageListenerImplementation(); private StorageManager.StorageListener mStorageListener = new StorageListenerImplementation();
/** this variable is used to save the calling AttachmentView
* until the onActivityResult is called.
* => with this reference we can identity the caller
*/
private AttachmentView attachmentTmpStore;
/** /**
* Used to temporarily store the destination folder for refile operations if a confirmation * Used to temporarily store the destination folder for refile operations if a confirmation
* dialog is shown. * dialog is shown.
@ -295,6 +305,32 @@ public class MessageView extends K9Activity implements OnClickListener {
mTopView = mToggleScrollView = (ToggleScrollView) findViewById(R.id.top_view); mTopView = mToggleScrollView = (ToggleScrollView) findViewById(R.id.top_view);
mMessageView = (SingleMessageView) findViewById(R.id.message_view); mMessageView = (SingleMessageView) findViewById(R.id.message_view);
//set a callback for the attachment view. With this callback the attachmentview
//request the start of a filebrowser activity.
mMessageView.setAttachmentCallback(new AttachmentFileDownloadCallback() {
@Override
public void showFileBrowser(final AttachmentView caller) {
FileBrowserHelper.getInstance()
.showFileBrowserActivity(MessageView.this,
null,
MessageView.ACTIVITY_CHOOSE_DIRECTORY,
callback);
attachmentTmpStore = caller;
}
FileBrowserFailOverCallback callback = new FileBrowserFailOverCallback() {
@Override
public void onPathEntered(String path) {
attachmentTmpStore.writeFile(new File(path));
}
@Override
public void onCancel() {
// canceled, do nothing
}
};
});
mMessageView.initialize(this); mMessageView.initialize(this);
setTitle(""); setTitle("");
@ -712,6 +748,19 @@ public class MessageView extends K9Activity implements OnClickListener {
if (resultCode != RESULT_OK) if (resultCode != RESULT_OK)
return; return;
switch (requestCode) { switch (requestCode) {
case ACTIVITY_CHOOSE_DIRECTORY:
if (resultCode == RESULT_OK && data != null) {
// obtain the filename
Uri fileUri = data.getData();
if (fileUri != null) {
String filePath = fileUri.getPath();
if (filePath != null) {
attachmentTmpStore.writeFile(new File(filePath));
}
}
}
break;
case ACTIVITY_CHOOSE_FOLDER_MOVE: case ACTIVITY_CHOOSE_FOLDER_MOVE:
case ACTIVITY_CHOOSE_FOLDER_COPY: case ACTIVITY_CHOOSE_FOLDER_COPY:
if (data == null) if (data == null)
@ -774,7 +823,7 @@ public class MessageView extends K9Activity implements OnClickListener {
private void onMarkAsUnread() { private void onMarkAsUnread() {
if (mMessage != null) { if (mMessage != null) {
mController.setFlag(mAccount, mMessageReference.folderName, new String[] { mMessage.getUid() }, Flag.SEEN, false); // (Issue 3319) mController.setFlag(mAccount, mMessageReference.folderName, new String[] { mMessage.getUid() }, Flag.SEEN, false);
try { try {
mMessage.setFlag(Flag.SEEN, false); mMessage.setFlag(Flag.SEEN, false);
mMessageView.setHeaders(mMessage, mAccount); mMessageView.setHeaders(mMessage, mAccount);
@ -932,29 +981,29 @@ public class MessageView extends K9Activity implements OnClickListener {
switch (id) { switch (id) {
case R.id.dialog_confirm_delete: case R.id.dialog_confirm_delete:
return ConfirmationDialog.create(this, id, return ConfirmationDialog.create(this, id,
R.string.dialog_confirm_delete_title, R.string.dialog_confirm_delete_title,
R.string.dialog_confirm_delete_message, R.string.dialog_confirm_delete_message,
R.string.dialog_confirm_delete_confirm_button, R.string.dialog_confirm_delete_confirm_button,
R.string.dialog_confirm_delete_cancel_button, R.string.dialog_confirm_delete_cancel_button,
new Runnable() { new Runnable() {
@Override @Override
public void run() { public void run() {
delete(); delete();
} }
}); });
case R.id.dialog_confirm_spam: case R.id.dialog_confirm_spam:
return ConfirmationDialog.create(this, id, return ConfirmationDialog.create(this, id,
R.string.dialog_confirm_spam_title, R.string.dialog_confirm_spam_title,
R.string.dialog_confirm_spam_message, R.string.dialog_confirm_spam_message,
R.string.dialog_confirm_spam_confirm_button, R.string.dialog_confirm_spam_confirm_button,
R.string.dialog_confirm_spam_cancel_button, R.string.dialog_confirm_spam_cancel_button,
new Runnable() { new Runnable() {
@Override @Override
public void run() { public void run() {
refileMessage(mDstFolder); refileMessage(mDstFolder);
mDstFolder = null; mDstFolder = null;
} }
}); });
case R.id.dialog_attachment_progress: case R.id.dialog_attachment_progress:
ProgressDialog d = new ProgressDialog(this); ProgressDialog d = new ProgressDialog(this);
d.setIndeterminate(true); d.setIndeterminate(true);

View File

@ -88,6 +88,7 @@ public class AccountSettings extends K9PreferenceActivity {
private static final String PREFERENCE_MESSAGE_FORMAT = "message_format"; private static final String PREFERENCE_MESSAGE_FORMAT = "message_format";
private static final String PREFERENCE_QUOTE_PREFIX = "account_quote_prefix"; private static final String PREFERENCE_QUOTE_PREFIX = "account_quote_prefix";
private static final String PREFERENCE_QUOTE_STYLE = "quote_style"; private static final String PREFERENCE_QUOTE_STYLE = "quote_style";
private static final String PREFERENCE_DEFAULT_QUOTED_TEXT_SHOWN = "default_quoted_text_shown";
private static final String PREFERENCE_REPLY_AFTER_QUOTE = "reply_after_quote"; private static final String PREFERENCE_REPLY_AFTER_QUOTE = "reply_after_quote";
private static final String PREFERENCE_SYNC_REMOTE_DELETIONS = "account_sync_remote_deletetions"; private static final String PREFERENCE_SYNC_REMOTE_DELETIONS = "account_sync_remote_deletetions";
private static final String PREFERENCE_CRYPTO_APP = "crypto_app"; private static final String PREFERENCE_CRYPTO_APP = "crypto_app";
@ -145,6 +146,7 @@ public class AccountSettings extends K9PreferenceActivity {
private ListPreference mMessageFormat; private ListPreference mMessageFormat;
private ListPreference mQuoteStyle; private ListPreference mQuoteStyle;
private EditTextPreference mAccountQuotePrefix; private EditTextPreference mAccountQuotePrefix;
private CheckBoxPreference mAccountDefaultQuotedTextShown;
private CheckBoxPreference mReplyAfterQuote; private CheckBoxPreference mReplyAfterQuote;
private CheckBoxPreference mSyncRemoteDeletions; private CheckBoxPreference mSyncRemoteDeletions;
private CheckBoxPreference mSaveAllHeaders; private CheckBoxPreference mSaveAllHeaders;
@ -226,6 +228,9 @@ public class AccountSettings extends K9PreferenceActivity {
} }
}); });
mAccountDefaultQuotedTextShown = (CheckBoxPreference) findPreference(PREFERENCE_DEFAULT_QUOTED_TEXT_SHOWN);
mAccountDefaultQuotedTextShown.setChecked(mAccount.isDefaultQuotedTextShown());
mReplyAfterQuote = (CheckBoxPreference) findPreference(PREFERENCE_REPLY_AFTER_QUOTE); mReplyAfterQuote = (CheckBoxPreference) findPreference(PREFERENCE_REPLY_AFTER_QUOTE);
mReplyAfterQuote.setChecked(mAccount.isReplyAfterQuote()); mReplyAfterQuote.setChecked(mAccount.isReplyAfterQuote());
@ -695,12 +700,19 @@ public class AccountSettings extends K9PreferenceActivity {
mAccount.setMessageFormat(Account.MessageFormat.valueOf(mMessageFormat.getValue())); mAccount.setMessageFormat(Account.MessageFormat.valueOf(mMessageFormat.getValue()));
mAccount.setQuoteStyle(QuoteStyle.valueOf(mQuoteStyle.getValue())); mAccount.setQuoteStyle(QuoteStyle.valueOf(mQuoteStyle.getValue()));
mAccount.setQuotePrefix(mAccountQuotePrefix.getText()); mAccount.setQuotePrefix(mAccountQuotePrefix.getText());
mAccount.setDefaultQuotedTextShown(mAccountDefaultQuotedTextShown.isChecked());
mAccount.setReplyAfterQuote(mReplyAfterQuote.isChecked()); mAccount.setReplyAfterQuote(mReplyAfterQuote.isChecked());
mAccount.setCryptoApp(mCryptoApp.getValue()); mAccount.setCryptoApp(mCryptoApp.getValue());
mAccount.setCryptoAutoSignature(mCryptoAutoSignature.isChecked()); mAccount.setCryptoAutoSignature(mCryptoAutoSignature.isChecked());
mAccount.setLocalStorageProviderId(mLocalStorageProvider.getValue()); mAccount.setLocalStorageProviderId(mLocalStorageProvider.getValue());
mAccount.setAutoExpandFolderName(reverseTranslateFolder(mAutoExpandFolder.getValue())); // In webdav account we use the exact folder name also for inbox,
// since it varies because of internationalization
if (mAccount.getStoreUri().startsWith("webdav"))
mAccount.setAutoExpandFolderName(mAutoExpandFolder.getValue());
else
mAccount.setAutoExpandFolderName(reverseTranslateFolder(mAutoExpandFolder.getValue()));
mAccount.setArchiveFolderName(mArchiveFolder.getValue()); mAccount.setArchiveFolderName(mArchiveFolder.getValue());
mAccount.setDraftsFolderName(mDraftsFolder.getValue()); mAccount.setDraftsFolderName(mDraftsFolder.getValue());
mAccount.setSentFolderName(mSentFolder.getValue()); mAccount.setSentFolderName(mSentFolder.getValue());
@ -826,7 +838,7 @@ public class AccountSettings extends K9PreferenceActivity {
} }
private String translateFolder(String in) { private String translateFolder(String in) {
if (K9.INBOX.equalsIgnoreCase(in)) { if (mAccount.getInboxFolderName().equalsIgnoreCase(in)) {
return getString(R.string.special_mailbox_name_inbox); return getString(R.string.special_mailbox_name_inbox);
} else { } else {
return in; return in;
@ -835,7 +847,7 @@ public class AccountSettings extends K9PreferenceActivity {
private String reverseTranslateFolder(String in) { private String reverseTranslateFolder(String in) {
if (getString(R.string.special_mailbox_name_inbox).equals(in)) { if (getString(R.string.special_mailbox_name_inbox).equals(in)) {
return K9.INBOX; return mAccount.getInboxFolderName();
} else { } else {
return in; return in;
} }
@ -867,7 +879,7 @@ public class AccountSettings extends K9PreferenceActivity {
Iterator <? extends Folder > iter = folders.iterator(); Iterator <? extends Folder > iter = folders.iterator();
while (iter.hasNext()) { while (iter.hasNext()) {
Folder folder = iter.next(); Folder folder = iter.next();
if (mAccount.getOutboxFolderName().equalsIgnoreCase(folder.getName())) { if (mAccount.getOutboxFolderName().equals(folder.getName())) {
iter.remove(); iter.remove();
} }
} }

View File

@ -18,7 +18,6 @@ import android.widget.CheckBox;
import android.widget.EditText; import android.widget.EditText;
import com.fsck.k9.*; import com.fsck.k9.*;
import com.fsck.k9.activity.K9Activity; import com.fsck.k9.activity.K9Activity;
import com.fsck.k9.helper.Contacts;
import com.fsck.k9.helper.Utility; import com.fsck.k9.helper.Utility;
import java.io.Serializable; import java.io.Serializable;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
@ -134,17 +133,11 @@ public class AccountSetupBasics extends K9Activity
private String getOwnerName() { private String getOwnerName() {
String name = null; String name = null;
try { try {
name = Contacts.getInstance(this).getOwnerName(); name = getDefaultAccountName();
} catch (Exception e) { } catch (Exception e) {
Log.e(K9.LOG_TAG, "Could not get owner name, using default account name", e); Log.e(K9.LOG_TAG, "Could not get default account name", e);
}
if (name == null || name.length() == 0) {
try {
name = getDefaultAccountName();
} catch (Exception e) {
Log.e(K9.LOG_TAG, "Could not get default account name", e);
}
} }
if (name == null) { if (name == null) {
name = ""; name = "";
} }

View File

@ -5,6 +5,7 @@ import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.Process; import android.os.Process;
@ -30,6 +31,8 @@ import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.util.Collection;
import java.util.List;
/** /**
* Checks the given settings to make sure that they can be used to send and * Checks the given settings to make sure that they can be used to send and
@ -116,7 +119,7 @@ public class AccountSetupCheckSettings extends K9Activity implements OnClickList
setMessage(R.string.account_setup_check_settings_fetch); setMessage(R.string.account_setup_check_settings_fetch);
} }
MessagingController.getInstance(getApplication()).listFoldersSynchronous(mAccount, true, null); MessagingController.getInstance(getApplication()).listFoldersSynchronous(mAccount, true, null);
MessagingController.getInstance(getApplication()).synchronizeMailbox(mAccount, K9.INBOX , null, null); MessagingController.getInstance(getApplication()).synchronizeMailbox(mAccount, mAccount.getInboxFolderName(), null, null);
} }
if (mDestroyed) { if (mDestroyed) {
return; return;
@ -249,8 +252,78 @@ public class AccountSetupCheckSettings extends K9Activity implements OnClickList
} }
for (int i = 0; i < chain.length; i++) { for (int i = 0; i < chain.length; i++) {
// display certificate chain information // display certificate chain information
//TODO: localize this strings
chainInfo.append("Certificate chain[" + i + "]:\n"); chainInfo.append("Certificate chain[" + i + "]:\n");
chainInfo.append("Subject: " + chain[i].getSubjectDN().toString() + "\n"); chainInfo.append("Subject: " + chain[i].getSubjectDN().toString() + "\n");
// display SubjectAltNames too
// (the user may be mislead into mistrusting a certificate
// by a subjectDN not matching the server even though a
// SubjectAltName matches)
try {
final Collection < List<? >> subjectAlternativeNames = chain[i].getSubjectAlternativeNames();
if (subjectAlternativeNames != null) {
// The list of SubjectAltNames may be very long
//TODO: localize this string
StringBuffer altNamesText = new StringBuffer("Subject has " + subjectAlternativeNames.size() + " alternative names\n");
// we need these for matching
String storeURIHost = (Uri.parse(mAccount.getStoreUri())).getHost();
String transportURIHost = (Uri.parse(mAccount.getTransportUri())).getHost();
for (List<?> subjectAlternativeName : subjectAlternativeNames) {
Integer type = (Integer)subjectAlternativeName.get(0);
Object value = subjectAlternativeName.get(1);
String name = "";
switch (type.intValue()) {
case 0:
Log.w(K9.LOG_TAG, "SubjectAltName of type OtherName not supported.");
continue;
case 1: // RFC822Name
name = (String)value;
break;
case 2: // DNSName
name = (String)value;
break;
case 3:
Log.w(K9.LOG_TAG, "unsupported SubjectAltName of type x400Address");
continue;
case 4:
Log.w(K9.LOG_TAG, "unsupported SubjectAltName of type directoryName");
continue;
case 5:
Log.w(K9.LOG_TAG, "unsupported SubjectAltName of type ediPartyName");
continue;
case 6: // Uri
name = (String)value;
break;
case 7: // ip-address
name = (String)value;
break;
default:
Log.w(K9.LOG_TAG, "unsupported SubjectAltName of unknown type");
continue;
}
// if some of the SubjectAltNames match the store or transport -host,
// display them
if (name.equalsIgnoreCase(storeURIHost) || name.equalsIgnoreCase(transportURIHost)) {
//TODO: localize this string
altNamesText.append("Subject(alt): " + name + ",...\n");
} else if (name.startsWith("*.")) {
if (storeURIHost.endsWith(name.substring(2)) || transportURIHost.endsWith(name.substring(2))) {
//TODO: localize this string
altNamesText.append("Subject(alt): " + name + ",...\n");
}
}
}
chainInfo.append(altNamesText);
}
} catch (Exception e1) {
// don't fail just because of subjectAltNames
Log.w(K9.LOG_TAG, "cannot display SubjectAltNames in dialog", e1);
}
chainInfo.append("Issuer: " + chain[i].getIssuerDN().toString() + "\n"); chainInfo.append("Issuer: " + chain[i].getIssuerDN().toString() + "\n");
if (sha1 != null) { if (sha1 != null) {
sha1.reset(); sha1.reset();

View File

@ -65,10 +65,10 @@ public class AccountSetupIncoming extends K9Activity implements OnClickListener
private Button mNextButton; private Button mNextButton;
private Account mAccount; private Account mAccount;
private boolean mMakeDefault; private boolean mMakeDefault;
private CheckBox compressionMobile; private CheckBox mCompressionMobile;
private CheckBox compressionWifi; private CheckBox mCompressionWifi;
private CheckBox compressionOther; private CheckBox mCompressionOther;
private CheckBox subscribedFoldersOnly; private CheckBox mSubscribedFoldersOnly;
public static void actionIncomingSettings(Activity context, Account account, boolean makeDefault) { public static void actionIncomingSettings(Activity context, Account account, boolean makeDefault) {
Intent i = new Intent(context, AccountSetupIncoming.class); Intent i = new Intent(context, AccountSetupIncoming.class);
@ -101,10 +101,10 @@ public class AccountSetupIncoming extends K9Activity implements OnClickListener
mWebdavAuthPathView = (EditText)findViewById(R.id.webdav_auth_path); mWebdavAuthPathView = (EditText)findViewById(R.id.webdav_auth_path);
mWebdavMailboxPathView = (EditText)findViewById(R.id.webdav_mailbox_path); mWebdavMailboxPathView = (EditText)findViewById(R.id.webdav_mailbox_path);
mNextButton = (Button)findViewById(R.id.next); mNextButton = (Button)findViewById(R.id.next);
compressionMobile = (CheckBox)findViewById(R.id.compression_mobile); mCompressionMobile = (CheckBox)findViewById(R.id.compression_mobile);
compressionWifi = (CheckBox)findViewById(R.id.compression_wifi); mCompressionWifi = (CheckBox)findViewById(R.id.compression_wifi);
compressionOther = (CheckBox)findViewById(R.id.compression_other); mCompressionOther = (CheckBox)findViewById(R.id.compression_other);
subscribedFoldersOnly = (CheckBox)findViewById(R.id.subscribed_folders_only); mSubscribedFoldersOnly = (CheckBox)findViewById(R.id.subscribed_folders_only);
mNextButton.setOnClickListener(this); mNextButton.setOnClickListener(this);
@ -236,6 +236,7 @@ public class AccountSetupIncoming extends K9Activity implements OnClickListener
findViewById(R.id.webdav_auth_path_section).setVisibility(View.GONE); findViewById(R.id.webdav_auth_path_section).setVisibility(View.GONE);
findViewById(R.id.compression_section).setVisibility(View.GONE); findViewById(R.id.compression_section).setVisibility(View.GONE);
findViewById(R.id.compression_label).setVisibility(View.GONE); findViewById(R.id.compression_label).setVisibility(View.GONE);
mSubscribedFoldersOnly.setVisibility(View.GONE);
mAccount.setDeletePolicy(Account.DELETE_POLICY_NEVER); mAccount.setDeletePolicy(Account.DELETE_POLICY_NEVER);
} else if (uri.getScheme().startsWith("imap")) { } else if (uri.getScheme().startsWith("imap")) {
serverLabelView.setText(R.string.account_setup_incoming_imap_server_label); serverLabelView.setText(R.string.account_setup_incoming_imap_server_label);
@ -266,7 +267,7 @@ public class AccountSetupIncoming extends K9Activity implements OnClickListener
findViewById(R.id.account_auth_type).setVisibility(View.GONE); findViewById(R.id.account_auth_type).setVisibility(View.GONE);
findViewById(R.id.compression_section).setVisibility(View.GONE); findViewById(R.id.compression_section).setVisibility(View.GONE);
findViewById(R.id.compression_label).setVisibility(View.GONE); findViewById(R.id.compression_label).setVisibility(View.GONE);
subscribedFoldersOnly.setVisibility(View.GONE); mSubscribedFoldersOnly.setVisibility(View.GONE);
if (uri.getPath() != null && uri.getPath().length() > 0) { if (uri.getPath() != null && uri.getPath().length() > 0) {
String[] pathParts = uri.getPath().split("\\|"); String[] pathParts = uri.getPath().split("\\|");
@ -299,9 +300,9 @@ public class AccountSetupIncoming extends K9Activity implements OnClickListener
SpinnerOption.setSpinnerOptionValue(mSecurityTypeView, i); SpinnerOption.setSpinnerOptionValue(mSecurityTypeView, i);
} }
} }
compressionMobile.setChecked(mAccount.useCompression(Account.TYPE_MOBILE)); mCompressionMobile.setChecked(mAccount.useCompression(Account.TYPE_MOBILE));
compressionWifi.setChecked(mAccount.useCompression(Account.TYPE_WIFI)); mCompressionWifi.setChecked(mAccount.useCompression(Account.TYPE_WIFI));
compressionOther.setChecked(mAccount.useCompression(Account.TYPE_OTHER)); mCompressionOther.setChecked(mAccount.useCompression(Account.TYPE_OTHER));
if (uri.getHost() != null) { if (uri.getHost() != null) {
mServerView.setText(uri.getHost()); mServerView.setText(uri.getHost());
@ -313,7 +314,7 @@ public class AccountSetupIncoming extends K9Activity implements OnClickListener
updatePortFromSecurityType(); updatePortFromSecurityType();
} }
subscribedFoldersOnly.setChecked(mAccount.subscribedFoldersOnly()); mSubscribedFoldersOnly.setChecked(mAccount.subscribedFoldersOnly());
validateFields(); validateFields();
} catch (Exception e) { } catch (Exception e) {
@ -409,9 +410,9 @@ public class AccountSetupIncoming extends K9Activity implements OnClickListener
} else { } else {
String authType = ((SpinnerOption)mAuthTypeView.getSelectedItem()).label; String authType = ((SpinnerOption)mAuthTypeView.getSelectedItem()).label;
if (!authType.equalsIgnoreCase("plain")) { if (!authType.equalsIgnoreCase("plain")) {
userInfo = authType + ":" + userEnc + ":" + passwordEnc; userInfo = authType + ":" + userEnc + ":" + passwordEnc;
} else { } else {
userInfo = userEnc + ":" + passwordEnc; userInfo = userEnc + ":" + passwordEnc;
} }
} }
URI uri = new URI( URI uri = new URI(
@ -425,10 +426,10 @@ public class AccountSetupIncoming extends K9Activity implements OnClickListener
mAccount.setStoreUri(uri.toString()); mAccount.setStoreUri(uri.toString());
mAccount.setCompression(Account.TYPE_MOBILE, compressionMobile.isChecked()); mAccount.setCompression(Account.TYPE_MOBILE, mCompressionMobile.isChecked());
mAccount.setCompression(Account.TYPE_WIFI, compressionWifi.isChecked()); mAccount.setCompression(Account.TYPE_WIFI, mCompressionWifi.isChecked());
mAccount.setCompression(Account.TYPE_OTHER, compressionOther.isChecked()); mAccount.setCompression(Account.TYPE_OTHER, mCompressionOther.isChecked());
mAccount.setSubscribedFoldersOnly(subscribedFoldersOnly.isChecked()); mAccount.setSubscribedFoldersOnly(mSubscribedFoldersOnly.isChecked());
AccountSetupCheckSettings.actionCheckSettings(this, mAccount, true, false); AccountSetupCheckSettings.actionCheckSettings(this, mAccount, true, false);
} catch (Exception e) { } catch (Exception e) {

View File

@ -16,6 +16,8 @@ import android.widget.CompoundButton.OnCheckedChangeListener;
import com.fsck.k9.*; import com.fsck.k9.*;
import com.fsck.k9.activity.K9Activity; import com.fsck.k9.activity.K9Activity;
import com.fsck.k9.helper.Utility; import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.transport.SmtpTransport;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
@ -45,10 +47,13 @@ public class AccountSetupOutgoing extends K9Activity implements OnClickListener,
"webdav", "webdav+ssl", "webdav+ssl+", "webdav+tls", "webdav+tls+" "webdav", "webdav+ssl", "webdav+ssl+", "webdav+tls", "webdav+tls+"
}; };
*/ */
private static final String authTypes[] = { private static final String authTypes[] = {
"PLAIN", "CRAM_MD5" SmtpTransport.AUTH_AUTOMATIC,
SmtpTransport.AUTH_LOGIN,
SmtpTransport.AUTH_PLAIN,
SmtpTransport.AUTH_CRAM_MD5,
}; };
private EditText mUsernameView; private EditText mUsernameView;
private EditText mPasswordView; private EditText mPasswordView;
private EditText mServerView; private EditText mServerView;
@ -117,14 +122,10 @@ public class AccountSetupOutgoing extends K9Activity implements OnClickListener,
new SpinnerOption(4, getString(R.string.account_setup_incoming_security_tls_label)), new SpinnerOption(4, getString(R.string.account_setup_incoming_security_tls_label)),
}; };
// This needs to be kept in sync with the list at the top of the file. SpinnerOption authTypeSpinnerOptions[] = new SpinnerOption[authTypes.length];
// that makes me somewhat unhappy for (int i = 0; i < authTypes.length; i++) {
SpinnerOption authTypeSpinnerOptions[] = { authTypeSpinnerOptions[i] = new SpinnerOption(i, authTypes[i]);
new SpinnerOption(0, "PLAIN"), }
new SpinnerOption(1, "CRAM_MD5")
};
ArrayAdapter<SpinnerOption> securityTypesAdapter = new ArrayAdapter<SpinnerOption>(this, ArrayAdapter<SpinnerOption> securityTypesAdapter = new ArrayAdapter<SpinnerOption>(this,
android.R.layout.simple_spinner_item, securityTypes); android.R.layout.simple_spinner_item, securityTypes);

View File

@ -1,5 +1,6 @@
package com.fsck.k9.activity.setup; package com.fsck.k9.activity.setup;
import java.io.File;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.Vector; import java.util.Vector;
@ -8,11 +9,13 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor; import android.content.SharedPreferences.Editor;
import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.preference.CheckBoxPreference; import android.preference.CheckBoxPreference;
import android.preference.ListPreference; import android.preference.ListPreference;
import android.preference.Preference; import android.preference.Preference;
import android.preference.Preference.OnPreferenceClickListener;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.widget.Toast; import android.widget.Toast;
@ -23,6 +26,8 @@ import com.fsck.k9.activity.Accounts;
import com.fsck.k9.activity.ColorPickerDialog; import com.fsck.k9.activity.ColorPickerDialog;
import com.fsck.k9.activity.K9PreferenceActivity; import com.fsck.k9.activity.K9PreferenceActivity;
import com.fsck.k9.helper.DateFormatter; import com.fsck.k9.helper.DateFormatter;
import com.fsck.k9.helper.FileBrowserHelper;
import com.fsck.k9.helper.FileBrowserHelper.FileBrowserFailOverCallback;
import com.fsck.k9.preferences.CheckBoxListPreference; import com.fsck.k9.preferences.CheckBoxListPreference;
import com.fsck.k9.preferences.TimePickerPreference; import com.fsck.k9.preferences.TimePickerPreference;
@ -76,7 +81,9 @@ public class Prefs extends K9PreferenceActivity {
private static final String PREFERENCE_DEBUG_LOGGING = "debug_logging"; private static final String PREFERENCE_DEBUG_LOGGING = "debug_logging";
private static final String PREFERENCE_SENSITIVE_LOGGING = "sensitive_logging"; private static final String PREFERENCE_SENSITIVE_LOGGING = "sensitive_logging";
private static final String PREFERENCE_ATTACHMENT_DEF_PATH = "attachment_default_path";
private static final int ACTIVITY_CHOOSE_FOLDER = 1;
private ListPreference mLanguage; private ListPreference mLanguage;
private ListPreference mTheme; private ListPreference mTheme;
private ListPreference mDateFormat; private ListPreference mDateFormat;
@ -110,7 +117,7 @@ public class Prefs extends K9PreferenceActivity {
private CheckBoxPreference mQuietTimeEnabled; private CheckBoxPreference mQuietTimeEnabled;
private com.fsck.k9.preferences.TimePickerPreference mQuietTimeStarts; private com.fsck.k9.preferences.TimePickerPreference mQuietTimeStarts;
private com.fsck.k9.preferences.TimePickerPreference mQuietTimeEnds; private com.fsck.k9.preferences.TimePickerPreference mQuietTimeEnds;
private Preference mAttachmentPathPreference;
public static void actionPrefs(Context context) { public static void actionPrefs(Context context) {
@ -182,15 +189,15 @@ public class Prefs extends K9PreferenceActivity {
mConfirmActions = (CheckBoxListPreference) findPreference(PREFERENCE_CONFIRM_ACTIONS); mConfirmActions = (CheckBoxListPreference) findPreference(PREFERENCE_CONFIRM_ACTIONS);
mConfirmActions.setItems(new CharSequence[] { mConfirmActions.setItems(new CharSequence[] {
getString(R.string.global_settings_confirm_action_delete), getString(R.string.global_settings_confirm_action_delete),
getString(R.string.global_settings_confirm_action_spam), getString(R.string.global_settings_confirm_action_spam),
getString(R.string.global_settings_confirm_action_mark_all_as_read) getString(R.string.global_settings_confirm_action_mark_all_as_read)
}); });
mConfirmActions.setCheckedItems(new boolean[] { mConfirmActions.setCheckedItems(new boolean[] {
K9.confirmDelete(), K9.confirmDelete(),
K9.confirmSpam(), K9.confirmSpam(),
K9.confirmMarkAllAsRead() K9.confirmMarkAllAsRead()
}); });
mPrivacyMode = (CheckBoxPreference) findPreference(PREFERENCE_PRIVACY_MODE); mPrivacyMode = (CheckBoxPreference) findPreference(PREFERENCE_PRIVACY_MODE);
mPrivacyMode.setChecked(K9.keyguardPrivacy()); mPrivacyMode.setChecked(K9.keyguardPrivacy());
@ -298,6 +305,36 @@ public class Prefs extends K9PreferenceActivity {
mDebugLogging.setChecked(K9.DEBUG); mDebugLogging.setChecked(K9.DEBUG);
mSensitiveLogging.setChecked(K9.DEBUG_SENSITIVE); mSensitiveLogging.setChecked(K9.DEBUG_SENSITIVE);
mAttachmentPathPreference = findPreference(PREFERENCE_ATTACHMENT_DEF_PATH);
mAttachmentPathPreference.setSummary(K9.getAttachmentDefaultPath());
mAttachmentPathPreference
.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
FileBrowserHelper
.getInstance()
.showFileBrowserActivity(Prefs.this,
new File(K9.getAttachmentDefaultPath()),
ACTIVITY_CHOOSE_FOLDER, callback);
return true;
}
FileBrowserFailOverCallback callback = new FileBrowserFailOverCallback() {
@Override
public void onPathEntered(String path) {
mAttachmentPathPreference.setSummary(path);
K9.setAttachmentDefaultPath(path);
}
@Override
public void onCancel() {
// canceled, do nothing
}
};
});
} }
private void saveSettings() { private void saveSettings() {
@ -336,7 +373,7 @@ public class Prefs extends K9PreferenceActivity {
K9.setZoomControlsEnabled(mZoomControlsEnabled.isChecked()); K9.setZoomControlsEnabled(mZoomControlsEnabled.isChecked());
K9.setAttachmentDefaultPath(mAttachmentPathPreference.getSummary().toString());
boolean needsRefresh = K9.setBackgroundOps(mBackgroundOps.getValue()); boolean needsRefresh = K9.setBackgroundOps(mBackgroundOps.getValue());
K9.setUseGalleryBugWorkaround(mUseGalleryBugWorkaround.isChecked()); K9.setUseGalleryBugWorkaround(mUseGalleryBugWorkaround.isChecked());
@ -381,4 +418,25 @@ public class Prefs extends K9PreferenceActivity {
}, },
K9.getContactNameColor()).show(); K9.getContactNameColor()).show();
} }
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case ACTIVITY_CHOOSE_FOLDER:
if (resultCode == RESULT_OK && data != null) {
// obtain the filename
Uri fileUri = data.getData();
if (fileUri != null) {
String filePath = fileUri.getPath();
if (filePath != null) {
mAttachmentPathPreference.setSummary(filePath.toString());
K9.setAttachmentDefaultPath(filePath.toString());
}
}
}
break;
}
super.onActivityResult(requestCode, resultCode, data);
}
} }

View File

@ -719,7 +719,7 @@ public class MessagingController implements Runnable {
} }
} }
// Never exclude the INBOX (see issue 1817) // Never exclude the INBOX (see issue 1817)
else if (noSpecialFolders && !localFolderName.equalsIgnoreCase(K9.INBOX) && else if (noSpecialFolders && !localFolderName.equalsIgnoreCase(account.getInboxFolderName()) &&
!localFolderName.equals(account.getArchiveFolderName()) && account.isSpecialFolder(localFolderName)) { !localFolderName.equals(account.getArchiveFolderName()) && account.isSpecialFolder(localFolderName)) {
include = false; include = false;
} else if (displayableOnly && modeMismatch(account.getFolderDisplayMode(), folder.getDisplayClass())) { } else if (displayableOnly && modeMismatch(account.getFolderDisplayMode(), folder.getDisplayClass())) {
@ -793,7 +793,7 @@ public class MessagingController implements Runnable {
LocalStore localStore = account.getLocalStore(); LocalStore localStore = account.getLocalStore();
LocalFolder localFolder = localStore.getFolder(folder); LocalFolder localFolder = localStore.getFolder(folder);
if (localFolder.getVisibleLimit() > 0) { if (localFolder.getVisibleLimit() > 0) {
localFolder.setVisibleLimit(localFolder.getVisibleLimit() + account.getDisplayCount()); localFolder.setVisibleLimit(localFolder.getVisibleLimit() + localFolder.getMessageCount());
} }
synchronizeMailbox(account, folder, listener, null); synchronizeMailbox(account, folder, listener, null);
} catch (MessagingException me) { } catch (MessagingException me) {
@ -1161,12 +1161,32 @@ public class MessagingController implements Runnable {
} }
} }
/**
* Fetches the messages described by inputMessages from the remote store and writes them to
* local storage.
*
* @param account
* The account the remote store belongs to.
* @param remoteFolder
* The remote folder to download messages from.
* @param localFolder
* The {@link LocalFolder} instance corresponding to the remote folder.
* @param inputMessages
* A list of messages objects that store the UIDs of which messages to download.
* @param flagSyncOnly
* Only flags will be fetched from the remote store if this is {@code true}.
*
* @return The number of downloaded messages that are not flagged as {@link Flag#SEEN}.
*
* @throws MessagingException
*/
private int downloadMessages(final Account account, final Folder remoteFolder, private int downloadMessages(final Account account, final Folder remoteFolder,
final LocalFolder localFolder, List<Message> inputMessages, boolean flagSyncOnly) throws MessagingException { final LocalFolder localFolder, List<Message> inputMessages,
boolean flagSyncOnly) throws MessagingException {
final Date earliestDate = account.getEarliestPollDate(); final Date earliestDate = account.getEarliestPollDate();
Date downloadStarted = new Date(); // now Date downloadStarted = new Date(); // now
if (earliestDate != null) { if (earliestDate != null) {
if (K9.DEBUG) { if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "Only syncing messages after " + earliestDate); Log.d(K9.LOG_TAG, "Only syncing messages after " + earliestDate);
@ -1541,7 +1561,7 @@ public class MessagingController implements Runnable {
remoteFolder.fetch(smallMessages.toArray(new Message[smallMessages.size()]), remoteFolder.fetch(smallMessages.toArray(new Message[smallMessages.size()]),
fp, new MessageRetrievalListener() { fp, new MessageRetrievalListener() {
@Override @Override
public void messageFinished(Message message, int number, int ofTotal) { public void messageFinished(final Message message, int number, int ofTotal) {
try { try {
if (!shouldImportMessage(account, folder, message, progress, earliestDate)) { if (!shouldImportMessage(account, folder, message, progress, earliestDate)) {
@ -1557,6 +1577,13 @@ public class MessagingController implements Runnable {
progress.incrementAndGet(); progress.incrementAndGet();
} }
}); });
// Increment the number of "new messages" if the newly downloaded message is
// not marked as read.
if (!localMessage.isSet(Flag.SEEN)) {
newMessages.incrementAndGet();
}
if (K9.DEBUG) if (K9.DEBUG)
Log.v(K9.LOG_TAG, "About to notify listeners that we got a new small message " Log.v(K9.LOG_TAG, "About to notify listeners that we got a new small message "
+ account + ":" + folder + ":" + message.getUid()); + account + ":" + folder + ":" + message.getUid());
@ -1572,7 +1599,6 @@ public class MessagingController implements Runnable {
// Send a notification of this message // Send a notification of this message
if (shouldNotifyForMessage(account, localFolder, message)) { if (shouldNotifyForMessage(account, localFolder, message)) {
newMessages.incrementAndGet();
notifyAccount(mApplication, account, message, unreadBeforeStart, newMessages); notifyAccount(mApplication, account, message, unreadBeforeStart, newMessages);
} }
@ -1691,6 +1717,13 @@ public class MessagingController implements Runnable {
// Update the listener with what we've found // Update the listener with what we've found
progress.incrementAndGet(); progress.incrementAndGet();
Message localMessage = localFolder.getMessage(message.getUid()); Message localMessage = localFolder.getMessage(message.getUid());
// Increment the number of "new messages" if the newly downloaded message is
// not marked as read.
if (!localMessage.isSet(Flag.SEEN)) {
newMessages.incrementAndGet();
}
for (MessagingListener l : getListeners()) { for (MessagingListener l : getListeners()) {
l.synchronizeMailboxAddOrUpdateMessage(account, folder, localMessage); l.synchronizeMailboxAddOrUpdateMessage(account, folder, localMessage);
l.synchronizeMailboxProgress(account, folder, progress.get(), todo); l.synchronizeMailboxProgress(account, folder, progress.get(), todo);
@ -1701,7 +1734,6 @@ public class MessagingController implements Runnable {
// Send a notification of this message // Send a notification of this message
if (shouldNotifyForMessage(account, localFolder, message)) { if (shouldNotifyForMessage(account, localFolder, message)) {
newMessages.incrementAndGet();
notifyAccount(mApplication, account, message, unreadBeforeStart, newMessages); notifyAccount(mApplication, account, message, unreadBeforeStart, newMessages);
} }
@ -2174,14 +2206,7 @@ public class MessagingController implements Runnable {
Store remoteStore = account.getRemoteStore(); Store remoteStore = account.getRemoteStore();
Folder remoteFolder = remoteStore.getFolder(folder); Folder remoteFolder = remoteStore.getFolder(folder);
if (!remoteFolder.exists() || if (!remoteFolder.exists() || !remoteFolder.isFlagSupported(flag)) {
/*
* Don't proceed if the remote folder doesn't support flags and
* the flag to be changed isn't the deleted flag. This avoids
* unnecessary connections to POP3 servers.
*/
// TODO: This should actually call a supportsSettingFlag(flag) method.
(!remoteFolder.supportsFetchingFlags() && !Flag.DELETED.equals(flag))) {
return; return;
} }
@ -2385,7 +2410,7 @@ public class MessagingController implements Runnable {
Store remoteStore = account.getRemoteStore(); Store remoteStore = account.getRemoteStore();
remoteFolder = remoteStore.getFolder(folder); remoteFolder = remoteStore.getFolder(folder);
if (!remoteFolder.exists()) { if (!remoteFolder.exists() || !remoteFolder.isFlagSupported(Flag.SEEN)) {
return; return;
} }
remoteFolder.open(OpenMode.READ_WRITE); remoteFolder.open(OpenMode.READ_WRITE);
@ -2880,7 +2905,7 @@ public class MessagingController implements Runnable {
(NotificationManager)mApplication.getSystemService(Context.NOTIFICATION_SERVICE); (NotificationManager)mApplication.getSystemService(Context.NOTIFICATION_SERVICE);
Notification notif = new Notification(R.drawable.ic_menu_refresh, Notification notif = new Notification(R.drawable.ic_menu_refresh,
mApplication.getString(R.string.notification_bg_send_ticker, account.getDescription()), System.currentTimeMillis()); mApplication.getString(R.string.notification_bg_send_ticker, account.getDescription()), System.currentTimeMillis());
Intent intent = MessageList.actionHandleFolderIntent(mApplication, account, K9.INBOX); Intent intent = MessageList.actionHandleFolderIntent(mApplication, account, account.getInboxFolderName());
PendingIntent pi = PendingIntent.getActivity(mApplication, 0, intent, 0); PendingIntent pi = PendingIntent.getActivity(mApplication, 0, intent, 0);
notif.setLatestEventInfo(mApplication, mApplication.getString(R.string.notification_bg_send_title), notif.setLatestEventInfo(mApplication, mApplication.getString(R.string.notification_bg_send_title),
account.getDescription() , pi); account.getDescription() , pi);
@ -2922,7 +2947,7 @@ public class MessagingController implements Runnable {
Notification notif = new Notification(R.drawable.ic_menu_refresh, Notification notif = new Notification(R.drawable.ic_menu_refresh,
mApplication.getString(R.string.notification_bg_sync_ticker, account.getDescription(), folder.getName()), mApplication.getString(R.string.notification_bg_sync_ticker, account.getDescription(), folder.getName()),
System.currentTimeMillis()); System.currentTimeMillis());
Intent intent = MessageList.actionHandleFolderIntent(mApplication, account, K9.INBOX); Intent intent = MessageList.actionHandleFolderIntent(mApplication, account, account.getInboxFolderName());
PendingIntent pi = PendingIntent.getActivity(mApplication, 0, intent, 0); PendingIntent pi = PendingIntent.getActivity(mApplication, 0, intent, 0);
notif.setLatestEventInfo(mApplication, mApplication.getString(R.string.notification_bg_sync_title), account.getDescription() notif.setLatestEventInfo(mApplication, mApplication.getString(R.string.notification_bg_sync_title), account.getDescription()
+ mApplication.getString(R.string.notification_bg_title_separator) + folder.getName(), pi); + mApplication.getString(R.string.notification_bg_title_separator) + folder.getName(), pi);
@ -3025,6 +3050,16 @@ public class MessagingController implements Runnable {
localFolder.fetch(new Message[] { message }, fp, null); localFolder.fetch(new Message[] { message }, fp, null);
try { try {
if (message.getHeader(K9.IDENTITY_HEADER) != null) {
Log.v(K9.LOG_TAG, "The user has set the Outbox and Drafts folder to the same thing. " +
"This message appears to be a draft, so K-9 will not send it");
continue;
}
message.setFlag(Flag.X_SEND_IN_PROGRESS, true); message.setFlag(Flag.X_SEND_IN_PROGRESS, true);
if (K9.DEBUG) if (K9.DEBUG)
Log.i(K9.LOG_TAG, "Sending message with UID " + message.getUid()); Log.i(K9.LOG_TAG, "Sending message with UID " + message.getUid());
@ -3856,18 +3891,31 @@ public class MessagingController implements Runnable {
private boolean shouldNotifyForMessage(Account account, LocalFolder localFolder, Message message) { private boolean shouldNotifyForMessage(Account account, LocalFolder localFolder, Message message) {
// Do not notify if the user does not have notifications // If we don't even have an account name, don't show the notification.
// enabled or if the message has been read // (This happens during initial account setup)
if (!account.isNotifyNewMail() || message.isSet(Flag.SEEN) || (account.getName() == null)) { if (account.getName() == null) {
return false; return false;
} }
// Do not notify if the user does not have notifications enabled or if the message has
// been read.
if (!account.isNotifyNewMail() || message.isSet(Flag.SEEN)) {
return false;
}
// If the account is a POP3 account and the message is older than the oldest message we've
// previously seen, then don't notify about it.
if (account.getStoreUri().startsWith("pop3") &&
message.olderThan(new Date(account.getLatestOldMessageSeenTime()))) {
return false;
}
// No notification for new messages in Trash, Drafts, Spam or Sent folder.
// But do notify if it's the INBOX (see issue 1817).
Folder folder = message.getFolder(); Folder folder = message.getFolder();
if (folder != null) { if (folder != null) {
// No notification for new messages in Trash, Drafts, Spam or Sent folder.
// But do notify if it's the INBOX (see issue 1817).
String folderName = folder.getName(); String folderName = folder.getName();
if (!K9.INBOX.equals(folderName) && if (!account.getInboxFolderName().equals(folderName) &&
(account.getTrashFolderName().equals(folderName) (account.getTrashFolderName().equals(folderName)
|| account.getDraftsFolderName().equals(folderName) || account.getDraftsFolderName().equals(folderName)
|| account.getSpamFolderName().equals(folderName) || account.getSpamFolderName().equals(folderName)
@ -3881,7 +3929,8 @@ public class MessagingController implements Runnable {
Integer messageUid = Integer.parseInt(message.getUid()); Integer messageUid = Integer.parseInt(message.getUid());
if (messageUid <= localFolder.getLastUid()) { if (messageUid <= localFolder.getLastUid()) {
if (K9.DEBUG) if (K9.DEBUG)
Log.d(K9.LOG_TAG, "Message uid is " + messageUid + ", max message uid is " + localFolder.getLastUid() + ". Skipping notification."); Log.d(K9.LOG_TAG, "Message uid is " + messageUid + ", max message uid is " +
localFolder.getLastUid() + ". Skipping notification.");
return false; return false;
} }
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
@ -3889,31 +3938,22 @@ public class MessagingController implements Runnable {
} }
} }
return true; // Don't notify if the sender address matches one of our identities and the user chose not
// to be notified for such messages.
if (account.isAnIdentity(message.getFrom()) && !account.isNotifySelfNewMail()) {
return false;
}
return true;
} }
/** Creates a notification of new email messages /**
* ringtone, lights, and vibration to be played * Creates a notification of a newly received message.
*/ */
private boolean notifyAccount(Context context, Account account, Message message, int previousUnreadMessageCount, AtomicInteger newMessageCount) { private void notifyAccount(Context context, Account account, Message message,
// If we don't even have an account name, don't show the notification int previousUnreadMessageCount, AtomicInteger newMessageCount) {
// (This happens during initial account setup)
//
if (account.getName() == null) {
return false;
}
// If the account us a POP3 account and the message is older than
// the oldest message we've previously seen then don't notify about it
if (account.getStoreUri().startsWith("pop3")) {
if (message.olderThan(new Date(account.getLatestOldMessageSeenTime()))) {
return false;
}
}
// If we have a message, set the notification to "<From>: <Subject>" // If we have a message, set the notification to "<From>: <Subject>"
StringBuilder messageNotice = new StringBuilder(); StringBuilder messageNotice = new StringBuilder();
@ -3934,19 +3974,13 @@ public class MessagingController implements Runnable {
} }
// show To: if the message was sent from me // show To: if the message was sent from me
else { else {
if (!account.isNotifySelfNewMail()) {
return false;
}
Address[] rcpts = message.getRecipients(Message.RecipientType.TO); Address[] rcpts = message.getRecipients(Message.RecipientType.TO);
String to = rcpts.length > 0 ? rcpts[0].toFriendly().toString() : null; String to = rcpts.length > 0 ? rcpts[0].toFriendly().toString() : null;
if (to != null) { if (to != null) {
messageNotice.append(String.format(context.getString(R.string.message_to_fmt), to)).append(": ").append(subject); messageNotice.append(String.format(context.getString(R.string.message_to_fmt), to)).append(": ").append(subject);
} else { } else {
messageNotice.append(context.getString(R.string.general_no_sender)).append(": ").append(subject); messageNotice.append(context.getString(R.string.general_no_sender)).append(": ").append(subject);
} }
} }
} }
} }
@ -3972,7 +4006,8 @@ public class MessagingController implements Runnable {
Intent i = FolderList.actionHandleNotification(context, account, message.getFolder().getName()); Intent i = FolderList.actionHandleNotification(context, account, message.getFolder().getName());
PendingIntent pi = PendingIntent.getActivity(context, 0, i, 0); PendingIntent pi = PendingIntent.getActivity(context, 0, i, 0);
String accountNotice = context.getString(R.string.notification_new_one_account_fmt, unreadCount, account.getDescription()); String accountDescr = (account.getDescription() != null) ? account.getDescription() : account.getEmail();
String accountNotice = context.getString(R.string.notification_new_one_account_fmt, unreadCount, accountDescr);
notif.setLatestEventInfo(context, accountNotice, messageNotice, pi); notif.setLatestEventInfo(context, accountNotice, messageNotice, pi);
// Only ring or vibrate if we have not done so already on this // Only ring or vibrate if we have not done so already on this
@ -3988,7 +4023,6 @@ public class MessagingController implements Runnable {
configureNotification(notif, (n.shouldRing() ? n.getRingtone() : null), (n.shouldVibrate() ? n.getVibration() : null), (n.isLed() ? n.getLedColor() : null), K9.NOTIFICATION_LED_BLINK_SLOW, ringAndVibrate); configureNotification(notif, (n.shouldRing() ? n.getRingtone() : null), (n.shouldVibrate() ? n.getVibration() : null), (n.isLed() ? n.getLedColor() : null), K9.NOTIFICATION_LED_BLINK_SLOW, ringAndVibrate);
notifMgr.notify(account.getAccountNumber(), notif); notifMgr.notify(account.getAccountNumber(), notif);
return true;
} }
/** /**

View File

@ -97,13 +97,6 @@ public abstract class Contacts {
mContentResolver = context.getContentResolver(); mContentResolver = context.getContentResolver();
} }
/**
* Get the name of the device's owner.
*
* @return The name of the owner if available. <tt>null</tt>, otherwise.
*/
public abstract String getOwnerName();
/** /**
* Start the activity to add information to an existing contact or add a * Start the activity to add information to an existing contact or add a
* new one. * new one.
@ -195,7 +188,7 @@ public abstract class Contacts {
public boolean hasContactPicker() { public boolean hasContactPicker() {
if (mHasContactPicker == null) { if (mHasContactPicker == null) {
mHasContactPicker = (mContext.getPackageManager(). mHasContactPicker = (mContext.getPackageManager().
queryIntentActivities(contactPickerIntent(), 0).size() > 0); queryIntentActivities(contactPickerIntent(), 0).size() > 0);
} }
return mHasContactPicker; return mHasContactPicker;
} }

View File

@ -85,27 +85,6 @@ public class ContactsSdk3_4 extends com.fsck.k9.helper.Contacts {
mContext.startActivity(contactIntent); mContext.startActivity(contactIntent);
} }
@Override
public String getOwnerName() {
String name = null;
final Cursor c = mContentResolver.query(
Uri.withAppendedPath(Contacts.People.CONTENT_URI, "owner"),
new String[] {Contacts.ContactMethods.DISPLAY_NAME},
null,
null,
null);
if (c != null) {
if (c.getCount() > 0) {
c.moveToFirst();
name = c.getString(0); // owner's display name
}
c.close();
}
return name;
}
@Override @Override
public boolean isInContacts(final String emailAddress) { public boolean isInContacts(final String emailAddress) {
boolean result = false; boolean result = false;
@ -239,11 +218,11 @@ public class ContactsSdk3_4 extends com.fsck.k9.helper.Contacts {
if (cursor.moveToFirst()) { if (cursor.moveToFirst()) {
String emailId = cursor.getString(cursor.getColumnIndex(Contacts.People.PRIMARY_EMAIL_ID)); String emailId = cursor.getString(cursor.getColumnIndex(Contacts.People.PRIMARY_EMAIL_ID));
cursor2 = mContext.getContentResolver().query( cursor2 = mContext.getContentResolver().query(
ContactMethods.CONTENT_EMAIL_URI, ContactMethods.CONTENT_EMAIL_URI,
new String[] { ContactMethods.DATA }, new String[] { ContactMethods.DATA },
"contact_methods._id=?", "contact_methods._id=?",
new String[] { emailId }, new String[] { emailId },
null); null);
if (cursor2.moveToFirst()) { if (cursor2.moveToFirst()) {
email = cursor2.getString(0); email = cursor2.getString(0);

View File

@ -1,7 +1,5 @@
package com.fsck.k9.helper; package com.fsck.k9.helper;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.database.Cursor; import android.database.Cursor;
@ -87,22 +85,6 @@ public class ContactsSdk5 extends com.fsck.k9.helper.Contacts {
mContext.startActivity(contactIntent); mContext.startActivity(contactIntent);
} }
@Override
public String getOwnerName() {
String name = null;
// Get the name of the first account that has one.
Account[] accounts = AccountManager.get(mContext).getAccounts();
for (final Account account : accounts) {
if (account.name != null) {
name = account.name;
break;
}
}
return name;
}
@Override @Override
public boolean isInContacts(final String emailAddress) { public boolean isInContacts(final String emailAddress) {
boolean result = false; boolean result = false;

View File

@ -0,0 +1,134 @@
package com.fsck.k9.helper;
import java.io.File;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.text.InputType;
import android.widget.EditText;
import com.fsck.k9.K9;
import com.fsck.k9.R;
public class FileBrowserHelper {
/**
* A string array that specifies the name of the intent to use, and the scheme to use with it
* when setting the data for the intent.
*/
private static final String[][] PICK_DIRECTORY_INTENTS = {
{ "org.openintents.action.PICK_DIRECTORY", "file://" }, // OI File Manager (maybe others)
{ "com.estrongs.action.PICK_DIRECTORY", "file://" }, // ES File Explorer
{ Intent.ACTION_PICK, "folder://" }, // Blackmoon File Browser (maybe others)
{ "com.androidworkz.action.PICK_DIRECTORY", "file://" }
}; // SystemExplorer
private static FileBrowserHelper sInstance;
/**
* callback class to provide the result of the fallback textedit path dialog
*/
public interface FileBrowserFailOverCallback {
/**
* the user has entered a path
* @param path the path as String
*/
public void onPathEntered(String path);
/**
* the user has cancel the inputtext dialog
*/
public void onCancel();
}
/**
* factory method
*
*/
private FileBrowserHelper() {
}
public synchronized static FileBrowserHelper getInstance() {
if (sInstance == null) {
sInstance = new FileBrowserHelper();
}
return sInstance;
}
/**
* tries to open known filebrowsers.
* If no filebrowser is found and fallback textdialog is shown
* @param c the context as activity
* @param startPath: the default value, where the filebrowser will start.
* if startPath = null => the default path is used
* @param requestcode: the int you will get as requestcode in onActivityResult
* (only used if there is a filebrowser installed)
* @param callback: the callback (only used when no filebrowser is installed.
* if a filebrowser is installed => override the onActivtyResult Method
*
* @return true: if a filebrowser has been found (the result will be in the onActivityResult
* false: a fallback textinput has been shown. The Result will be sent with the callback method
*
*
*/
public boolean showFileBrowserActivity(Activity c, File startPath, int requestcode, FileBrowserFailOverCallback callback) {
boolean success = false;
if (startPath == null) {
startPath = new File(K9.getAttachmentDefaultPath());
}
int listIndex = 0;
do {
String intentAction = PICK_DIRECTORY_INTENTS[listIndex][0];
String uriPrefix = PICK_DIRECTORY_INTENTS[listIndex][1];
Intent intent = new Intent(intentAction);
intent.setData(Uri.parse(uriPrefix + startPath.getPath()));
try {
c.startActivityForResult(intent, requestcode);
success = true;
} catch (ActivityNotFoundException e) {
// Try the next intent in the list
listIndex++;
};
} while (!success && (listIndex < PICK_DIRECTORY_INTENTS.length));
if (listIndex == PICK_DIRECTORY_INTENTS.length) {
//No Filebrowser is installed => show a fallback textdialog
showPathTextInput(c, startPath, callback);
success = false;
}
return success;
}
private void showPathTextInput(final Activity c, final File startPath, final FileBrowserFailOverCallback callback) {
AlertDialog.Builder alert = new AlertDialog.Builder(c);
alert.setTitle(c.getString(R.string.attachment_save_title));
alert.setMessage(c.getString(R.string.attachment_save_desc));
final EditText input = new EditText(c);
input.setInputType(InputType.TYPE_CLASS_TEXT);
if (startPath != null)
input.setText(startPath.toString());
alert.setView(input);
alert.setPositiveButton(c.getString(R.string.okay_action), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
String path = input.getText().toString();
callback.onPathEntered(path);
}
});
alert.setNegativeButton(c.getString(R.string.cancel_action),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
callback.onCancel();
}
});
alert.show();
}
}

View File

@ -123,6 +123,45 @@ public class HtmlConverter {
private static final int MAX_SMART_HTMLIFY_MESSAGE_LENGTH = 1024 * 256 ; private static final int MAX_SMART_HTMLIFY_MESSAGE_LENGTH = 1024 * 256 ;
/**
* Naively convert a text string into an HTML document. This method avoids using regular expressions on the entire
* message body to save memory.
* @param text Plain text string.
* @return HTML string.
*/
private static String simpleTextToHtml(String text) {
// Encode HTML entities to make sure we don't display something evil.
text = TextUtils.htmlEncode(text);
StringReader reader = new StringReader(text);
StringBuilder buff = new StringBuilder(text.length() + TEXT_TO_HTML_EXTRA_BUFFER_LENGTH);
buff.append("<html><head/><body>");
int c;
try {
while ((c = reader.read()) != -1) {
switch (c) {
case '\n':
// pine treats <br> as two newlines, but <br/> as one newline. Use <br/> so our messages aren't
// doublespaced.
buff.append("<br />");
break;
case '\r':
break;
default:
buff.append((char)c);
}//switch
}
} catch (IOException e) {
//Should never happen
Log.e(K9.LOG_TAG, "Could not read string to convert text to HTML:", e);
}
buff.append("</body></html>");
return buff.toString();
}
/** /**
* Convert a text string into an HTML document. Attempts to do smart replacement for large * Convert a text string into an HTML document. Attempts to do smart replacement for large
* documents to prevent OOM errors. This method adds headers and footers to create a proper HTML * documents to prevent OOM errors. This method adds headers and footers to create a proper HTML
@ -136,11 +175,7 @@ public class HtmlConverter {
// if the message is big and plain text, just do // if the message is big and plain text, just do
// a trivial htmlification // a trivial htmlification
if (text.length() > MAX_SMART_HTMLIFY_MESSAGE_LENGTH) { if (text.length() > MAX_SMART_HTMLIFY_MESSAGE_LENGTH) {
return "<html><head/><body>" + return simpleTextToHtml(text);
htmlifyMessageHeader() +
text +
htmlifyMessageFooter() +
"</body></html>";
} }
StringReader reader = new StringReader(text); StringReader reader = new StringReader(text);
StringBuilder buff = new StringBuilder(text.length() + TEXT_TO_HTML_EXTRA_BUFFER_LENGTH); StringBuilder buff = new StringBuilder(text.length() + TEXT_TO_HTML_EXTRA_BUFFER_LENGTH);
@ -148,6 +183,11 @@ public class HtmlConverter {
try { try {
while ((c = reader.read()) != -1) { while ((c = reader.read()) != -1) {
switch (c) { switch (c) {
case '\n':
// pine treats <br> as two newlines, but <br/> as one newline. Use <br/> so our messages aren't
// doublespaced.
buff.append("<br />");
break;
case '&': case '&':
buff.append("&amp;"); buff.append("&amp;");
break; break;
@ -168,8 +208,14 @@ public class HtmlConverter {
Log.e(K9.LOG_TAG, "Could not read string to convert text to HTML:", e); Log.e(K9.LOG_TAG, "Could not read string to convert text to HTML:", e);
} }
text = buff.toString(); text = buff.toString();
// Replace lines of -,= or _ with horizontal rules
text = text.replaceAll("\\s*([-=_]{30,}+)\\s*", "<hr />"); text = text.replaceAll("\\s*([-=_]{30,}+)\\s*", "<hr />");
// TODO: reverse engineer (or troll history) and document
text = text.replaceAll("(?m)^([^\r\n]{4,}[\\s\\w,:;+/])(?:\r\n|\n|\r)(?=[a-z]\\S{0,10}[\\s\\n\\r])", "$1 "); text = text.replaceAll("(?m)^([^\r\n]{4,}[\\s\\w,:;+/])(?:\r\n|\n|\r)(?=[a-z]\\S{0,10}[\\s\\n\\r])", "$1 ");
// Compress four or more newlines down to two newlines
text = text.replaceAll("(?m)(\r\n|\n|\r){4,}", "\n\n"); text = text.replaceAll("(?m)(\r\n|\n|\r){4,}", "\n\n");
StringBuffer sb = new StringBuffer(text.length() + TEXT_TO_HTML_EXTRA_BUFFER_LENGTH); StringBuffer sb = new StringBuffer(text.length() + TEXT_TO_HTML_EXTRA_BUFFER_LENGTH);

View File

@ -99,4 +99,9 @@ public class MessageHelper {
return mDateFormat.format(date); return mDateFormat.format(date);
} }
} }
public void refresh() {
mDateFormat = DateFormatter.getDateFormat(mContext);
mTodayDateFormat = android.text.format.DateFormat.getTimeFormat(mContext);
}
} }

View File

@ -57,24 +57,25 @@ public class Utility {
} }
/** /**
* Combines the given array of Objects into a single string using the * Combines the given array of Objects into a single String using
* seperator character and each Object's toString() method. between each * each Object's toString() method and the separator character
* part. * between each part.
* *
* @param parts * @param parts
* @param seperator * @param separator
* @return * @return new String
*/ */
public static String combine(Object[] parts, char seperator) { public static String combine(Object[] parts, char separator) {
if (parts == null) { if (parts == null) {
return null; return null;
} else if (parts.length == 0) {
return "";
} }
StringBuffer sb = new StringBuffer(); StringBuilder sb = new StringBuilder();
for (int i = 0; i < parts.length; i++) { sb.append(parts[0]);
sb.append(parts[i].toString()); for (int i = 1; i < parts.length; ++i) {
if (i < parts.length - 1) { sb.append(separator);
sb.append(seperator); sb.append(parts[i]);
}
} }
return sb.toString(); return sb.toString();
} }

View File

@ -6,80 +6,80 @@ import com.fsck.k9.mail.filter.Base64;
import com.fsck.k9.mail.filter.Hex; import com.fsck.k9.mail.filter.Hex;
public class Authentication { public class Authentication {
private static final String US_ASCII = "US-ASCII"; private static final String US_ASCII = "US-ASCII";
/** /**
* Computes the response for CRAM-MD5 authentication mechanism given the user credentials and * Computes the response for CRAM-MD5 authentication mechanism given the user credentials and
* the server-provided nonce. * the server-provided nonce.
* *
* @param username The username. * @param username The username.
* @param password The password. * @param password The password.
* @param b64Nonce The nonce as base64-encoded string. * @param b64Nonce The nonce as base64-encoded string.
* @return The CRAM-MD5 response. * @return The CRAM-MD5 response.
* *
* @throws AuthenticationFailedException If something went wrong. * @throws AuthenticationFailedException If something went wrong.
* *
* @see Authentication#computeCramMd5Bytes(String, String, byte[]) * @see Authentication#computeCramMd5Bytes(String, String, byte[])
*/ */
public static String computeCramMd5(String username, String password, String b64Nonce) public static String computeCramMd5(String username, String password, String b64Nonce)
throws AuthenticationFailedException { throws AuthenticationFailedException {
try { try {
byte[] b64NonceBytes = b64Nonce.getBytes(US_ASCII); byte[] b64NonceBytes = b64Nonce.getBytes(US_ASCII);
byte[] b64CRAM = computeCramMd5Bytes(username, password, b64NonceBytes); byte[] b64CRAM = computeCramMd5Bytes(username, password, b64NonceBytes);
return new String(b64CRAM, US_ASCII); return new String(b64CRAM, US_ASCII);
} catch (AuthenticationFailedException e) { } catch (AuthenticationFailedException e) {
throw e; throw e;
} catch (Exception e) { } catch (Exception e) {
throw new AuthenticationFailedException("This shouldn't happen", e); throw new AuthenticationFailedException("This shouldn't happen", e);
} }
} }
/** /**
* Computes the response for CRAM-MD5 authentication mechanism given the user credentials and * Computes the response for CRAM-MD5 authentication mechanism given the user credentials and
* the server-provided nonce. * the server-provided nonce.
* *
* @param username The username. * @param username The username.
* @param password The password. * @param password The password.
* @param b64Nonce The nonce as base64-encoded byte array. * @param b64Nonce The nonce as base64-encoded byte array.
* @return The CRAM-MD5 response as byte array. * @return The CRAM-MD5 response as byte array.
* *
* @throws AuthenticationFailedException If something went wrong. * @throws AuthenticationFailedException If something went wrong.
* *
* @see <a href="https://tools.ietf.org/html/rfc2195">RFC 2195</a> * @see <a href="https://tools.ietf.org/html/rfc2195">RFC 2195</a>
*/ */
public static byte[] computeCramMd5Bytes(String username, String password, byte[] b64Nonce) public static byte[] computeCramMd5Bytes(String username, String password, byte[] b64Nonce)
throws AuthenticationFailedException { throws AuthenticationFailedException {
try { try {
byte[] nonce = Base64.decodeBase64(b64Nonce); byte[] nonce = Base64.decodeBase64(b64Nonce);
byte[] secretBytes = password.getBytes(US_ASCII); byte[] secretBytes = password.getBytes(US_ASCII);
MessageDigest md = MessageDigest.getInstance("MD5"); MessageDigest md = MessageDigest.getInstance("MD5");
if (secretBytes.length > 64) { if (secretBytes.length > 64) {
secretBytes = md.digest(secretBytes); secretBytes = md.digest(secretBytes);
} }
byte[] ipad = new byte[64]; byte[] ipad = new byte[64];
byte[] opad = new byte[64]; byte[] opad = new byte[64];
System.arraycopy(secretBytes, 0, ipad, 0, secretBytes.length); System.arraycopy(secretBytes, 0, ipad, 0, secretBytes.length);
System.arraycopy(secretBytes, 0, opad, 0, secretBytes.length); System.arraycopy(secretBytes, 0, opad, 0, secretBytes.length);
for (int i = 0; i < ipad.length; i++) ipad[i] ^= 0x36; for (int i = 0; i < ipad.length; i++) ipad[i] ^= 0x36;
for (int i = 0; i < opad.length; i++) opad[i] ^= 0x5c; for (int i = 0; i < opad.length; i++) opad[i] ^= 0x5c;
md.update(ipad); md.update(ipad);
byte[] firstPass = md.digest(nonce); byte[] firstPass = md.digest(nonce);
md.update(opad); md.update(opad);
byte[] result = md.digest(firstPass); byte[] result = md.digest(firstPass);
String plainCRAM = username + " " + new String(Hex.encodeHex(result)); String plainCRAM = username + " " + new String(Hex.encodeHex(result));
byte[] b64CRAM = Base64.encodeBase64(plainCRAM.getBytes(US_ASCII)); byte[] b64CRAM = Base64.encodeBase64(plainCRAM.getBytes(US_ASCII));
return b64CRAM; return b64CRAM;
} catch (Exception e) { } catch (Exception e) {
throw new AuthenticationFailedException("Something went wrong during CRAM-MD5 computation", e); throw new AuthenticationFailedException("Something went wrong during CRAM-MD5 computation", e);
} }
} }
} }

View File

@ -153,9 +153,13 @@ public abstract class Folder {
return null; return null;
} }
public boolean isFlagSupported(Flag flag) {
return true;
}
public boolean supportsFetchingFlags() { public boolean supportsFetchingFlags() {
return true; return true;
}//isFlagSupported }
@Override @Override
public String toString() { public String toString() {

View File

@ -20,14 +20,13 @@ package com.fsck.k9.mail.filter;
* This code was copied from the Apache Commons project. * This code was copied from the Apache Commons project.
* The unnecessary parts have been left out. * The unnecessary parts have been left out.
*/ */
public class Hex public class Hex {
{
/** /**
* Used building output as Hex * Used building output as Hex
*/ */
private static final char[] DIGITS = { private static final char[] DIGITS = {
'0', '1', '2', '3', '4', '5', '6', '7', '0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f' '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
}; };
/** /**
@ -43,15 +42,15 @@ public class Hex
int l = data.length; int l = data.length;
char[] out = new char[l << 1]; char[] out = new char[l << 1];
// two characters form the hex value. // two characters form the hex value.
for (int i = 0, j = 0; i < l; i++) { for (int i = 0, j = 0; i < l; i++) {
out[j++] = DIGITS[(0xF0 & data[i]) >>> 4 ]; out[j++] = DIGITS[(0xF0 & data[i]) >>> 4 ];
out[j++] = DIGITS[ 0x0F & data[i] ]; out[j++] = DIGITS[ 0x0F & data[i] ];
} }
return out; return out;
} }
} }

View File

@ -30,7 +30,7 @@ public class PeekableInputStream extends InputStream {
public int peek() throws IOException { public int peek() throws IOException {
if (!mPeeked) { if (!mPeeked) {
mPeekedByte = read(); mPeekedByte = mIn.read();
mPeeked = true; mPeeked = true;
} }
return mPeekedByte; return mPeekedByte;

View File

@ -14,6 +14,7 @@ import java.io.OutputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.charset.IllegalCharsetNameException;
public class MimeUtility { public class MimeUtility {
@ -1212,13 +1213,13 @@ public class MimeUtility {
* @see #MIME_TYPE_REPLACEMENT_MAP * @see #MIME_TYPE_REPLACEMENT_MAP
*/ */
public static String canonicalizeMimeType(String mimeType) { public static String canonicalizeMimeType(String mimeType) {
for (String[] mimeTypeMapEntry : MIME_TYPE_REPLACEMENT_MAP) { for (String[] mimeTypeMapEntry : MIME_TYPE_REPLACEMENT_MAP) {
if (mimeTypeMapEntry[0].equals(mimeType)) { if (mimeTypeMapEntry[0].equals(mimeType)) {
return mimeTypeMapEntry[1]; return mimeTypeMapEntry[1];
} }
} }
return mimeType; return mimeType;
} }
/** /**
* When viewing the attachment we want the MIME type to be as sensible as * When viewing the attachment we want the MIME type to be as sensible as
@ -1381,13 +1382,19 @@ public class MimeUtility {
/* /*
* See if there is conversion from the MIME charset to the Java one. * See if there is conversion from the MIME charset to the Java one.
* this function may also throw an exception if the charset name is not known
*/ */
if (!Charset.isSupported(charset)) { boolean supported;
try {
supported = Charset.isSupported(charset);
} catch (IllegalCharsetNameException e) {
supported = false;
}
if (!supported) {
Log.e(K9.LOG_TAG, "I don't know how to deal with the charset " + charset + Log.e(K9.LOG_TAG, "I don't know how to deal with the charset " + charset +
". Falling back to US-ASCII"); ". Falling back to US-ASCII");
charset = "US-ASCII"; charset = "US-ASCII";
} }
/* /*
* Convert and return as new String * Convert and return as new String
*/ */

View File

@ -15,6 +15,7 @@ public class ImapResponseParser {
private static final SimpleDateFormat mDateTimeFormat = new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss Z", Locale.US); private static final SimpleDateFormat mDateTimeFormat = new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss Z", Locale.US);
private static final SimpleDateFormat badDateTimeFormat = new SimpleDateFormat("dd MMM yyyy HH:mm:ss Z", Locale.US); private static final SimpleDateFormat badDateTimeFormat = new SimpleDateFormat("dd MMM yyyy HH:mm:ss Z", Locale.US);
private static final SimpleDateFormat badDateTimeFormat2 = new SimpleDateFormat("E, dd MMM yyyy HH:mm:ss Z", Locale.US); private static final SimpleDateFormat badDateTimeFormat2 = new SimpleDateFormat("E, dd MMM yyyy HH:mm:ss Z", Locale.US);
private static final SimpleDateFormat badDateTimeFormat3 = new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss", Locale.US);
private PeekableInputStream mIn; private PeekableInputStream mIn;
private ImapResponse mResponse; private ImapResponse mResponse;
@ -194,7 +195,7 @@ public class ImapResponseParser {
} }
private String parseAtom() throws IOException { private String parseAtom() throws IOException {
StringBuffer sb = new StringBuffer(); StringBuilder sb = new StringBuilder();
int ch; int ch;
while (true) { while (true) {
ch = mIn.peek(); ch = mIn.peek();
@ -361,7 +362,7 @@ public class ImapResponseParser {
} }
return parseDate(value); return parseDate(value);
} catch (ParseException pe) { } catch (ParseException pe) {
throw new MessagingException("Unable to parse IMAP datetime '"+value+"' ", pe); throw new MessagingException("Unable to parse IMAP datetime '" + value + "' ", pe);
} }
} }
@ -426,8 +427,14 @@ public class ImapResponseParser {
return badDateTimeFormat.parse(value); return badDateTimeFormat.parse(value);
} }
} catch (Exception e2) { } catch (Exception e2) {
synchronized (badDateTimeFormat2) { try {
return badDateTimeFormat2.parse(value); synchronized (badDateTimeFormat2) {
return badDateTimeFormat2.parse(value);
}
} catch (Exception e3) {
synchronized (badDateTimeFormat3) {
return badDateTimeFormat3.parse(value);
}
} }
} }
} }

View File

@ -18,7 +18,10 @@ import java.net.URISyntaxException;
import java.net.URLDecoder; import java.net.URLDecoder;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.CharBuffer; import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CodingErrorAction;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.security.Security; import java.security.Security;
@ -373,16 +376,31 @@ public class ImapStore extends Store {
for (ImapResponse response : responses) { for (ImapResponse response : responses) {
if (ImapResponseParser.equalsIgnoreCase(response.get(0), commandResponse)) { if (ImapResponseParser.equalsIgnoreCase(response.get(0), commandResponse)) {
boolean includeFolder = true; boolean includeFolder = true;
String folder = decodeFolderName(response.getString(3));
String decodedFolderName;
try {
decodedFolderName = decodeFolderName(response.getString(3));
} catch (CharacterCodingException e) {
Log.w(K9.LOG_TAG, "Folder name not correctly encoded with the UTF-7 variant " +
"as defined by RFC 3501: " + response.getString(3), e);
//TODO: Use the raw name returned by the server for all commands that require
// a folder name. Use the decoded name only for showing it to the user.
// We currently just skip folders with malformed names.
continue;
}
String folder = decodedFolderName;
if (mPathDelimeter == null) { if (mPathDelimeter == null) {
mPathDelimeter = response.getString(2); mPathDelimeter = response.getString(2);
mCombinedPrefix = null; mCombinedPrefix = null;
} }
if (folder.equalsIgnoreCase(K9.INBOX)) { if (folder.equalsIgnoreCase(mAccount.getInboxFolderName())) {
continue; continue;
} else if (folder.equalsIgnoreCase(K9.OUTBOX)) { } else if (folder.equals(mAccount.getOutboxFolderName())) {
/* /*
* There is a folder on the server with the same name as our local * There is a folder on the server with the same name as our local
* outbox. Until we have a good plan to deal with this situation * outbox. Until we have a good plan to deal with this situation
@ -390,12 +408,13 @@ public class ImapStore extends Store {
*/ */
continue; continue;
} else { } else {
int prefixLength = getCombinedPrefix().length();
if (getCombinedPrefix().length() > 0) { if (prefixLength > 0) {
if (folder.length() >= getCombinedPrefix().length()) { // Strip prefix from the folder name
folder = folder.substring(getCombinedPrefix().length()); if (folder.length() >= prefixLength) {
folder = folder.substring(prefixLength);
} }
if (!decodeFolderName(response.getString(3)).equalsIgnoreCase(getCombinedPrefix() + folder)) { if (!decodedFolderName.equalsIgnoreCase(getCombinedPrefix() + folder)) {
includeFolder = false; includeFolder = false;
} }
} }
@ -413,7 +432,7 @@ public class ImapStore extends Store {
} }
} }
} }
folders.add(getFolder("INBOX")); folders.add(getFolder(mAccount.getInboxFolderName()));
return folders; return folders;
} }
@ -492,14 +511,15 @@ public class ImapStore extends Store {
} }
} }
private String decodeFolderName(String name) { private String decodeFolderName(String name) throws CharacterCodingException {
/* /*
* Convert the encoded name to US-ASCII, then pass it through the modified UTF-7 * Convert the encoded name to US-ASCII, then pass it through the modified UTF-7
* decoder and return the Unicode String. * decoder and return the Unicode String.
*/ */
try { try {
byte[] encoded = name.getBytes("US-ASCII"); // Make sure the decoder throws an exception if it encounters an invalid encoding.
CharBuffer cb = mModifiedUtf7Charset.decode(ByteBuffer.wrap(encoded)); CharsetDecoder decoder = mModifiedUtf7Charset.newDecoder().onMalformedInput(CodingErrorAction.REPORT);
CharBuffer cb = decoder.decode(ByteBuffer.wrap(name.getBytes("US-ASCII")));
return cb.toString(); return cb.toString();
} catch (UnsupportedEncodingException uee) { } catch (UnsupportedEncodingException uee) {
/* /*
@ -548,7 +568,7 @@ public class ImapStore extends Store {
public String getPrefixedName() throws MessagingException { public String getPrefixedName() throws MessagingException {
String prefixedName = ""; String prefixedName = "";
if (!K9.INBOX.equalsIgnoreCase(mName)) { if (!mAccount.getInboxFolderName().equalsIgnoreCase(mName)) {
ImapConnection connection = null; ImapConnection connection = null;
synchronized (this) { synchronized (this) {
if (mConnection == null) { if (mConnection == null) {
@ -2098,7 +2118,7 @@ public class ImapStore extends Store {
System.arraycopy(buf, 1, b64NonceTrim, 0, b64NonceLen - 2); System.arraycopy(buf, 1, b64NonceTrim, 0, b64NonceLen - 2);
byte[] b64CRAM = Authentication.computeCramMd5Bytes(mSettings.getUsername(), byte[] b64CRAM = Authentication.computeCramMd5Bytes(mSettings.getUsername(),
mSettings.getPassword(), b64NonceTrim); mSettings.getPassword(), b64NonceTrim);
mOut.write(b64CRAM); mOut.write(b64CRAM);
mOut.write(new byte[] { 0x0d, 0x0a }); mOut.write(new byte[] { 0x0d, 0x0a });

View File

@ -10,6 +10,7 @@ import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
@ -366,7 +367,7 @@ public class LocalStore extends Store implements Serializable {
Folder.FolderClass pushClass = Folder.FolderClass.SECOND_CLASS; Folder.FolderClass pushClass = Folder.FolderClass.SECOND_CLASS;
boolean inTopGroup = false; boolean inTopGroup = false;
boolean integrate = false; boolean integrate = false;
if (K9.INBOX.equals(name)) { if (mAccount.getInboxFolderName().equals(name)) {
displayClass = Folder.FolderClass.FIRST_CLASS; displayClass = Folder.FolderClass.FIRST_CLASS;
syncClass = Folder.FolderClass.FIRST_CLASS; syncClass = Folder.FolderClass.FIRST_CLASS;
pushClass = Folder.FolderClass.FIRST_CLASS; pushClass = Folder.FolderClass.FIRST_CLASS;
@ -484,9 +485,6 @@ public class LocalStore extends Store implements Serializable {
cursor = db.rawQuery("SELECT COUNT(*) FROM messages", null); cursor = db.rawQuery("SELECT COUNT(*) FROM messages", null);
cursor.moveToFirst(); cursor.moveToFirst();
return cursor.getInt(0); // message count return cursor.getInt(0); // message count
} finally { } finally {
if (cursor != null) { if (cursor != null) {
cursor.close(); cursor.close();
@ -496,8 +494,6 @@ public class LocalStore extends Store implements Serializable {
}); });
} }
public void getMessageCounts(final AccountStats stats) throws MessagingException { public void getMessageCounts(final AccountStats stats) throws MessagingException {
final Account.FolderMode displayMode = mAccount.getFolderDisplayMode(); final Account.FolderMode displayMode = mAccount.getFolderDisplayMode();
@ -506,65 +502,61 @@ public class LocalStore extends Store implements Serializable {
public Integer doDbWork(final SQLiteDatabase db) { public Integer doDbWork(final SQLiteDatabase db) {
Cursor cursor = null; Cursor cursor = null;
try { try {
String baseQuery = "SELECT SUM(unread_count), SUM(flagged_count) FROM folders WHERE ( name != ? AND name != ? AND name != ? AND name != ? AND name != ? ) "; // Always count messages in the INBOX but exclude special folders and possibly
if (displayMode == Account.FolderMode.NONE) { // more (depending on the folder display mode)
cursor = db.rawQuery(baseQuery + "AND (name = ? )", new String[] { String baseQuery = "SELECT SUM(unread_count), SUM(flagged_count) " +
"FROM folders " +
"WHERE (name = ?)" + /* INBOX */
" OR (" +
"name NOT IN (?, ?, ?, ?, ?)" + /* special folders */
"%s)"; /* placeholder for additional constraints */
mAccount.getTrashFolderName() != null ? mAccount.getTrashFolderName() : "" , List<String> queryParam = new ArrayList<String>();
mAccount.getDraftsFolderName() != null ? mAccount.getDraftsFolderName() : "", queryParam.add(mAccount.getInboxFolderName());
mAccount.getSpamFolderName() != null ? mAccount.getSpamFolderName() : "",
mAccount.getOutboxFolderName() != null ? mAccount.getOutboxFolderName() : "",
mAccount.getSentFolderName() != null ? mAccount.getSentFolderName() : "",
K9.INBOX
}
); queryParam.add((mAccount.getTrashFolderName() != null) ?
} else if (displayMode == Account.FolderMode.FIRST_CLASS) { mAccount.getTrashFolderName() : "");
cursor = db.rawQuery(baseQuery + " AND ( name = ? OR display_class = ?)", new String[] { queryParam.add((mAccount.getDraftsFolderName() != null) ?
mAccount.getTrashFolderName() != null ? mAccount.getTrashFolderName() : "" , mAccount.getDraftsFolderName() : "");
mAccount.getDraftsFolderName() != null ? mAccount.getDraftsFolderName() : "", queryParam.add((mAccount.getSpamFolderName() != null) ?
mAccount.getSpamFolderName() != null ? mAccount.getSpamFolderName() : "", mAccount.getSpamFolderName() : "");
mAccount.getOutboxFolderName() != null ? mAccount.getOutboxFolderName() : "", queryParam.add((mAccount.getOutboxFolderName() != null) ?
mAccount.getSentFolderName() != null ? mAccount.getSentFolderName() : "", mAccount.getOutboxFolderName() : "");
K9.INBOX, Folder.FolderClass.FIRST_CLASS.name() queryParam.add((mAccount.getSentFolderName() != null) ?
}); mAccount.getSentFolderName() : "");
final String extraWhere;
} else if (displayMode == Account.FolderMode.FIRST_AND_SECOND_CLASS) { switch (displayMode) {
cursor = db.rawQuery(baseQuery + " AND ( name = ? OR display_class = ? OR display_class = ? )", new String[] { case FIRST_CLASS:
mAccount.getTrashFolderName() != null ? mAccount.getTrashFolderName() : "" , // Count messages in the INBOX and non-special first class folders
mAccount.getDraftsFolderName() != null ? mAccount.getDraftsFolderName() : "", extraWhere = " AND (display_class = ?)";
mAccount.getSpamFolderName() != null ? mAccount.getSpamFolderName() : "", queryParam.add(Folder.FolderClass.FIRST_CLASS.name());
mAccount.getOutboxFolderName() != null ? mAccount.getOutboxFolderName() : "", break;
mAccount.getSentFolderName() != null ? mAccount.getSentFolderName() : "", case FIRST_AND_SECOND_CLASS:
K9.INBOX, Folder.FolderClass.FIRST_CLASS.name(), Folder.FolderClass.SECOND_CLASS.name() // Count messages in the INBOX and non-special first and second class folders
}); extraWhere = " AND (display_class IN (?, ?))";
} else if (displayMode == Account.FolderMode.NOT_SECOND_CLASS) { queryParam.add(Folder.FolderClass.FIRST_CLASS.name());
cursor = db.rawQuery(baseQuery + " AND ( name = ? OR display_class != ?)", new String[] { queryParam.add(Folder.FolderClass.SECOND_CLASS.name());
break;
mAccount.getTrashFolderName() != null ? mAccount.getTrashFolderName() : "" , case NOT_SECOND_CLASS:
mAccount.getDraftsFolderName() != null ? mAccount.getDraftsFolderName() : "", // Count messages in the INBOX and non-special non-second-class folders
mAccount.getSpamFolderName() != null ? mAccount.getSpamFolderName() : "", extraWhere = " AND (display_class != ?)";
mAccount.getOutboxFolderName() != null ? mAccount.getOutboxFolderName() : "", queryParam.add(Folder.FolderClass.SECOND_CLASS.name());
mAccount.getSentFolderName() != null ? mAccount.getSentFolderName() : "", break;
K9.INBOX, Folder.FolderClass.SECOND_CLASS.name() case ALL:
}); // Count messages in the INBOX and non-special folders
} else if (displayMode == Account.FolderMode.ALL) { extraWhere = "";
cursor = db.rawQuery(baseQuery, new String[] { break;
default:
mAccount.getTrashFolderName() != null ? mAccount.getTrashFolderName() : "" ,
mAccount.getDraftsFolderName() != null ? mAccount.getDraftsFolderName() : "",
mAccount.getSpamFolderName() != null ? mAccount.getSpamFolderName() : "",
mAccount.getOutboxFolderName() != null ? mAccount.getOutboxFolderName() : "",
mAccount.getSentFolderName() != null ? mAccount.getSentFolderName() : "",
});
} else {
Log.e(K9.LOG_TAG, "asked to compute account statistics for an impossible folder mode " + displayMode); Log.e(K9.LOG_TAG, "asked to compute account statistics for an impossible folder mode " + displayMode);
stats.unreadMessageCount = 0; stats.unreadMessageCount = 0;
stats.flaggedMessageCount = 0; stats.flaggedMessageCount = 0;
return null; return null;
} }
String query = String.format(Locale.US, baseQuery, extraWhere);
cursor = db.rawQuery(query, queryParam.toArray(EMPTY_STRING_ARRAY));
cursor.moveToFirst(); cursor.moveToFirst();
stats.unreadMessageCount = cursor.getInt(0); stats.unreadMessageCount = cursor.getInt(0);
stats.flaggedMessageCount = cursor.getInt(1); stats.flaggedMessageCount = cursor.getInt(1);
@ -1047,14 +1039,14 @@ public class LocalStore extends Store implements Serializable {
if (mAccount.isSpecialFolder(name)) { if (mAccount.isSpecialFolder(name)) {
prefHolder.inTopGroup = true; prefHolder.inTopGroup = true;
prefHolder.displayClass = LocalFolder.FolderClass.FIRST_CLASS; prefHolder.displayClass = LocalFolder.FolderClass.FIRST_CLASS;
if (name.equalsIgnoreCase(K9.INBOX)) { if (name.equalsIgnoreCase(mAccount.getInboxFolderName())) {
prefHolder.integrate = true; prefHolder.integrate = true;
prefHolder.pushClass = LocalFolder.FolderClass.FIRST_CLASS; prefHolder.pushClass = LocalFolder.FolderClass.FIRST_CLASS;
} else { } else {
prefHolder.pushClass = LocalFolder.FolderClass.INHERITED; prefHolder.pushClass = LocalFolder.FolderClass.INHERITED;
} }
if (name.equalsIgnoreCase(K9.INBOX) || if (name.equalsIgnoreCase(mAccount.getInboxFolderName()) ||
name.equalsIgnoreCase(mAccount.getDraftsFolderName())) { name.equalsIgnoreCase(mAccount.getDraftsFolderName())) {
prefHolder.syncClass = LocalFolder.FolderClass.FIRST_CLASS; prefHolder.syncClass = LocalFolder.FolderClass.FIRST_CLASS;
} else { } else {
@ -1104,7 +1096,8 @@ public class LocalStore extends Store implements Serializable {
super(LocalStore.this.mAccount); super(LocalStore.this.mAccount);
this.mName = name; this.mName = name;
if (K9.INBOX.equals(getName())) { if (LocalStore.this.mAccount.getInboxFolderName().equals(getName())) {
mSyncClass = FolderClass.FIRST_CLASS; mSyncClass = FolderClass.FIRST_CLASS;
mPushClass = FolderClass.FIRST_CLASS; mPushClass = FolderClass.FIRST_CLASS;
mInTopGroup = true; mInTopGroup = true;
@ -1271,7 +1264,7 @@ public class LocalStore extends Store implements Serializable {
} }
Cursor cursor = null; Cursor cursor = null;
try { try {
cursor = db.rawQuery("SELECT COUNT(*) FROM messages WHERE folder_id = ?", cursor = db.rawQuery("SELECT COUNT(*) FROM messages WHERE deleted = 0 and folder_id = ?",
new String[] { new String[] {
Long.toString(mFolderId) Long.toString(mFolderId)
}); });
@ -1484,19 +1477,19 @@ public class LocalStore extends Store implements Serializable {
String id = getPrefId(); String id = getPrefId();
// there can be a lot of folders. For the defaults, let's not save prefs, saving space, except for INBOX // there can be a lot of folders. For the defaults, let's not save prefs, saving space, except for INBOX
if (mDisplayClass == FolderClass.NO_CLASS && !K9.INBOX.equals(getName())) { if (mDisplayClass == FolderClass.NO_CLASS && !mAccount.getInboxFolderName().equals(getName())) {
editor.remove(id + ".displayMode"); editor.remove(id + ".displayMode");
} else { } else {
editor.putString(id + ".displayMode", mDisplayClass.name()); editor.putString(id + ".displayMode", mDisplayClass.name());
} }
if (mSyncClass == FolderClass.INHERITED && !K9.INBOX.equals(getName())) { if (mSyncClass == FolderClass.INHERITED && !mAccount.getInboxFolderName().equals(getName())) {
editor.remove(id + ".syncMode"); editor.remove(id + ".syncMode");
} else { } else {
editor.putString(id + ".syncMode", mSyncClass.name()); editor.putString(id + ".syncMode", mSyncClass.name());
} }
if (mPushClass == FolderClass.SECOND_CLASS && !K9.INBOX.equals(getName())) { if (mPushClass == FolderClass.SECOND_CLASS && !mAccount.getInboxFolderName().equals(getName())) {
editor.remove(id + ".pushMode"); editor.remove(id + ".pushMode");
} else { } else {
editor.putString(id + ".pushMode", mPushClass.name()); editor.putString(id + ".pushMode", mPushClass.name());
@ -1674,20 +1667,22 @@ public class LocalStore extends Store implements Serializable {
body = new LocalAttachmentBody(Uri.parse(contentUri), mApplication); body = new LocalAttachmentBody(Uri.parse(contentUri), mApplication);
} }
String encoded_name = EncoderUtil.encodeIfNecessary(name,
EncoderUtil.Usage.WORD_ENTITY, 7);
MimeBodyPart bp = new LocalAttachmentBodyPart(body, id); MimeBodyPart bp = new LocalAttachmentBodyPart(body, id);
bp.setHeader(MimeHeader.HEADER_CONTENT_TYPE,
String.format("%s;\n name=\"%s\"",
type,
encoded_name));
bp.setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, "base64"); bp.setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, "base64");
bp.setHeader(MimeHeader.HEADER_CONTENT_DISPOSITION, if (name != null) {
String.format("%s;\n filename=\"%s\";\n size=%d", String encoded_name = EncoderUtil.encodeIfNecessary(name,
contentDisposition, EncoderUtil.Usage.WORD_ENTITY, 7);
encoded_name, // TODO: Should use encoded word defined in RFC 2231.
size)); bp.setHeader(MimeHeader.HEADER_CONTENT_TYPE,
String.format("%s;\n name=\"%s\"",
type,
encoded_name));
bp.setHeader(MimeHeader.HEADER_CONTENT_DISPOSITION,
String.format("%s;\n filename=\"%s\";\n size=%d",
contentDisposition,
encoded_name, // TODO: Should use encoded word defined in RFC 2231.
size));
}
bp.setHeader(MimeHeader.HEADER_CONTENT_ID, contentId); bp.setHeader(MimeHeader.HEADER_CONTENT_ID, contentId);
/* /*
@ -2076,6 +2071,15 @@ public class LocalStore extends Store implements Serializable {
for (Part viewable : viewables) { for (Part viewable : viewables) {
try { try {
String text = MimeUtility.getTextFromPart(viewable); String text = MimeUtility.getTextFromPart(viewable);
/*
* Small hack to make sure the string "null" doesn't end up
* in one of the StringBuffers.
*/
if (text == null) {
text = "";
}
/* /*
* Anything with MIME type text/html will be stored as such. Anything * Anything with MIME type text/html will be stored as such. Anything
* else will be stored as text/plain. * else will be stored as text/plain.
@ -2181,6 +2185,15 @@ public class LocalStore extends Store implements Serializable {
Part viewable = viewables.get(i); Part viewable = viewables.get(i);
try { try {
String text = MimeUtility.getTextFromPart(viewable); String text = MimeUtility.getTextFromPart(viewable);
/*
* Small hack to make sure the string "null" doesn't end up
* in one of the StringBuffers.
*/
if (text == null) {
text = "";
}
/* /*
* Anything with MIME type text/html will be stored as such. Anything * Anything with MIME type text/html will be stored as such. Anything
* else will be stored as text/plain. * else will be stored as text/plain.
@ -2427,16 +2440,18 @@ public class LocalStore extends Store implements Serializable {
{ Long.toString(messageId) }, null, null, null); { Long.toString(messageId) }, null, null, null);
try { try {
if (cursor.moveToNext()) { if (cursor.moveToNext()) {
String new_html; String htmlContent = cursor.getString(0);
new_html = cursor.getString(0); if (htmlContent != null) {
new_html = new_html.replaceAll(Pattern.quote("cid:" + contentId), String newHtmlContent = htmlContent.replaceAll(
contentUri.toString()); Pattern.quote("cid:" + contentId),
contentUri.toString());
ContentValues cv = new ContentValues(); ContentValues cv = new ContentValues();
cv.put("html_content", new_html); cv.put("html_content", newHtmlContent);
db.update("messages", cv, "id = ?", new String[] db.update("messages", cv, "id = ?", new String[]
{ Long.toString(messageId) }); { Long.toString(messageId) });
}
} }
} finally { } finally {
if (cursor != null) { if (cursor != null) {
@ -2552,6 +2567,7 @@ public class LocalStore extends Store implements Serializable {
setPushState(null); setPushState(null);
setLastPush(0); setLastPush(0);
setLastChecked(0); setLastChecked(0);
setVisibleLimit(mAccount.getDisplayCount());
} }
private void resetUnreadAndFlaggedCounts() { private void resetUnreadAndFlaggedCounts() {
@ -2707,19 +2723,26 @@ public class LocalStore extends Store implements Serializable {
text = text.substring(0, 8192); text = text.substring(0, 8192);
} }
// try to remove lines of dashes in the preview
text = text.replaceAll("(?m)^----.*?$", ""); text = text.replaceAll("(?m)^----.*?$", "");
// remove quoted text from the preview
text = text.replaceAll("(?m)^[#>].*$", ""); text = text.replaceAll("(?m)^[#>].*$", "");
// Remove a common quote header from the preview
text = text.replaceAll("(?m)^On .*wrote.?$", ""); text = text.replaceAll("(?m)^On .*wrote.?$", "");
// Remove a more generic quote header from the preview
text = text.replaceAll("(?m)^.*\\w+:$", ""); text = text.replaceAll("(?m)^.*\\w+:$", "");
// URLs in the preview should just be shown as "..." - They're not
// clickable and they usually overwhelm the preview
text = text.replaceAll("https?://\\S+", "..."); text = text.replaceAll("https?://\\S+", "...");
// Don't show newlines in the preview
text = text.replaceAll("(\\r|\\n)+", " "); text = text.replaceAll("(\\r|\\n)+", " ");
// Collapse whitespace in the preview
text = text.replaceAll("\\s+", " "); text = text.replaceAll("\\s+", " ");
if (text.length() <= 512) { if (text.length() <= 512) {
return text; return text;
} else { } else {
text = text.substring(0, 512); return text.substring(0, 512);
return text;
} }
} }

View File

@ -43,6 +43,13 @@ public class Pop3Store extends Store {
private HashMap<String, Folder> mFolders = new HashMap<String, Folder>(); private HashMap<String, Folder> mFolders = new HashMap<String, Folder>();
private Pop3Capabilities mCapabilities; private Pop3Capabilities mCapabilities;
/**
* This value is {@code true} if the server supports the CAPA command but doesn't advertise
* support for the TOP command OR if the server doesn't support the CAPA command and we
* already unsuccessfully tried to use the TOP command.
*/
private boolean mTopNotSupported;
/** /**
* pop3://user:password@server:port CONNECTION_SECURITY_NONE * pop3://user:password@server:port CONNECTION_SECURITY_NONE
* pop3+tls://user:password@server:port CONNECTION_SECURITY_TLS_OPTIONAL * pop3+tls://user:password@server:port CONNECTION_SECURITY_TLS_OPTIONAL
@ -91,11 +98,10 @@ public class Pop3Store extends Store {
try { try {
int userIndex = 0, passwordIndex = 1; int userIndex = 0, passwordIndex = 1;
String[] userInfoParts = uri.getUserInfo().split(":"); String[] userInfoParts = uri.getUserInfo().split(":");
if (userInfoParts.length > 2) if (userInfoParts.length > 2) {
{ userIndex++;
userIndex++; passwordIndex++;
passwordIndex++; useCramMd5 = true;
useCramMd5 = true;
} }
mUsername = URLDecoder.decode(userInfoParts[userIndex], "UTF-8"); mUsername = URLDecoder.decode(userInfoParts[userIndex], "UTF-8");
if (userInfoParts.length > passwordIndex) { if (userInfoParts.length > passwordIndex) {
@ -121,13 +127,13 @@ public class Pop3Store extends Store {
@Override @Override
public List <? extends Folder > getPersonalNamespaces(boolean forceListAll) throws MessagingException { public List <? extends Folder > getPersonalNamespaces(boolean forceListAll) throws MessagingException {
List<Folder> folders = new LinkedList<Folder>(); List<Folder> folders = new LinkedList<Folder>();
folders.add(getFolder("INBOX")); folders.add(getFolder(mAccount.getInboxFolderName()));
return folders; return folders;
} }
@Override @Override
public void checkSettings() throws MessagingException { public void checkSettings() throws MessagingException {
Pop3Folder folder = new Pop3Folder("INBOX"); Pop3Folder folder = new Pop3Folder(mAccount.getInboxFolderName());
folder.open(OpenMode.READ_WRITE); folder.open(OpenMode.READ_WRITE);
if (!mCapabilities.uidl) { if (!mCapabilities.uidl) {
/* /*
@ -158,8 +164,9 @@ public class Pop3Store extends Store {
public Pop3Folder(String name) { public Pop3Folder(String name) {
super(Pop3Store.this.mAccount); super(Pop3Store.this.mAccount);
this.mName = name; this.mName = name;
if (mName.equalsIgnoreCase("INBOX")) {
mName = "INBOX"; if (mName.equalsIgnoreCase(mAccount.getInboxFolderName())) {
mName = mAccount.getInboxFolderName();
} }
} }
@ -169,7 +176,7 @@ public class Pop3Store extends Store {
return; return;
} }
if (!mName.equalsIgnoreCase("INBOX")) { if (!mName.equalsIgnoreCase(mAccount.getInboxFolderName())) {
throw new MessagingException("Folder does not exist"); throw new MessagingException("Folder does not exist");
} }
@ -224,8 +231,7 @@ public class Pop3Store extends Store {
} }
} }
if (useCramMd5) if (useCramMd5) {
{
try { try {
String b64Nonce = executeSimpleCommand("AUTH CRAM-MD5").replace("+ ", ""); String b64Nonce = executeSimpleCommand("AUTH CRAM-MD5").replace("+ ", "");
@ -235,9 +241,7 @@ public class Pop3Store extends Store {
} catch (MessagingException me) { } catch (MessagingException me) {
throw new AuthenticationFailedException(null, me); throw new AuthenticationFailedException(null, me);
} }
} } else {
else
{
try { try {
executeSimpleCommand("USER " + mUsername); executeSimpleCommand("USER " + mUsername);
executeSimpleCommand("PASS " + mPassword, true); executeSimpleCommand("PASS " + mPassword, true);
@ -279,7 +283,9 @@ public class Pop3Store extends Store {
@Override @Override
public void close() { public void close() {
try { try {
executeSimpleCommand("QUIT"); if (isOpen()) {
executeSimpleCommand("QUIT");
}
} catch (Exception e) { } catch (Exception e) {
/* /*
* QUIT may fail if the connection is already closed. We don't care. It's just * QUIT may fail if the connection is already closed. We don't care. It's just
@ -329,7 +335,7 @@ public class Pop3Store extends Store {
@Override @Override
public boolean exists() throws MessagingException { public boolean exists() throws MessagingException {
return mName.equalsIgnoreCase("INBOX"); return mName.equalsIgnoreCase(mAccount.getInboxFolderName());
} }
@Override @Override
@ -659,41 +665,67 @@ public class Pop3Store extends Store {
} }
/** /**
* Fetches the body of the given message, limiting the stored data * Fetches the body of the given message, limiting the downloaded data to the specified
* to the specified number of lines. If lines is -1 the entire message * number of lines if possible.
* is fetched. This is implemented with RETR for lines = -1 or TOP *
* for any other value. If the server does not support TOP it is * If lines is -1 the entire message is fetched. This is implemented with RETR for
* emulated with RETR and extra lines are thrown away. * lines = -1 or TOP for any other value. If the server does not support TOP, RETR is used
* @param message * instead.
* @param lines
*/ */
private void fetchBody(Pop3Message message, int lines) private void fetchBody(Pop3Message message, int lines)
throws IOException, MessagingException { throws IOException, MessagingException {
String response = null; String response = null;
if (lines == -1 || !mCapabilities.top) {
// Try hard to use the TOP command if we're not asked to download the whole message.
if (lines != -1 && (!mTopNotSupported || mCapabilities.top)) {
try {
if (K9.DEBUG && K9.DEBUG_PROTOCOL_POP3 && !mCapabilities.top) {
Log.d(K9.LOG_TAG, "This server doesn't support the CAPA command. " +
"Checking to see if the TOP command is supported nevertheless.");
}
response = executeSimpleCommand(String.format("TOP %d %d",
mUidToMsgNumMap.get(message.getUid()), lines));
// TOP command is supported. Remember this for the next time.
mCapabilities.top = true;
} catch (Pop3ErrorResponse e) {
if (mCapabilities.top) {
// The TOP command should be supported but something went wrong.
throw e;
} else {
if (K9.DEBUG && K9.DEBUG_PROTOCOL_POP3) {
Log.d(K9.LOG_TAG, "The server really doesn't support the TOP " +
"command. Using RETR instead.");
}
// Don't try to use the TOP command again.
mTopNotSupported = true;
}
}
}
if (response == null) {
response = executeSimpleCommand(String.format("RETR %d", response = executeSimpleCommand(String.format("RETR %d",
mUidToMsgNumMap.get(message.getUid()))); mUidToMsgNumMap.get(message.getUid())));
} else {
response = executeSimpleCommand(String.format("TOP %d %d",
mUidToMsgNumMap.get(message.getUid()),
lines));
} }
if (response != null) {
try { try {
message.parse(new Pop3ResponseInputStream(mIn)); message.parse(new Pop3ResponseInputStream(mIn));
if (lines == -1 || !mCapabilities.top) {
message.setFlag(Flag.X_DOWNLOADED_FULL, true); // TODO: if we've received fewer lines than requested we also have the complete message.
} if (lines == -1 || !mCapabilities.top) {
} catch (MessagingException me) { message.setFlag(Flag.X_DOWNLOADED_FULL, true);
/* }
* If we're only downloading headers it's possible } catch (MessagingException me) {
* we'll get a broken MIME message which we're not /*
* real worried about. If we've downloaded the body * If we're only downloading headers it's possible
* and can't parse it we need to let the user know. * we'll get a broken MIME message which we're not
*/ * real worried about. If we've downloaded the body
if (lines == -1) { * and can't parse it we need to let the user know.
throw me; */
} if (lines == -1) {
throw me;
} }
} }
} }
@ -722,10 +754,8 @@ public class Pop3Store extends Store {
} }
@Override @Override
public void setFlags(Flag[] flags, boolean value) public void setFlags(Flag[] flags, boolean value) throws MessagingException {
throws MessagingException { throw new UnsupportedOperationException("POP3: No setFlags(Flag[],boolean)");
Message[] messages = getMessages(null);
setFlags(messages, flags, value);
} }
@Override @Override
@ -809,6 +839,14 @@ public class Pop3Store extends Store {
capabilities.top = true; capabilities.top = true;
} }
} }
if (!capabilities.top) {
/*
* If the CAPA command is supported but it doesn't advertise support for the
* TOP command, we won't check for it manually.
*/
mTopNotSupported = true;
}
} catch (MessagingException me) { } catch (MessagingException me) {
/* /*
* The server may not support the CAPA command, so we just eat this Exception * The server may not support the CAPA command, so we just eat this Exception
@ -841,7 +879,7 @@ public class Pop3Store extends Store {
String response = readLine(); String response = readLine();
if (response.length() > 1 && response.charAt(0) == '-') { if (response.length() > 1 && response.charAt(0) == '-') {
throw new MessagingException(response); throw new Pop3ErrorResponse(response);
} }
return response; return response;
@ -853,6 +891,11 @@ public class Pop3Store extends Store {
} }
} }
@Override
public boolean isFlagSupported(Flag flag) {
return (flag == Flag.DELETED);
}
@Override @Override
public boolean supportsFetchingFlags() { public boolean supportsFetchingFlags() {
return false; return false;
@ -956,4 +999,13 @@ public class Pop3Store extends Store {
return d; return d;
} }
} }
/**
* Exception that is thrown if the server returns an error response.
*/
static class Pop3ErrorResponse extends MessagingException {
public Pop3ErrorResponse(String message) {
super(message, true);
}
}
} }

View File

@ -4,7 +4,6 @@ import android.util.Log;
import com.fsck.k9.Account; import com.fsck.k9.Account;
import com.fsck.k9.K9; import com.fsck.k9.K9;
import com.fsck.k9.R;
import com.fsck.k9.controller.MessageRetrievalListener; import com.fsck.k9.controller.MessageRetrievalListener;
import com.fsck.k9.helper.Utility; import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.*; import com.fsck.k9.mail.*;
@ -51,6 +50,7 @@ import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Stack; import java.util.Stack;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
@ -79,8 +79,15 @@ public class WebDavStore extends Store {
private static final Message[] EMPTY_MESSAGE_ARRAY = new Message[0]; private static final Message[] EMPTY_MESSAGE_ARRAY = new Message[0];
// These are the ids used from Exchange server to identify the special folders
// http://social.technet.microsoft.com/Forums/en/exchangesvrdevelopment/thread/1cd2e98c-8a12-44bd-a3e3-9c5ee9e4e14d
private static final String DAV_MAIL_INBOX_FOLDER = "inbox";
private static final String DAV_MAIL_DRAFTS_FOLDER = "drafts";
private static final String DAV_MAIL_SPAM_FOLDER = "junkemail";
private static final String DAV_MAIL_SEND_FOLDER = "##DavMailSubmissionURI##"; private static final String DAV_MAIL_SEND_FOLDER = "##DavMailSubmissionURI##";
private static final String DAV_MAIL_TMP_FOLDER = "drafts"; private static final String DAV_MAIL_TRASH_FOLDER = "deleteditems";
private static final String DAV_MAIL_OUTBOX_FOLDER = "outbox";
private static final String DAV_MAIL_SENT_FOLDER = "sentitems";
private short mConnectionSecurity; private short mConnectionSecurity;
private String mUsername; /* Stores the username for authentications */ private String mUsername; /* Stores the username for authentications */
@ -101,6 +108,7 @@ public class WebDavStore extends Store {
private short mAuthentication = AUTH_TYPE_NONE; private short mAuthentication = AUTH_TYPE_NONE;
private String mCachedLoginUrl; private String mCachedLoginUrl;
private Folder mSendFolder = null;
private HashMap<String, WebDavFolder> mFolderList = new HashMap<String, WebDavFolder>(); private HashMap<String, WebDavFolder> mFolderList = new HashMap<String, WebDavFolder>();
/** /**
@ -231,63 +239,136 @@ public class WebDavStore extends Store {
@Override @Override
public List <? extends Folder > getPersonalNamespaces(boolean forceListAll) throws MessagingException { public List <? extends Folder > getPersonalNamespaces(boolean forceListAll) throws MessagingException {
LinkedList<Folder> folderList = new LinkedList<Folder>(); LinkedList<Folder> folderList = new LinkedList<Folder>();
HashMap<String, String> headers = new HashMap<String, String>();
DataSet dataset = new DataSet();
String messageBody;
String[] folderUrls;
int urlLength;
String translatedInbox = K9.app.getString(R.string.special_mailbox_name_inbox);
/** /**
* We have to check authentication here so we have the proper URL stored * We have to check authentication here so we have the proper URL stored
*/ */
getHttpClient(); getHttpClient();
messageBody = getFolderListXml();
/**
* Firstly we get the "special" folders list (inbox, outbox, etc)
* and setup the account accordingly
*/
HashMap<String, String> headers = new HashMap<String, String>();
DataSet dataset = new DataSet();
headers.put("Depth", "0");
headers.put("Brief", "t"); headers.put("Brief", "t");
dataset = processRequest(this.mUrl, "SEARCH", messageBody, headers); dataset = processRequest(this.mUrl, "PROPFIND", getSpecialFoldersList(), headers);
folderUrls = dataset.getHrefs(); HashMap<String, String> specialFoldersMap = dataset.getSpecialFolderToUrl();
urlLength = folderUrls.length; String folderName = getFolderName(specialFoldersMap.get(DAV_MAIL_INBOX_FOLDER));
if (folderName != null) {
mAccount.setAutoExpandFolderName(folderName);
mAccount.setInboxFolderName(folderName);
}
for (int i = 0; i < urlLength; i++) { folderName = getFolderName(specialFoldersMap.get(DAV_MAIL_DRAFTS_FOLDER));
String[] urlParts = folderUrls[i].split("/"); if (folderName != null)
String folderName = urlParts[urlParts.length - 1]; mAccount.setDraftsFolderName(folderName);
String fullPathName = "";
WebDavFolder wdFolder;
// Check each Exchange folder name to see if it is the user's inbox. folderName = getFolderName(specialFoldersMap.get(DAV_MAIL_TRASH_FOLDER));
// We will check for the default English inbox ("Inbox"), and the user's if (folderName != null)
// translation for "Inbox", in case the user is using a non-English mAccount.setTrashFolderName(folderName);
// version of Exchange.
if (folderName.equalsIgnoreCase("Inbox") ||
folderName.equalsIgnoreCase(translatedInbox)) {
folderName = K9.INBOX;
} else {
for (int j = 5, count = urlParts.length; j < count; j++) {
if (j != 5) {
fullPathName = fullPathName + "/" + urlParts[j];
} else {
fullPathName = urlParts[j];
}
}
try {
folderName = java.net.URLDecoder.decode(fullPathName, "UTF-8");
} catch (UnsupportedEncodingException uee) {
/** If we don't support UTF-8 there's a problem, don't decode it then */
folderName = fullPathName;
}
}
wdFolder = new WebDavFolder(this, folderName); folderName = getFolderName(specialFoldersMap.get(DAV_MAIL_SPAM_FOLDER));
wdFolder.setUrl(folderUrls[i]); if (folderName != null)
folderList.add(wdFolder); mAccount.setSpamFolderName(folderName);
this.mFolderList.put(folderName, wdFolder);
// K-9 Mail's outbox is a special local folder and different from Exchange/WebDAV's outbox.
/*
folderName = getFolderName(specialFoldersMap.get(DAV_MAIL_OUTBOX_FOLDER));
if (folderName != null)
mAccount.setOutboxFolderName(folderName);
*/
folderName = getFolderName(specialFoldersMap.get(DAV_MAIL_SENT_FOLDER));
if (folderName != null)
mAccount.setSentFolderName(folderName);
/**
* Next we get all the folders (including "special" ones)
*/
headers = new HashMap<String, String>();
dataset = new DataSet();
headers.put("Brief", "t");
dataset = processRequest(this.mUrl, "SEARCH", getFolderListXml(), headers);
String[] folderUrls = dataset.getHrefs();
for (int i = 0; i < folderUrls.length; i++) {
String tempUrl = folderUrls[i];
WebDavFolder folder = createFolder(tempUrl);
if (folder != null)
folderList.add(folder);
} }
return folderList; return folderList;
} }
/**
* Creates a folder using the URL passed as parameter (only if it has not been
* already created) and adds this to our store folder map.
*
* @param folderUrl
* @return
*/
private WebDavFolder createFolder(String folderUrl) {
if (folderUrl == null)
return null;
WebDavFolder wdFolder = null;
String folderName = getFolderName(folderUrl);
if (folderName != null) {
if (!this.mFolderList.containsKey(folderName)) {
wdFolder = new WebDavFolder(this, folderName);
wdFolder.setUrl(folderUrl);
mFolderList.put(folderName, wdFolder);
}
}
// else: Unknown URL format => NO Folder created
return wdFolder;
}
private String getFolderName(String folderUrl) {
if (folderUrl == null)
return null;
// Here we extract the folder name starting from the complete url.
// folderUrl is in the form http://mail.domain.com/exchange/username/foldername
// so we need "foldername" which is the string after the fifth slash
int folderSlash = -1;
for (int j = 0; j < 5; j++) {
folderSlash = folderUrl.indexOf('/', folderSlash + 1);
if (folderSlash < 0)
break;
}
if (folderSlash > 0) {
String folderName;
String fullPathName;
// Removes the final slash if present
if (folderUrl.charAt(folderUrl.length() - 1) == '/')
fullPathName = folderUrl.substring(folderSlash + 1, folderUrl.length() - 1);
else
fullPathName = folderUrl.substring(folderSlash + 1);
// Decodes the url-encoded folder name (i.e. "My%20folder" => "My Folder"
try {
folderName = java.net.URLDecoder.decode(fullPathName, "UTF-8");
} catch (UnsupportedEncodingException uee) {
/**
* If we don't support UTF-8 there's a problem, don't decode
* it then
*/
folderName = fullPathName;
}
return folderName;
}
return null;
}
@Override @Override
public Folder getFolder(String name) { public Folder getFolder(String name) {
WebDavFolder folder; WebDavFolder folder;
@ -300,7 +381,10 @@ public class WebDavStore extends Store {
} }
public Folder getSendSpoolFolder() throws MessagingException { public Folder getSendSpoolFolder() throws MessagingException {
return getFolder(DAV_MAIL_SEND_FOLDER); if (mSendFolder == null)
mSendFolder = getFolder(DAV_MAIL_SEND_FOLDER);
return mSendFolder;
} }
@Override @Override
@ -313,6 +397,26 @@ public class WebDavStore extends Store {
return true; return true;
} }
private String getSpecialFoldersList() {
StringBuffer buffer = new StringBuffer(200);
buffer.append("<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"no\"?>");
buffer.append("<propfind xmlns=\"DAV:\">");
buffer.append("<prop>");
buffer.append("<").append(DAV_MAIL_INBOX_FOLDER).append(" xmlns=\"urn:schemas:httpmail:\"/>");
buffer.append("<").append(DAV_MAIL_DRAFTS_FOLDER).append(" xmlns=\"urn:schemas:httpmail:\"/>");
buffer.append("<").append(DAV_MAIL_OUTBOX_FOLDER).append(" xmlns=\"urn:schemas:httpmail:\"/>");
buffer.append("<").append(DAV_MAIL_SENT_FOLDER).append(" xmlns=\"urn:schemas:httpmail:\"/>");
buffer.append("<").append(DAV_MAIL_TRASH_FOLDER).append(" xmlns=\"urn:schemas:httpmail:\"/>");
// This should always be ##DavMailSubmissionURI## for which we already have a constant
// buffer.append("<sendmsg xmlns=\"urn:schemas:httpmail:\"/>");
buffer.append("<").append(DAV_MAIL_SPAM_FOLDER).append(" xmlns=\"urn:schemas:httpmail:\"/>");
buffer.append("</prop>");
buffer.append("</propfind>");
return buffer.toString();
}
/*************************************************************** /***************************************************************
* WebDAV XML Request body retrieval functions * WebDAV XML Request body retrieval functions
*/ */
@ -980,7 +1084,7 @@ public class WebDavStore extends Store {
@Override @Override
public void sendMessages(Message[] messages) throws MessagingException { public void sendMessages(Message[] messages) throws MessagingException {
WebDavFolder tmpFolder = (WebDavStore.WebDavFolder) getFolder(DAV_MAIL_TMP_FOLDER); WebDavFolder tmpFolder = (WebDavStore.WebDavFolder) getFolder(mAccount.getDraftsFolderName());
try { try {
tmpFolder.open(OpenMode.READ_WRITE); tmpFolder.open(OpenMode.READ_WRITE);
Message[] retMessages = tmpFolder.appendWebDavMessages(messages); Message[] retMessages = tmpFolder.appendWebDavMessages(messages);
@ -1017,37 +1121,30 @@ public class WebDavStore extends Store {
store = nStore; store = nStore;
this.mName = name; this.mName = name;
if (DAV_MAIL_SEND_FOLDER.equals(name)) { String encodedName = "";
this.mFolderUrl = getUrl() + "/" + name + "/"; try {
} else { String[] urlParts = name.split("/");
String encodedName = ""; String url = "";
try { for (int i = 0, count = urlParts.length; i < count; i++) {
String[] urlParts = name.split("/"); if (i != 0) {
String url = ""; url = url + "/" + java.net.URLEncoder.encode(urlParts[i], "UTF-8");
for (int i = 0, count = urlParts.length; i < count; i++) { } else {
if (i != 0) { url = java.net.URLEncoder.encode(urlParts[i], "UTF-8");
url = url + "/" + java.net.URLEncoder.encode(urlParts[i], "UTF-8");
} else {
url = java.net.URLEncoder.encode(urlParts[i], "UTF-8");
}
} }
encodedName = url;
} catch (UnsupportedEncodingException uee) {
Log.e(K9.LOG_TAG, "UnsupportedEncodingException URLEncoding folder name, skipping encoded");
encodedName = name;
} }
encodedName = url;
encodedName = encodedName.replaceAll("\\+", "%20"); } catch (UnsupportedEncodingException uee) {
Log.e(K9.LOG_TAG, "UnsupportedEncodingException URLEncoding folder name, skipping encoded");
if (encodedName.equals(K9.INBOX)) { encodedName = name;
encodedName = "Inbox";
}
this.mFolderUrl = WebDavStore.this.mUrl;
if (!WebDavStore.this.mUrl.endsWith("/")) {
this.mFolderUrl += "/";
}
this.mFolderUrl += encodedName;
} }
encodedName = encodedName.replaceAll("\\+", "%20");
this.mFolderUrl = WebDavStore.this.mUrl;
if (!WebDavStore.this.mUrl.endsWith("/")) {
this.mFolderUrl += "/";
}
this.mFolderUrl += encodedName;
} }
public void setUrl(String url) { public void setUrl(String url) {
@ -1977,6 +2074,17 @@ public class WebDavStore extends Store {
mTempData = new HashMap<String, String>(); mTempData = new HashMap<String, String>();
} }
/**
* Returns a hashmap of special folder name => special folder url
*/
public HashMap<String, String> getSpecialFolderToUrl() {
// We return the first (and only) map
for (HashMap<String, String> folderMap : mData.values()) {
return folderMap;
}
return new HashMap<String, String>();
}
/** /**
* Returns a hashmap of Message UID => Message Url * Returns a hashmap of Message UID => Message Url
*/ */
@ -2088,8 +2196,8 @@ public class WebDavStore extends Store {
String date = data.get(header); String date = data.get(header);
date = date.substring(0, date.length() - 1); date = date.substring(0, date.length() - 1);
DateFormat dfInput = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"); DateFormat dfInput = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.US);
DateFormat dfOutput = new SimpleDateFormat("EEE, d MMM yy HH:mm:ss Z"); DateFormat dfOutput = new SimpleDateFormat("EEE, d MMM yy HH:mm:ss Z", Locale.US);
String tempDate = ""; String tempDate = "";
try { try {

View File

@ -39,6 +39,14 @@ public class SmtpTransport extends Transport {
public static final int CONNECTION_SECURITY_SSL_OPTIONAL = 4; public static final int CONNECTION_SECURITY_SSL_OPTIONAL = 4;
public static final String AUTH_PLAIN = "PLAIN";
public static final String AUTH_CRAM_MD5 = "CRAM_MD5";
public static final String AUTH_LOGIN = "LOGIN";
public static final String AUTH_AUTOMATIC = "AUTOMATIC";
String mHost; String mHost;
int mPort; int mPort;
@ -163,7 +171,7 @@ public class SmtpTransport extends Transport {
executeSimpleCommand(null); executeSimpleCommand(null);
InetAddress localAddress = mSocket.getLocalAddress(); InetAddress localAddress = mSocket.getLocalAddress();
String localHost = localAddress.getHostName(); String localHost = localAddress.getCanonicalHostName();
String ipAddr = localAddress.getHostAddress(); String ipAddr = localAddress.getHostAddress();
if (localHost.equals("") || localHost.equals(ipAddr) || localHost.contains("_")) { if (localHost.equals("") || localHost.equals(ipAddr) || localHost.contains("_")) {
@ -221,9 +229,13 @@ public class SmtpTransport extends Transport {
} }
} }
/* boolean useAuthLogin = AUTH_LOGIN.equals(mAuthType);
* result contains the results of the EHLO in concatenated form boolean useAuthPlain = AUTH_PLAIN.equals(mAuthType);
*/ boolean useAuthCramMD5 = AUTH_CRAM_MD5.equals(mAuthType);
// Automatically choose best authentication method if none was explicitly selected
boolean useAutomaticAuth = !(useAuthLogin || useAuthPlain || useAuthCramMD5);
boolean authLoginSupported = false; boolean authLoginSupported = false;
boolean authPlainSupported = false; boolean authPlainSupported = false;
boolean authCramMD5Supported = false; boolean authCramMD5Supported = false;
@ -234,7 +246,7 @@ public class SmtpTransport extends Transport {
if (result.matches(".*AUTH.*PLAIN.*$")) { if (result.matches(".*AUTH.*PLAIN.*$")) {
authPlainSupported = true; authPlainSupported = true;
} }
if (result.matches(".*AUTH.*CRAM-MD5.*$") && mAuthType != null && mAuthType.equals("CRAM_MD5")) { if (result.matches(".*AUTH.*CRAM-MD5.*$")) {
authCramMD5Supported = true; authCramMD5Supported = true;
} }
if (result.matches(".*SIZE \\d*$")) { if (result.matches(".*SIZE \\d*$")) {
@ -248,13 +260,40 @@ public class SmtpTransport extends Transport {
} }
} }
if (mUsername != null && mUsername.length() > 0 && mPassword != null if (mUsername != null && mUsername.length() > 0 &&
&& mPassword.length() > 0) { mPassword != null && mPassword.length() > 0) {
if (authCramMD5Supported) { if (useAuthCramMD5 || (useAutomaticAuth && authCramMD5Supported)) {
if (!authCramMD5Supported && K9.DEBUG && K9.DEBUG_PROTOCOL_SMTP) {
Log.d(K9.LOG_TAG, "Using CRAM_MD5 as authentication method although the " +
"server didn't advertise support for it in EHLO response.");
}
saslAuthCramMD5(mUsername, mPassword); saslAuthCramMD5(mUsername, mPassword);
} else if (authPlainSupported) { } else if (useAuthPlain || (useAutomaticAuth && authPlainSupported)) {
saslAuthPlain(mUsername, mPassword); if (!authPlainSupported && K9.DEBUG && K9.DEBUG_PROTOCOL_SMTP) {
} else if (authLoginSupported) { Log.d(K9.LOG_TAG, "Using PLAIN as authentication method although the " +
"server didn't advertise support for it in EHLO response.");
}
try {
saslAuthPlain(mUsername, mPassword);
} catch (MessagingException ex) {
// PLAIN is a special case. Historically, PLAIN has represented both PLAIN and LOGIN; only the
// protocol being advertised by the server would be used, with PLAIN taking precedence. Instead
// of using only the requested protocol, we'll try PLAIN and then try LOGIN.
if (useAuthPlain && authLoginSupported) {
if (K9.DEBUG && K9.DEBUG_PROTOCOL_SMTP) {
Log.d(K9.LOG_TAG, "Using legacy PLAIN authentication behavior and trying LOGIN.");
}
saslAuthLogin(mUsername, mPassword);
} else {
// If it was auto detected and failed, continue throwing the exception back up.
throw ex;
}
}
} else if (useAuthLogin || (useAutomaticAuth && authLoginSupported)) {
if (!authPlainSupported && K9.DEBUG && K9.DEBUG_PROTOCOL_SMTP) {
Log.d(K9.LOG_TAG, "Using LOGIN as authentication method although the " +
"server didn't advertise support for it in EHLO response.");
}
saslAuthLogin(mUsername, mPassword); saslAuthLogin(mUsername, mPassword);
} else { } else {
throw new MessagingException("No valid authentication mechanism found."); throw new MessagingException("No valid authentication mechanism found.");
@ -313,11 +352,20 @@ public class SmtpTransport extends Transport {
// If the message has attachments and our server has told us about a limit on // If the message has attachments and our server has told us about a limit on
// the size of messages, count the message's size before sending it // the size of messages, count the message's size before sending it
if (mLargestAcceptableMessage > 0 && ((LocalMessage)message).hasAttachments()) { if (mLargestAcceptableMessage > 0 && ((LocalMessage)message).hasAttachments()) {
if (message.calculateSize() > mLargestAcceptableMessage) { if (K9.DEBUG_PROTOCOL_SMTP) {
Log.d(K9.LOG_TAG, "calculating message size");
}
close(); // (prevent timeouts while calculating the size)
final long calculatedSize = message.calculateSize();
if (calculatedSize > mLargestAcceptableMessage) {
MessagingException me = new MessagingException("Message too large for server"); MessagingException me = new MessagingException("Message too large for server");
me.setPermanentFailure(possibleSend); me.setPermanentFailure(possibleSend);
throw me; throw me;
} }
open(); // (prevent timeouts while calculating the size)
if (K9.DEBUG_PROTOCOL_SMTP) {
Log.d(K9.LOG_TAG, "calculating message size DONE size=" + calculatedSize + " max allowed=" + mLargestAcceptableMessage);
}
} }
Address[] from = message.getFrom(); Address[] from = message.getFrom();
@ -344,6 +392,14 @@ public class SmtpTransport extends Transport {
executeSimpleCommand("\r\n."); executeSimpleCommand("\r\n.");
} catch (Exception e) { } catch (Exception e) {
MessagingException me = new MessagingException("Unable to send message", e); MessagingException me = new MessagingException("Unable to send message", e);
// "5xx text" -responses are permanent failures
String msg = e.getMessage();
if (msg != null && msg.startsWith("5")) {
Log.w(K9.LOG_TAG, "handling 5xx SMTP error code as a permanent failure");
possibleSend = false;
}
me.setPermanentFailure(possibleSend); me.setPermanentFailure(possibleSend);
throw me; throw me;
} finally { } finally {
@ -520,9 +576,9 @@ public class SmtpTransport extends Transport {
private void saslAuthCramMD5(String username, String password) throws MessagingException, private void saslAuthCramMD5(String username, String password) throws MessagingException,
AuthenticationFailedException, IOException { AuthenticationFailedException, IOException {
List<String> respList = executeSimpleCommand("AUTH CRAM-MD5"); List<String> respList = executeSimpleCommand("AUTH CRAM-MD5");
if (respList.size() != 1) { if (respList.size() != 1) {
throw new AuthenticationFailedException("Unable to negotiate CRAM-MD5"); throw new AuthenticationFailedException("Unable to negotiate CRAM-MD5");
} }
String b64Nonce = respList.get(0); String b64Nonce = respList.get(0);

View File

@ -6,6 +6,7 @@ package com.fsck.k9.preferences;
import android.content.Context; import android.content.Context;
import android.preference.DialogPreference; import android.preference.DialogPreference;
import android.text.format.DateFormat;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.View; import android.view.View;
import android.widget.TimePicker; import android.widget.TimePicker;
@ -25,12 +26,23 @@ public class TimePickerPreference extends DialogPreference implements
* The default value for this preference * The default value for this preference
*/ */
private String defaultValue; private String defaultValue;
/**
* Store the original value, in case the user
* chooses to abort the {@link DialogPreference}
* after making a change.
*/
private int originalHour = 0;
/**
* Store the original value, in case the user
* chooses to abort the {@link DialogPreference}
* after making a change.
*/
private int originalMinute = 0;
/** /**
* @param context * @param context
* @param attrs * @param attrs
*/ */
public TimePickerPreference(Context context, AttributeSet attrs) { public TimePickerPreference(final Context context, final AttributeSet attrs) {
super(context, attrs); super(context, attrs);
initialize(); initialize();
} }
@ -40,8 +52,8 @@ public class TimePickerPreference extends DialogPreference implements
* @param attrs * @param attrs
* @param defStyle * @param defStyle
*/ */
public TimePickerPreference(Context context, AttributeSet attrs, public TimePickerPreference(final Context context, final AttributeSet attrs,
int defStyle) { final int defStyle) {
super(context, attrs, defStyle); super(context, attrs, defStyle);
initialize(); initialize();
} }
@ -62,39 +74,49 @@ public class TimePickerPreference extends DialogPreference implements
protected View onCreateDialogView() { protected View onCreateDialogView() {
TimePicker tp = new TimePicker(getContext()); TimePicker tp = new TimePicker(getContext());
tp.setIs24HourView(DateFormat.is24HourFormat(getContext()));
tp.setOnTimeChangedListener(this); tp.setOnTimeChangedListener(this);
originalHour = getHour();
int h = getHour(); originalMinute = getMinute();
int m = getMinute(); if (originalHour >= 0 && originalMinute >= 0) {
if (h >= 0 && m >= 0) { tp.setCurrentHour(originalHour);
tp.setCurrentHour(h); tp.setCurrentMinute(originalMinute);
tp.setCurrentMinute(m);
} }
return tp; return tp;
} }
/* /**
* (non-Javadoc)
*
* @see * @see
* android.widget.TimePicker.OnTimeChangedListener#onTimeChanged(android * android.widget.TimePicker.OnTimeChangedListener#onTimeChanged(android
* .widget.TimePicker, int, int) * .widget.TimePicker, int, int)
*/ */
@Override @Override
public void onTimeChanged(TimePicker view, int hour, int minute) { public void onTimeChanged(final TimePicker view, final int hour, final int minute) {
persistString(String.format("%02d:%02d", hour, minute)); persistString(String.format("%02d:%02d", hour, minute));
callChangeListener(String.format("%02d:%02d", hour, minute)); callChangeListener(String.format("%02d:%02d", hour, minute));
} }
/* /**
* (non-Javadoc) * If not a positive result, restore the original value
* * before going to super.onDialogClosed(positiveResult).
*/
@Override
protected void onDialogClosed(boolean positiveResult) {
if (!positiveResult) {
persistString(String.format("%02d:%02d", originalHour, originalMinute));
callChangeListener(String.format("%02d:%02d", originalHour, originalMinute));
}
super.onDialogClosed(positiveResult);
}
/**
* @see android.preference.Preference#setDefaultValue(java.lang.Object) * @see android.preference.Preference#setDefaultValue(java.lang.Object)
*/ */
@Override @Override
public void setDefaultValue(Object defaultValue) { public void setDefaultValue(final Object defaultValue) {
// BUG this method is never called if you use the 'android:defaultValue' attribute in your XML preference file, not sure why it isn't // BUG this method is never called if you use the 'android:defaultValue' attribute in your XML preference file, not sure why it isn't
super.setDefaultValue(defaultValue); super.setDefaultValue(defaultValue);
@ -113,10 +135,10 @@ public class TimePickerPreference extends DialogPreference implements
/** /**
* Get the hour value (in 24 hour time) * Get the hour value (in 24 hour time)
* *
* @return The hour value, will be 0 to 23 (inclusive) * @return The hour value, will be 0 to 23 (inclusive) or -1 if illegal
*/ */
private int getHour() { private int getHour() {
String time = getPersistedString(this.defaultValue); String time = getTime();
if (time == null || !time.matches(VALIDATION_EXPRESSION)) { if (time == null || !time.matches(VALIDATION_EXPRESSION)) {
return -1; return -1;
} }
@ -127,10 +149,10 @@ public class TimePickerPreference extends DialogPreference implements
/** /**
* Get the minute value * Get the minute value
* *
* @return the minute value, will be 0 to 59 (inclusive) * @return the minute value, will be 0 to 59 (inclusive) or -1 if illegal
*/ */
private int getMinute() { private int getMinute() {
String time = getPersistedString(this.defaultValue); String time = getTime();
if (time == null || !time.matches(VALIDATION_EXPRESSION)) { if (time == null || !time.matches(VALIDATION_EXPRESSION)) {
return -1; return -1;
} }
@ -138,6 +160,12 @@ public class TimePickerPreference extends DialogPreference implements
return Integer.valueOf(time.split(":")[1]); return Integer.valueOf(time.split(":")[1]);
} }
/**
* Get the time. It is only legal, if it matches
* {@link #VALIDATION_EXPRESSION}.
*
* @return the time as hh:mm
*/
public String getTime() { public String getTime() {
return getPersistedString(this.defaultValue); return getPersistedString(this.defaultValue);
} }

View File

@ -72,7 +72,14 @@ public class AttachmentProvider extends ContentProvider {
* We use the cache dir as a temporary directory (since Android doesn't give us one) so * We use the cache dir as a temporary directory (since Android doesn't give us one) so
* on startup we'll clean up any .tmp files from the last run. * on startup we'll clean up any .tmp files from the last run.
*/ */
File[] files = getContext().getCacheDir().listFiles(); final File cacheDir = getContext().getCacheDir();
if (cacheDir == null) {
return true;
}
File[] files = cacheDir.listFiles();
if (files == null) {
return true;
}
for (File file : files) { for (File file : files) {
if (file.getName().endsWith(".tmp")) { if (file.getName().endsWith(".tmp")) {
file.delete(); file.delete();

View File

@ -1,7 +1,16 @@
package com.fsck.k9.view; package com.fsck.k9.view;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.commons.io.IOUtils;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.net.Uri; import android.net.Uri;
@ -9,7 +18,12 @@ import android.os.Environment;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
import android.widget.*; import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.fsck.k9.Account; import com.fsck.k9.Account;
import com.fsck.k9.K9; import com.fsck.k9.K9;
import com.fsck.k9.R; import com.fsck.k9.R;
@ -23,9 +37,6 @@ import com.fsck.k9.mail.Part;
import com.fsck.k9.mail.internet.MimeUtility; import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.store.LocalStore.LocalAttachmentBodyPart; import com.fsck.k9.mail.store.LocalStore.LocalAttachmentBodyPart;
import com.fsck.k9.provider.AttachmentProvider; import com.fsck.k9.provider.AttachmentProvider;
import org.apache.commons.io.IOUtils;
import java.io.*;
public class AttachmentView extends FrameLayout { public class AttachmentView extends FrameLayout {
@ -42,6 +53,8 @@ public class AttachmentView extends FrameLayout {
public long size; public long size;
public ImageView iconView; public ImageView iconView;
private AttachmentFileDownloadCallback callback;
public AttachmentView(Context context, AttributeSet attrs, int defStyle) { public AttachmentView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle); super(context, attrs, defStyle);
mContext = context; mContext = context;
@ -56,7 +69,18 @@ public class AttachmentView extends FrameLayout {
} }
public interface AttachmentFileDownloadCallback {
/**
* this method i called by the attachmentview when
* he wants to show a filebrowser
* the provider should show the filebrowser activity
* and save the reference to the attachment view for later.
* in his onActivityResult he can get the saved reference and
* call the saveFile method of AttachmentView
* @param view
*/
public void showFileBrowser(AttachmentView caller);
}
public boolean populateFromPart(Part inputPart, Message message, Account account, MessagingController controller, MessagingListener listener) { public boolean populateFromPart(Part inputPart, Message message, Account account, MessagingController controller, MessagingListener listener) {
try { try {
part = (LocalAttachmentBodyPart) inputPart; part = (LocalAttachmentBodyPart) inputPart;
@ -113,6 +137,14 @@ public class AttachmentView extends FrameLayout {
return; return;
} }
}); });
downloadButton.setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
callback.showFileBrowser(AttachmentView.this);
return true;
}
});
attachmentName.setText(name); attachmentName.setText(name);
attachmentInfo.setText(SizeFormatter.formatSize(mContext, size)); attachmentInfo.setText(SizeFormatter.formatSize(mContext, size));
@ -158,9 +190,13 @@ public class AttachmentView extends FrameLayout {
saveFile(); saveFile();
} }
public void writeFile() { /**
* Writes the attachment onto the given path
* @param directory: the base dir where the file should be saved.
*/
public void writeFile(File directory) {
try { try {
File file = Utility.createUniqueFile(Environment.getExternalStorageDirectory(), name); File file = Utility.createUniqueFile(directory, name);
Uri uri = AttachmentProvider.getAttachmentUri(mAccount, part.getAttachmentId()); Uri uri = AttachmentProvider.getAttachmentUri(mAccount, part.getAttachmentId());
InputStream in = mContext.getContentResolver().openInputStream(uri); InputStream in = mContext.getContentResolver().openInputStream(uri);
OutputStream out = new FileOutputStream(file); OutputStream out = new FileOutputStream(file);
@ -168,14 +204,22 @@ public class AttachmentView extends FrameLayout {
out.flush(); out.flush();
out.close(); out.close();
in.close(); in.close();
attachmentSaved(file.getName()); attachmentSaved(file.toString());
new MediaScannerNotifier(mContext, file); new MediaScannerNotifier(mContext, file);
} catch (IOException ioe) { } catch (IOException ioe) {
attachmentNotSaved(); attachmentNotSaved();
} }
} }
/**
* saves the file to the defaultpath setting in the config, or if the config
* is not set => to the Environment
*/
public void writeFile() {
writeFile(new File(K9.getAttachmentDefaultPath()));
}
public void saveFile() { public void saveFile() {
//TODO: Can the user save attachments on the internal filesystem or sd card only?
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
/* /*
* Abort early if there's no place to save the attachment. We don't want to spend * Abort early if there's no place to save the attachment. We don't want to spend
@ -248,4 +292,11 @@ public class AttachmentView extends FrameLayout {
mContext.getString(R.string.message_view_status_attachment_not_saved), mContext.getString(R.string.message_view_status_attachment_not_saved),
Toast.LENGTH_LONG).show(); Toast.LENGTH_LONG).show();
} }
public AttachmentFileDownloadCallback getCallback() {
return callback;
}
public void setCallback(AttachmentFileDownloadCallback callback) {
this.callback = callback;
}
} }

View File

@ -44,7 +44,7 @@ public class SingleMessageView extends LinearLayout {
private Button mDownloadRemainder; private Button mDownloadRemainder;
private LayoutInflater mInflater; private LayoutInflater mInflater;
private Contacts mContacts; private Contacts mContacts;
private AttachmentView.AttachmentFileDownloadCallback attachmentCallback;
public void initialize(Activity activity) { public void initialize(Activity activity) {
mMessageContentView = (MessageWebView) findViewById(R.id.message_content); mMessageContentView = (MessageWebView) findViewById(R.id.message_content);
@ -265,6 +265,7 @@ public class SingleMessageView extends LinearLayout {
return; return;
} }
AttachmentView view = (AttachmentView)mInflater.inflate(R.layout.message_view_attachment, null); AttachmentView view = (AttachmentView)mInflater.inflate(R.layout.message_view_attachment, null);
view.setCallback(attachmentCallback);
if (view.populateFromPart(part, message, account, controller, listener)) { if (view.populateFromPart(part, message, account, controller, listener)) {
addAttachment(view); addAttachment(view);
} }
@ -299,4 +300,14 @@ public class SingleMessageView extends LinearLayout {
mMessageContentView.clearView(); mMessageContentView.clearView();
mAttachments.removeAllViews(); mAttachments.removeAllViews();
} }
public AttachmentView.AttachmentFileDownloadCallback getAttachmentCallback() {
return attachmentCallback;
}
public void setAttachmentCallback(
AttachmentView.AttachmentFileDownloadCallback attachmentCallback) {
this.attachmentCallback = attachmentCallback;
}
} }

View File

@ -1,4 +1,4 @@
package com.fsck.k9; package com.fsck.k9.activity;
import android.test.ActivityInstrumentationTestCase2; import android.test.ActivityInstrumentationTestCase2;
import com.fsck.k9.activity.Accounts; import com.fsck.k9.activity.Accounts;