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

Merge commit '4.113' into issue-162-new

Conflicts:
	src/com/fsck/k9/activity/ChooseFolder.java
	src/com/fsck/k9/controller/MessagingController.java
	src/com/fsck/k9/mail/store/ImapStore.java
	src/com/fsck/k9/view/MessageHeader.java

I hacked on src/com/fsck/k9/controller/MessagingController.java and perhaps broke uidplus support,
but things run fine in my simple tests.
This commit is contained in:
ashley willis 2012-09-11 00:56:06 -05:00
commit a5d0e44711
67 changed files with 2447 additions and 1033 deletions

View File

@ -9,5 +9,6 @@
<classpathentry kind="lib" path="libs/jzlib-1.0.7.jar"/>
<classpathentry kind="lib" path="libs/jutf7-1.0.1-SNAPSHOT.jar"/>
<classpathentry kind="lib" path="libs/htmlcleaner-2.2.jar"/>
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
<classpathentry kind="output" path="bin/classes"/>
</classpath>

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="15011"
android:versionName="4.112" package="com.fsck.k9"
android:versionCode="15012"
android:versionName="4.113" package="com.fsck.k9"
>
<uses-sdk
android:minSdkVersion="7"

View File

@ -19,5 +19,5 @@
split.density=false
java.encoding=utf8
# Project target.
target=android-9
target=android-15
extensible.libs.classpath=compile-only-libs

View File

@ -11,5 +11,5 @@
split.density=false
java.encoding=utf8
# Project target.
target=android-9
target=android-15
extensible.libs.classpath=compile-only-libs

Binary file not shown.

Before

Width:  |  Height:  |  Size: 434 B

After

Width:  |  Height:  |  Size: 570 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 436 B

After

Width:  |  Height:  |  Size: 570 B

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ExpandableListView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:divider="?android:attr/listDivider"
android:childDivider="?android:attr/listDivider"
android:indicatorLeft="14dip" />

View File

@ -3,37 +3,25 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
android:orientation="horizontal"
android:paddingRight="6dip"
android:paddingBottom="2dip"
android:descendantFocusability="blocksDescendants"
android:gravity="center_vertical" >
android:background="#cccccc"
android:gravity="left|center_vertical">
<View
android:id="@+id/chip"
android:layout_width="6dip"
android:layout_height="fill_parent"
android:layout_centerVertical="true"
android:layout_alignParentLeft="true" />
android:layout_width="6dp"/>
<LinearLayout
android:layout_width="wrap_content"
<TextView
android:id="@+id/name"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:gravity="center_vertical"
android:paddingLeft="?android:attr/expandableListPreferredItemPaddingLeft" >
<TextView
android:id="@+id/description"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="end"
android:textColor="?android:attr/textColorPrimary"
android:textAppearance="?android:attr/textAppearanceMedium" />
</LinearLayout>
android:singleLine="true"
android:ellipsize="end"
android:paddingTop="8dp"
android:paddingLeft="18dp"
android:paddingRight="4dp"
android:paddingBottom="8dp"
android:textColor="?android:attr/textColorPrimary"
android:textAppearance="?android:attr/textAppearanceMedium"/>
</LinearLayout>

View File

@ -4,45 +4,27 @@
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
android:orientation="horizontal"
android:paddingRight="6dip"
android:paddingBottom="2dip"
android:descendantFocusability="blocksDescendants"
android:gravity="center_vertical" >
android:orientation="vertical"
android:gravity="center_vertical"
android:paddingLeft="12dp"
android:paddingRight="4dp">
<View
android:id="@+id/chip"
android:layout_width="6dip"
android:layout_height="fill_parent"
android:layout_centerVertical="true"
android:layout_alignParentLeft="true" />
<LinearLayout
android:layout_width="wrap_content"
<TextView
android:id="@+id/name"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:gravity="center_vertical"
android:paddingLeft="?android:attr/expandableListPreferredItemPaddingLeft" >
android:singleLine="true"
android:ellipsize="end"
android:textColor="?android:attr/textColorPrimary"
android:textAppearance="?android:attr/textAppearanceSmall"/>
<TextView
android:id="@+id/name"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="end"
android:textColor="?android:attr/textColorPrimary"
android:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
android:id="@+id/description"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="end"
android:textColor="?android:attr/textColorSecondary"
android:textAppearance="?android:attr/textAppearanceSmall" />
</LinearLayout>
<TextView
android:id="@+id/description"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="end"
android:textColor="?android:attr/textColorSecondary"
android:textAppearance="?android:attr/textAppearanceSmall"/>
</LinearLayout>

View File

@ -2,15 +2,14 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:background="#ffffff" >
android:orientation="vertical">
<ScrollView
android:layout_width="fill_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:scrollbarStyle="outsideInset"
android:fillViewport="true" >
android:scrollbarStyle="insideOverlay"
android:fillViewport="true">
<LinearLayout
android:layout_width="fill_parent"
@ -20,104 +19,107 @@
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:paddingTop="6dp"
android:orientation="vertical"
android:background="#ededed" >
android:background="#45bcbcbc">
<TextView
android:id="@+id/from"
<Button
android:id="@+id/identity"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="left|center"
android:layout_marginLeft="6dip"
android:layout_marginRight="6dip"
android:textColor="@android:color/primary_text_light"
android:textAppearance="?android:attr/textAppearanceMedium" />
android:layout_marginRight="6dip"/>
<LinearLayout
android:id="@+id/to_wrapper"
android:layout_height="wrap_content"
android:baselineAligned="true"
android:layout_marginLeft="6dip"
android:layout_marginRight="6dip"
android:layout_marginLeft="6dip"
android:layout_marginRight="6dip"
android:layout_width="fill_parent">
<MultiAutoCompleteTextView
android:id="@+id/to"
android:layout_height="wrap_content"
android:inputType="textEmailAddress|textMultiLine"
android:imeOptions="actionNext"
android:hint="@string/message_compose_to_hint"
android:textColor="@android:color/primary_text_light"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="fill_parent"
android:layout_marginRight="6dip"
android:layout_weight="5"
/>
<ImageButton
android:id="@+id/add_to"
<MultiAutoCompleteTextView
android:id="@+id/to"
android:layout_height="wrap_content"
android:inputType="textEmailAddress|textMultiLine"
android:imeOptions="actionNext"
android:hint="@string/message_compose_to_hint"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="0dp"
android:layout_marginRight="6dip"
android:layout_weight="1"/>
<ImageButton
android:id="@+id/add_to"
android:contentDescription="@string/message_compose_description_add_to"
android:src="@drawable/ic_button_contacts"
android:layout_weight="1"
android:layout_height="fill_parent"
android:layout_width="60dip"
android:layout_marginTop="1dip"
/>
</LinearLayout>
android:layout_width="wrap_content"
android:layout_marginTop="1dip"/>
</LinearLayout>
<LinearLayout
android:id="@+id/cc_wrapper"
android:visibility="gone"
android:layout_height="wrap_content"
android:baselineAligned="true"
android:layout_marginLeft="6dip"
android:layout_marginRight="6dip"
android:layout_marginLeft="6dip"
android:layout_marginRight="6dip"
android:layout_width="fill_parent">
<MultiAutoCompleteTextView
android:id="@+id/cc"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="5"
android:layout_marginRight="6dip"
android:inputType="textEmailAddress|textMultiLine"
android:imeOptions="actionNext"
android:hint="@string/message_compose_cc_hint"
android:textColor="@android:color/primary_text_light"
android:textAppearance="?android:attr/textAppearanceMedium"
/>
<ImageButton
android:id="@+id/add_cc"
android:src="@drawable/ic_button_contacts"
android:layout_weight="1"
android:layout_width="60dip"
android:layout_height="fill_parent"
android:layout_marginTop="1dip"
/>
</LinearLayout>
<MultiAutoCompleteTextView
android:id="@+id/cc"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginRight="6dip"
android:inputType="textEmailAddress|textMultiLine"
android:imeOptions="actionNext"
android:hint="@string/message_compose_cc_hint"
android:textAppearance="?android:attr/textAppearanceMedium"/>
<ImageButton
android:id="@+id/add_cc"
android:contentDescription="@string/message_compose_description_add_cc"
android:src="@drawable/ic_button_contacts"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_marginTop="1dip"/>
</LinearLayout>
<LinearLayout
android:id="@+id/bcc_wrapper"
android:visibility="gone"
android:layout_height="wrap_content"
android:baselineAligned="true"
android:layout_marginLeft="6dip"
android:layout_marginRight="6dip"
android:layout_marginLeft="6dip"
android:layout_marginRight="6dip"
android:layout_width="fill_parent">
<MultiAutoCompleteTextView
android:id="@+id/bcc"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginRight="6dip"
android:layout_weight="5"
android:inputType="textEmailAddress|textMultiLine"
android:imeOptions="actionNext"
android:hint="@string/message_compose_bcc_hint"
android:textColor="@android:color/primary_text_light"
android:textAppearance="?android:attr/textAppearanceMedium"
/>
<ImageButton
android:layout_marginTop="1dip"
<MultiAutoCompleteTextView
android:id="@+id/bcc"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginRight="6dip"
android:layout_weight="1"
android:inputType="textEmailAddress|textMultiLine"
android:imeOptions="actionNext"
android:hint="@string/message_compose_bcc_hint"
android:textAppearance="?android:attr/textAppearanceMedium"/>
<ImageButton
android:id="@+id/add_bcc"
android:contentDescription="@string/message_compose_description_add_bcc"
android:layout_marginTop="1dip"
android:layout_height="fill_parent"
android:id="@+id/add_bcc"
android:layout_weight="1"
android:layout_width="60dip"
android:src="@drawable/ic_button_contacts"
/>
</LinearLayout>
android:layout_width="wrap_content"
android:src="@drawable/ic_button_contacts"/>
</LinearLayout>
<LinearLayout
android:id="@+id/layout_encrypt"
@ -138,7 +140,6 @@
<CheckBox
android:text="@string/btn_crypto_sign"
android:id="@+id/cb_crypto_signature"
android:textColor="@android:color/primary_text_light"
android:layout_gravity="center_vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
@ -154,7 +155,6 @@
android:id="@+id/userId"
android:ellipsize="end"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="@android:color/primary_text_light"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
@ -162,7 +162,6 @@
android:id="@+id/userIdRest"
android:textSize="10sp"
android:ellipsize="end"
android:textColor="@android:color/primary_text_light"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
@ -173,7 +172,6 @@
<CheckBox
android:text="@string/btn_encrypt"
android:id="@+id/cb_encrypt"
android:textColor="@android:color/primary_text_light"
android:layout_gravity="center_vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -191,7 +189,6 @@
android:inputType="textEmailSubject|textAutoCorrect|textCapSentences|textImeMultiLine"
android:imeOptions="actionNext"
android:singleLine="true"
android:textColor="@android:color/primary_text_light"
android:textAppearance="?android:attr/textAppearanceMedium" />
<!--
@ -211,30 +208,31 @@
</LinearLayout>
<!-- We have to use "wrap_content" (not "0dip") for "layout_height", otherwise the
EditText won't properly grow in height while the user is typing the message -->
<EditText
android:id="@+id/message_content"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1.0"
android:layout_weight="1"
android:gravity="left|top"
android:hint="@string/message_compose_content_hint"
android:inputType="textMultiLine|textAutoCorrect|textCapSentences"
android:imeOptions="actionDone|flagNoEnterAction"
android:minLines="3"
android:textColor="@android:color/primary_text_light"
android:textAppearance="?android:attr/textAppearanceMedium" />
<EditText
android:id="@+id/upper_signature"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1.0"
android:gravity="left|top"
android:editable="false"
android:minLines="0"
android:autoText="true"
android:capitalize="sentences"
android:textColor="@android:color/primary_text_light"
android:hint="@string/message_compose_signature_hint"
android:inputType="textMultiLine|textAutoCorrect|textCapSentences"
android:textAppearance="?android:attr/textAppearanceMedium" />
<Button
@ -244,9 +242,7 @@
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" />
android:layout_width="match_parent"/>
<!-- Quoted text bar -->
<RelativeLayout
@ -258,12 +254,11 @@
android:id="@+id/quoted_text"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1.0"
android:gravity="left|top"
android:minLines="3"
android:autoText="true"
android:capitalize="sentences"
android:textColor="@android:color/primary_text_light"
android:inputType="textMultiLine|textAutoCorrect|textCapSentences"
android:textAppearance="?android:attr/textAppearanceMedium" />
<com.fsck.k9.view.MessageWebView
@ -280,19 +275,17 @@
<ImageButton
android:id="@+id/quoted_text_edit"
android:contentDescription="@string/message_compose_description_edit_quoted_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:layout_marginRight="8dip"
android:background="@drawable/btn_edit" />
<ImageButton
android:id="@+id/quoted_text_delete"
android:contentDescription="@string/message_compose_description_delete_quoted_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
android:background="@drawable/btn_dialog" />
</LinearLayout>
@ -303,13 +296,13 @@
android:id="@+id/lower_signature"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1.0"
android:gravity="left|top"
android:editable="false"
android:minLines="0"
android:autoText="true"
android:capitalize="sentences"
android:textColor="@android:color/primary_text_light"
android:hint="@string/message_compose_signature_hint"
android:inputType="textMultiLine|textAutoCorrect|textCapSentences"
android:textAppearance="?android:attr/textAppearanceMedium" />
</LinearLayout>

View File

@ -122,16 +122,6 @@
</LinearLayout>
<TextView
android:id="@+id/additional_headers_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="false"
android:ellipsize="none"
android:textSize="10sp"
android:textColor="?android:attr/textColorSecondary"
android:textAppearance="?android:attr/textAppearanceSmall"/>
</LinearLayout>
@ -192,6 +182,31 @@
</TableRow>
<TableRow
android:id="@+id/additional_headers_row">
<!-- Color chip 2 -->
<View
android:id="@+id/chip2"
android:layout_marginRight="6dip"
android:layout_width="6dip"
android:layout_height="fill_parent"/>
<!-- Additional headers -->
<TextView
android:layout_span="2"
android:id="@+id/additional_headers_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="10dp"
android:singleLine="false"
android:ellipsize="none"
android:textSize="10sp"
android:textColor="?android:attr/textColorSecondary"
android:textAppearance="?android:attr/textAppearanceSmall" />
</TableRow>
</TableLayout>
<!-- Separator -->
@ -201,19 +216,19 @@
android:id="@+id/show_additional_headers_area"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="21.5dp"
android:layout_height="21dp"
android:focusable="true"
android:clickable="true"
android:background="@drawable/separator_area_background">
android:background="@drawable/message_view_header_background">
<RelativeLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="20dp">
<!-- Color chip 2 -->
<!-- Color chip 3 -->
<View
android:id="@+id/chip2"
android:id="@+id/chip3"
android:layout_marginRight="6dip"
android:layout_width="6dip"
android:layout_height="fill_parent"
@ -233,10 +248,9 @@
</RelativeLayout>
<View
android:id="@+id/separator"
android:layout_width="fill_parent"
android:layout_height="1.5dp"
android:background="#59000000"/>
android:layout_height="1dip"
android:background="@drawable/divider_horizontal_email" />
</LinearLayout>

View File

@ -30,12 +30,6 @@
android:title="@string/discard_action"
android:icon="@drawable/ic_menu_close_clear_cancel"
/>
<item
android:id="@+id/choose_identity"
android:alphabeticShortcut="i"
android:title="@string/send_as"
android:icon="@drawable/ic_menu_identity"
/>
<item
android:id="@+id/read_receipt"
android:alphabeticShortcut="r"

View File

@ -15,6 +15,10 @@
android:id="@+id/set_sort_date"
android:title="@string/sort_by_date"
/>
<item
android:id="@+id/set_sort_arrival"
android:title="@string/sort_by_arrival"
/>
<item
android:id="@+id/set_sort_subject"
android:title="@string/sort_by_subject"

View File

@ -64,7 +64,7 @@
<string name="done_action">Fertig</string> <!-- Used to complete a multi-step process -->
<string name="remove_action">Entfernen</string>
<string name="discard_action">Verwerfen</string>
<string name="save_draft_action">Als Entwurf speichern</string>
<string name="save_draft_action">Speichern</string>
<string name="retry_action">Erneut versuchen</string>
<string name="refresh_action">Aktualisieren</string>
<string name="check_mail_action">Nachrichten abrufen</string>
@ -218,7 +218,7 @@ Willkommen zum \"K-9 Mail\"-Setup. K-9 ist eine quelloffene E-Mail-Anwendung fü
\n * Speichern von Anhängen auf SD-Karte
\n * Papierkorb leeren
\n * Sortieren der Nachrichten
\n * ...und viele mehr
\n * und viele mehr
\n
\nBitte beachten Sie, dass K-9, wie viele andere E-Mail-Anwendungen auch, die meisten kostenlosen Hotmail-Accounts nicht unterstützt. Zudem gibt es einige Probleme mit Microsoft Exchange-Servern.
\n
@ -256,6 +256,7 @@ Willkommen zum \"K-9 Mail\"-Setup. K-9 ist eine quelloffene E-Mail-Anwendung fü
<string name="message_compose_bcc_hint">BCC</string>
<string name="message_compose_subject_hint">Betreff</string>
<string name="message_compose_content_hint">Nachrichtentext</string>
<string name="message_compose_signature_hint">Signatur</string>
<string name="message_compose_quote_header_separator">-------- Original-Nachricht --------</string>
<string name="message_compose_quote_header_subject">Betreff:</string>
<string name="message_compose_quote_header_send_date">Gesendet:</string>
@ -269,6 +270,11 @@ Willkommen zum \"K-9 Mail\"-Setup. K-9 ist eine quelloffene E-Mail-Anwendung fü
<string name="message_compose_downloading_attachments_toast">Einige Anhänge wurden nicht heruntergeladen. Sie werden automatisch heruntergeladen, bevor diese Nachricht gesendet wird.</string>
<string name="message_compose_attachments_skipped_toast">Einige Anhänge können nicht weitergeleitet werden, da diese nicht heruntergeladen wurden.</string>
<string name="message_compose_show_quoted_text_action">Original-Nachricht zitieren</string>
<string name="message_compose_description_add_to">Empfänger hinzufügen</string>
<string name="message_compose_description_add_cc">Empfänger hinzufügen (CC)</string>
<string name="message_compose_description_add_bcc">Empfänger hinzufügen (BCC)</string>
<string name="message_compose_description_delete_quoted_text">Zitierten Text entfernen</string>
<string name="message_compose_description_edit_quoted_text">Zitierten Text bearbeiten</string>
<string name="message_view_from_format">Von: <xliff:g id="name">%s</xliff:g> &lt;<xliff:g id="email">%s</xliff:g>&gt;</string>
<string name="message_to_label">An:</string>
@ -285,6 +291,7 @@ Willkommen zum \"K-9 Mail\"-Setup. K-9 ist eine quelloffene E-Mail-Anwendung fü
<string name="message_view_status_attachment_not_saved">Anhang konnte nicht auf SD-Karte gespeichert werden.</string>
<string name="message_view_show_pictures_instructions">Wählen Sie \"Bilder anzeigen\", um eingebettete Bilder abzurufen.</string>
<string name="message_view_show_pictures_action">Bilder anzeigen</string>
<string name="message_view_show_message_action">Zeige Nachricht</string>
<string name="message_view_show_attachments_action">Zeige Anhänge</string>
<string name="message_view_show_more_attachments_action">Mehr&#8230;</string>
<string name="message_view_fetching_attachment_toast">Lade Anhang.</string>
@ -402,7 +409,7 @@ Willkommen zum \"K-9 Mail\"-Setup. K-9 ist eine quelloffene E-Mail-Anwendung fü
<string name="account_setup_incoming_security_tls_optional_label">TLS (falls verfügbar)</string>
<string name="account_setup_incoming_security_tls_label">TLS (immer)</string>
<string name="account_setup_incoming_delete_policy_label">Bei Löschen von Nachrichten:</string>
<string name="account_setup_incoming_delete_policy_label">Beim Löschen von Nachrichten</string>
<string name="account_setup_incoming_delete_policy_never_label">Nie von Server löschen</string>
<string name="account_setup_incoming_delete_policy_7days_label">Nach 7 Tagen löschen</string>
<string name="account_setup_incoming_delete_policy_delete_label">Auch auf Server löschen</string>
@ -426,7 +433,7 @@ Willkommen zum \"K-9 Mail\"-Setup. K-9 ist eine quelloffene E-Mail-Anwendung fü
<string name="account_setup_expunge_policy_on_poll">Bei jedem Abrufen</string>
<string name="account_setup_expunge_policy_manual">Nur manuell</string>
<string name="account_setup_incoming_autodetect_namespace_label">IMAP Namensraum automatisch ermitteln</string>
<string name="account_setup_incoming_autodetect_namespace_label">IMAP-Namensraum automatisch ermitteln</string>
<string name="account_setup_incoming_imap_path_prefix_label">IMAP-Verzeichnispräfix</string>
<string name="drafts_folder_label">Ordner für Entwürfe</string>
@ -457,7 +464,7 @@ Willkommen zum \"K-9 Mail\"-Setup. K-9 ist eine quelloffene E-Mail-Anwendung fü
<string name="account_setup_outgoing_require_login_label">Anmeldung erforderlich.</string>
<string name="account_setup_outgoing_username_label">Benutzername</string>
<string name="account_setup_outgoing_password_label">Passwort</string>
<string name="account_setup_outgoing_authentication_label">Authentifizierungstyp</string>
<string name="account_setup_outgoing_authentication_label">Authentifizierungsmethode</string>
<!-- The authentication strings below are for a planned (hopefully) change to the above username and password options -->
<string name="account_setup_outgoing_authentication_basic_label">Benutzername &amp; Passwort</string>
<string name="account_setup_outgoing_authentication_basic_username_label">Benutzername</string>
@ -492,7 +499,7 @@ Willkommen zum \"K-9 Mail\"-Setup. K-9 ist eine quelloffene E-Mail-Anwendung fü
<string name="push_poll_on_connect_label">Abruf beim Start der Push-Verbindung</string>
<string name="account_setup_options_enable_push_label">Push-Mail für dieses Konto aktivieren</string>
<string name="account_setup_options_enable_push_summary">Neue Nachrichten werden nach dem Eintreffen umgehend abgerufen, falls Ihr Server dies unterstützt. Diese Einstellung kann zur Reduzierung der Laufzeit führen oder diese verbessern.</string>
<string name="idle_refresh_period_label">IDLE Verbindung refreshen</string>
<string name="idle_refresh_period_label">Push-Verbindung erneuern</string>
<string name="idle_refresh_period_1min">Jede Minute</string>
<string name="idle_refresh_period_2min">Alle 2 Minuten</string>
<string name="idle_refresh_period_3min">Alle 3 Minuten</string>
@ -544,6 +551,8 @@ Willkommen zum \"K-9 Mail\"-Setup. K-9 ist eine quelloffene E-Mail-Anwendung fü
<string name="account_settings_notification_opens_unread_summary">Zeigt beim Öffnen einer Benachrichtigung Liste der ungelesenen Nachrichten an</string>
<string name="account_settings_notification_unread_count_label">Anzahl ungelesener Nachrichten anzeigen</string>
<string name="account_settings_notification_unread_count_summary">Zeigt die Anzahl der ungelesenen Nachrichten in der Statuszeile.</string>
<string name="account_settings_mark_message_as_read_on_view_label">Nachricht beim Öffnen als gelesen markieren</string>
<string name="account_settings_mark_message_as_read_on_view_summary">Markiert eine Nachricht als gelesen, sobald sie zum Betrachten geöffnet wird.</string>
<string name="account_settings_enable_move_buttons_label">Spam-Leiste</string>
<string name="account_settings_enable_move_buttons_summary">Zeige Archivieren-, Verschieben- und Spam-Schaltfläche.</string>
@ -582,16 +591,15 @@ Willkommen zum \"K-9 Mail\"-Setup. K-9 ist eine quelloffene E-Mail-Anwendung fü
<string name="account_settings_folders">Ordner</string>
<string name="account_settings_message_lists">Liste der Nachrichten</string>
<string name="account_settings_message_view">Anzeige der Nachricht</string>
<string name="account_settings_quote_prefix_label">Prefix beim Zitieren</string>
<string name="account_settings_quote_prefix_label">Präfix beim Zitieren</string>
<string name="account_settings_crypto">Kryptographie</string>
<string name="account_settings_crypto_app">OpenPGP Provider</string>
<string name="account_settings_crypto_app_none">Keiner</string>
<string name="account_settings_crypto_app_not_available">Nicht verfügbar</string>
<string name="account_settings_crypto_auto_signature">Auto-sign</string>
<string name="account_settings_crypto_auto_signature_summary">Email Adresse des Kontos verwenden um Signaturschlüssel zu schätzen.</string>
<string name="account_settings_crypto_auto_encrypt">Automatische verschlüsselung</string>
<string name="account_settings_crypto_auto_encrypt_summary">Verschlüsselung aktivieren falls für den Empfänger ein öffentlichen Schlüssel abgespeichert ist.</string>
<string name="account_settings_crypto_auto_signature">Automatisches Signieren</string>
<string name="account_settings_crypto_auto_signature_summary">Verwendet die E-Mail-Adresse des Kontos, um den Signaturschlüssel zu finden.</string>
<string name="account_settings_crypto_auto_encrypt">Automatische Verschlüsselung</string>
<string name="account_settings_crypto_auto_encrypt_summary">Verschlüsselt automatisch, falls für den Empfänger ein öffentlicher Schlüssel vorhanden ist.</string>
<string name="account_settings_mail_check_frequency_label">Häufigkeit der E-Mail-Abfrage</string>
<string name="account_settings_second_class_check_frequency_label">Häufigkeit der Abfrage für Nebenordner</string>
@ -664,7 +672,7 @@ Willkommen zum \"K-9 Mail\"-Setup. K-9 ist eine quelloffene E-Mail-Anwendung fü
<string name="account_settings_folder_target_mode_not_second_class">Alle außer Nebenordner</string>
<string name="account_settings_sync_remote_deletetions_label">Übernehme Löschungen vom Server</string>
<string name="account_settings_sync_remote_deletetions_summary">Lösche Nachrichten wenn sie am Server gelöscht werden</string>
<string name="account_settings_sync_remote_deletetions_summary">Lösche Nachrichten, wenn sie vom Server gelöscht wurden</string>
<string name="folder_settings_title">Ordner-Einstellungen</string>
@ -776,8 +784,9 @@ Willkommen zum \"K-9 Mail\"-Setup. K-9 ist eine quelloffene E-Mail-Anwendung fü
<string name="sort_attach_first">Nachrichten mit Anhängen zuerst</string>
<string name="sort_unattached_first">Nachrichten ohne Anhänge zuerst</string>
<string name="sort_by">Sortieren nach...</string>
<string name="sort_by">Sortieren nach</string>
<string name="sort_by_date">Datum</string>
<string name="sort_by_arrival">Ankunftsdatum</string>
<string name="sort_by_sender">Absender</string>
<string name="sort_by_subject">Betreff</string>
<string name="sort_by_flag">Wichtigkeit</string>
@ -978,6 +987,9 @@ Willkommen zum \"K-9 Mail\"-Setup. K-9 ist eine quelloffene E-Mail-Anwendung fü
<string name="font_size_message_view_date">Datum</string>
<string name="font_size_message_view_content">Nachrichtentext</string>
<string name="font_size_message_compose">Nachricht verfassen</string>
<string name="font_size_message_compose_input">Texteingabefelder</string>
<string name="font_size_tiniest">Winzig</string>
<string name="font_size_tiny">Sehr klein</string>
<string name="font_size_smaller">Kleiner</string>
@ -1019,6 +1031,9 @@ Willkommen zum \"K-9 Mail\"-Setup. K-9 ist eine quelloffene E-Mail-Anwendung fü
<string name="save_or_discard_draft_message_dlg_title">Entwurf speichern?</string>
<string name="save_or_discard_draft_message_instructions_fmt">Entwurf speichern oder verwerfen?</string>
<string name="confirm_discard_draft_message_title">Nachricht verwerfen?</string>
<string name="confirm_discard_draft_message">Sind Sie sicher, dass Sie die Nachricht verwerfen möchten?</string>
<string name="refuse_to_save_draft_marked_encrypted_dlg_title">Speichern des Entwurfs verweigern.</string>
<string name="refuse_to_save_draft_marked_encrypted_instructions_fmt">Die Speicherung von als verschlüsselt markierten Entwürfen verweigern.</string>
@ -1058,9 +1073,9 @@ Willkommen zum \"K-9 Mail\"-Setup. K-9 ist eine quelloffene E-Mail-Anwendung fü
<string name="settings_import">Einstellungen importieren</string>
<string name="settings_import_selection">Auswahl importieren</string>
<string name="settings_import_global_settings">Globale Einstellungen</string>
<string name="settings_exporting">Einstellungen Exportieren...</string>
<string name="settings_importing">Einstellungen Importieren...</string>
<string name="settings_import_scanning_file">Datei lesen...</string>
<string name="settings_exporting">Einstellungen exportieren…</string>
<string name="settings_importing">Einstellungen importieren…</string>
<string name="settings_import_scanning_file">Datei lesen</string>
<string name="settings_export_success">Exportiere Einstellungen erfolgreich in <xliff:g id="filename">%s</xliff:g> gespeichert</string>
<string name="settings_import_global_settings_success">Globale Einstellungen erfolgreich von <xliff:g id="filename">%s</xliff:g> importiert</string>
<string name="settings_import_success"><xliff:g id="accounts">%s</xliff:g> erfolgreich von <xliff:g id="filename">%s</xliff:g> importiert</string>
@ -1069,21 +1084,21 @@ Willkommen zum \"K-9 Mail\"-Setup. K-9 ist eine quelloffene E-Mail-Anwendung fü
<item quantity="other"><xliff:g id="numAccounts">%s</xliff:g> Konten</item>
</plurals>
<string name="settings_export_failure">Exportieren der Einstellungen ist fehlgeschlagen</string>
<string name="settings_import_failure">Importieren der Einstellungen von <xliff:g id="filename">%s</xliff:g> sind fehlgeschlagen</string>
<string name="settings_import_failure">Importieren der Einstellungen von <xliff:g id="filename">%s</xliff:g> ist fehlgeschlagen</string>
<string name="settings_export_success_header">Export erfolgreich</string>
<string name="settings_export_failed_header">Export fehlgeschlagen</string>
<string name="settings_import_success_header">Import erfolgreich</string>
<string name="settings_import_failed_header">Import fehlgeschlagen</string>
<string name="settings_import_activate_account_header">Konto aktivieren</string>
<string name="settings_import_activate_account_intro">Um das Konto \"<xliff:g id="account">%s</xliff:g>\" zu benutzen müssen sie das <xliff:g id="server_passwords">%s</xliff:g> angeben.</string>
<string name="settings_import_activate_account_intro">Um das Konto \"<xliff:g id="account">%s</xliff:g>\" benutzen zu können, müssen Sie <xliff:g id="server_passwords">%s</xliff:g> angeben.</string>
<plurals name="settings_import_server_passwords">
<item quantity="one">Server Passwort</item>
<item quantity="other">Server Passwörter</item>
<item quantity="one">das Server Passwort</item>
<item quantity="other">die Server Passwörter</item>
</plurals>
<string name="settings_import_incoming_server">Posteingangsserver (<xliff:g id="hostname">%s</xliff:g>):</string>
<string name="settings_import_outgoing_server">Postausgangsserver (<xliff:g id="hostname">%s</xliff:g>):</string>
<plurals name="settings_import_setting_passwords">
<item quantity="one">Passwort Setzen...</item>
<item quantity="one">Passwort setzen...</item>
<item quantity="other">Passwörter setzen...</item>
</plurals>
<string name="settings_import_use_incoming_server_password">Passwort des Posteingangsservers benutzen</string>
@ -1093,12 +1108,44 @@ 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="settings_attachment_default_path">Anhang speichern unter...</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>
<string name="manage_accounts_move_up_action">Nach oben verschieben</string>
<string name="manage_accounts_move_down_action">Nach unten verschieben</string>
<string name="manage_accounts_moving_message">Konto verschieben...</string>
<string name="manage_accounts_moving_message">Konto verschieben</string>
<string name="unread_widget_label">K-9 Ungelesen</string>
<string name="unread_widget_select_account">Zeige die Anzahl ungelesener Nachrichten für…</string>
<string name="import_dialog_error_title">Kein Dateimanager gefunden!</string>
<string name="import_dialog_error_message">Es wurde keine geeignete Applikation gefunden, um den Import durchzuführen. Bitte installieren Sie einen Dateimanager aus dem Play Store.</string>
<string name="open_market">Play Store öffnen</string>
<string name="close">Abbrechen</string>
<string name="webview_contextmenu_link_view_action">Öffnen</string>
<string name="webview_contextmenu_link_share_action">Link weitergeben</string>
<string name="webview_contextmenu_link_copy_action">Link kopieren</string>
<string name="webview_contextmenu_link_clipboard_label">Link</string>
<string name="webview_contextmenu_image_title">Bild</string>
<string name="webview_contextmenu_image_view_action">Bild anzeigen</string>
<string name="webview_contextmenu_image_save_action">Bild speichern</string>
<string name="webview_contextmenu_image_download_action">Bild herunterladen</string>
<string name="webview_contextmenu_image_copy_action">Bild-URL kopieren</string>
<string name="webview_contextmenu_image_clipboard_label">Bild-URL</string>
<string name="webview_contextmenu_phone_call_action">Anrufen</string>
<string name="webview_contextmenu_phone_save_action">Im Adressbuch speichern</string>
<string name="webview_contextmenu_phone_copy_action">Telefonnummer kopieren</string>
<string name="webview_contextmenu_phone_clipboard_label">Telefonnummer</string>
<string name="webview_contextmenu_email_send_action">E-Mail senden</string>
<string name="webview_contextmenu_email_save_action">Im Adressbuch speichern</string>
<string name="webview_contextmenu_email_copy_action">E-Mail-Adresse kopieren</string>
<string name="webview_contextmenu_email_clipboard_label">E-Mail-Adresse</string>
<string name="image_saved_as">Das Bild wurde als \"<xliff:g id="filename">%s</xliff:g>\" gespeichert.</string>
<string name="image_saving_failed">Speichern des Bildes fehlgeschlagen.</string>
</resources>

View File

@ -258,6 +258,7 @@ K-9 Mail セットアップにようこそ。\nK-9 は標準のAndroidメール
<string name="message_compose_bcc_hint">Bcc</string>
<string name="message_compose_subject_hint">件名</string>
<string name="message_compose_content_hint">本文</string>
<string name="message_compose_signature_hint">署名</string>
<string name="message_compose_quote_header_separator">-------- 元メール --------</string>
<string name="message_compose_quote_header_subject">件名:</string>
<string name="message_compose_quote_header_send_date">送信日:</string>
@ -271,6 +272,11 @@ K-9 Mail セットアップにようこそ。\nK-9 は標準のAndroidメール
<string name="message_compose_downloading_attachments_toast">一部の添付ファイルをダウンロードしていません。このメールが送信される前に自動的にダウンロードされます。</string>
<string name="message_compose_attachments_skipped_toast">ダウンロードしていないため、一部の添付ファイルを転送することはできません。</string>
<string name="message_compose_show_quoted_text_action">元のメッセージを引用する</string>
<string name="message_compose_description_add_to">Toに宛先を追加します</string>
<string name="message_compose_description_add_cc">CCに宛先を追加します</string>
<string name="message_compose_description_add_bcc">BCCに宛先を追加します</string>
<string name="message_compose_description_delete_quoted_text">引用文を削除します</string>
<string name="message_compose_description_edit_quoted_text">引用文を編集します</string>
<string name="message_view_from_format">送信者: <xliff:g id="name">%s</xliff:g> &lt;<xliff:g id="email">%s</xliff:g>&gt;</string>
<string name="message_to_label">宛先:</string>
@ -287,8 +293,11 @@ K-9 Mail セットアップにようこそ。\nK-9 は標準のAndroidメール
<string name="message_view_status_attachment_not_saved">SDカードに添付ファイルを保存できません</string>
<string name="message_view_show_pictures_instructions">\"画像表示\"ボタンを押下すると描画します</string>
<string name="message_view_show_pictures_action">画像表示</string>
<string name="message_view_fetching_attachment_toast">添付取込中</string>
<string name="message_view_no_viewer">添付ファイルのビューワー見つけられません .<xliff:g id="mimetype">%s</xliff:g></string>
<string name="message_view_show_message_action">メッセージ表示</string>
<string name="message_view_show_attachments_action">添付ファイル表示</string>
<string name="message_view_show_more_attachments_action">&#8230;</string>
<string name="message_view_fetching_attachment_toast">添付ファイル取得中</string>
<string name="message_view_no_viewer"><xliff:g id="mimetype">%s</xliff:g>のビューワーが見つかりません</string>
<string name="message_view_download_remainder">すべてダウンロード</string>
@ -544,6 +553,8 @@ K-9 Mail セットアップにようこそ。\nK-9 は標準のAndroidメール
<string name="account_settings_notification_opens_unread_summary">通知を開いた際に未読メールを検索する</string>
<string name="account_settings_notification_unread_count_label">未読件数の表示</string>
<string name="account_settings_notification_unread_count_summary">通知バーに未読メッセージの件数を表示する</string>
<string name="account_settings_mark_message_as_read_on_view_label">開くと同時に既読にする</string>
<string name="account_settings_mark_message_as_read_on_view_summary">メッセージを参照したときに既読にする</string>
<string name="account_settings_enable_move_buttons_label">メール整理ボタンを有効にする</string>
<string name="account_settings_enable_move_buttons_summary">アーカイブ、移動、迷惑メールボタンを表示</string>
@ -777,6 +788,7 @@ K-9 Mail セットアップにようこそ。\nK-9 は標準のAndroidメール
<string name="sort_by">並べ替え...</string>
<string name="sort_by_date">日付</string>
<string name="sort_by_arrival">受信順</string>
<string name="sort_by_sender">送信者</string>
<string name="sort_by_subject">件名</string>
<string name="sort_by_flag">フラグ</string>
@ -795,7 +807,9 @@ K-9 Mail セットアップにようこそ。\nK-9 は標準のAndroidメール
<string name="provider_note_live">このプログラムでPOPアクセスが許可されているのは一部の「Plus」アカウントだけです。有料の「Plus」アカウントがなければ、正しいメールアドレスとパスワードを入力してもログインできません。これらのアカウントにはブラウザからアクセスしてください。</string>
<string name="provider_note_yahoojp">Yahoo! JapanでPOP3アクセスを使う場合は、Yahoo!メールサイトの「メールの設定」にてPOPアクセスが許可されていることを確認してください。</string>
<string name="provider_note_yahoojp">Yahoo! JapanでPOP3アクセスを行う場合は、Yahoo!メールサイトの「メールの設定」にてPOPアクセスが許可されていることを確認してください。</string>
<string name="provider_note_auonejp">au oneでIMAPアクセスを行う場合は、au oneポータルサイトの「メール」→「設定」ページにて「IMAPを有効にする」をチェックしてください。</string>
<string name="account_setup_failed_dlg_invalid_certificate_title">証明書が無効です</string>
<string name="account_setup_failed_dlg_invalid_certificate_accept">許可</string>
@ -1112,4 +1126,29 @@ K-9 Mail セットアップにようこそ。\nK-9 は標準のAndroidメール
<string name="import_dialog_error_message">設定をインポートするためのアプリケーションがありません。Androidマーケットからファイルマネージャをインストールしてください。</string>
<string name="open_market">マーケット</string>
<string name="close">閉じる</string>
<string name="webview_contextmenu_link_view_action">開く</string>
<string name="webview_contextmenu_link_share_action">リンクを共有</string>
<string name="webview_contextmenu_link_copy_action">リンクをコピー</string>
<string name="webview_contextmenu_link_clipboard_label">リンク</string>
<string name="webview_contextmenu_image_title">画像</string>
<string name="webview_contextmenu_image_view_action">表示</string>
<string name="webview_contextmenu_image_save_action">画像を保存</string>
<string name="webview_contextmenu_image_download_action">画像をダウンロード</string>
<string name="webview_contextmenu_image_copy_action">画像のURLをコピー</string>
<string name="webview_contextmenu_image_clipboard_label">画像のURL</string>
<string name="webview_contextmenu_phone_call_action">発信</string>
<string name="webview_contextmenu_phone_save_action">連絡先に保存</string>
<string name="webview_contextmenu_phone_copy_action">電話番号をコピー</string>
<string name="webview_contextmenu_phone_clipboard_label">電話番号</string>
<string name="webview_contextmenu_email_send_action">メール送信</string>
<string name="webview_contextmenu_email_save_action">連絡先に保存</string>
<string name="webview_contextmenu_email_copy_action">メールアドレスをコピー</string>
<string name="webview_contextmenu_email_clipboard_label">メールアドレス</string>
<string name="image_saved_as">\"<xliff:g id="filename">%s</xliff:g>\"に保存しました</string>
<string name="image_saving_failed">画像の保存に失敗しました</string>
</resources>

View File

@ -2,5 +2,5 @@
<resources>
<color name="message_list_item_background">#ffffff</color>
<color name="message_list_item_footer_background">#eeeeee</color>
<color name="message_view_header_background">#1a080808</color>
<color name="message_view_header_background">#45bcbcbc</color>
</resources>

View File

@ -268,6 +268,7 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin
<string name="message_compose_bcc_hint">Bcc</string>
<string name="message_compose_subject_hint">Subject</string>
<string name="message_compose_content_hint">Message text</string>
<string name="message_compose_signature_hint">Signature</string>
<string name="message_compose_quote_header_separator">-------- Original Message --------</string>
<string name="message_compose_quote_header_subject">Subject:</string>
<string name="message_compose_quote_header_send_date">Sent:</string>
@ -281,6 +282,11 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin
<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_show_quoted_text_action">Quote message</string>
<string name="message_compose_description_add_to">Add recipient (To)</string>
<string name="message_compose_description_add_cc">Add recipient (CC)</string>
<string name="message_compose_description_add_bcc">Add recipient (BCC)</string>
<string name="message_compose_description_delete_quoted_text">Remove quoted text</string>
<string name="message_compose_description_edit_quoted_text">Edit quoted text</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_to_label">To:</string>
@ -561,6 +567,8 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin
<string name="account_settings_notification_opens_unread_summary">Searches for unread messages when Notification is opened</string>
<string name="account_settings_notification_unread_count_label">Show unread count</string>
<string name="account_settings_notification_unread_count_summary">Show the number of unread messages in the notification bar.</string>
<string name="account_settings_mark_message_as_read_on_view_label">Mark message as read when opening</string>
<string name="account_settings_mark_message_as_read_on_view_summary">Mark a message as read when it is opened for viewing</string>
<string name="account_settings_enable_move_buttons_label">Enable refile buttons</string>
<string name="account_settings_enable_move_buttons_summary">Show the Archive, Move, and Spam buttons.</string>
@ -797,6 +805,7 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin
<string name="sort_by">Sort by...</string>
<string name="sort_by_date">Date</string>
<string name="sort_by_arrival">Arrival</string>
<string name="sort_by_sender">Sender</string>
<string name="sort_by_subject">Subject</string>
<string name="sort_by_flag">Star</string>
@ -819,6 +828,7 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin
\"Plus\" account. Please launch the Web browser to gain access to
these mail accounts.</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_auonejp">If you would like to use IMAP or POP3 for this provider, You should permit to use IMAP or POP3 on au one 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>
@ -1150,4 +1160,29 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin
the import operation. Please install a file manager application from Android Market</string>
<string name="open_market">Open Market</string>
<string name="close">Close</string>
<string name="webview_contextmenu_link_view_action">Open for viewing</string>
<string name="webview_contextmenu_link_share_action">Share link</string>
<string name="webview_contextmenu_link_copy_action">Copy link to clipboard</string>
<string name="webview_contextmenu_link_clipboard_label">Link</string>
<string name="webview_contextmenu_image_title">Image</string>
<string name="webview_contextmenu_image_view_action">View image</string>
<string name="webview_contextmenu_image_save_action">Save image</string>
<string name="webview_contextmenu_image_download_action">Download image</string>
<string name="webview_contextmenu_image_copy_action">Copy image URL to clipboard</string>
<string name="webview_contextmenu_image_clipboard_label">Image URL</string>
<string name="webview_contextmenu_phone_call_action">Call number</string>
<string name="webview_contextmenu_phone_save_action">Save to Contacts</string>
<string name="webview_contextmenu_phone_copy_action">Copy phone number to clipboard</string>
<string name="webview_contextmenu_phone_clipboard_label">Phone number</string>
<string name="webview_contextmenu_email_send_action">Send mail</string>
<string name="webview_contextmenu_email_save_action">Save to Contacts</string>
<string name="webview_contextmenu_email_copy_action">Copy email address to clipboard</string>
<string name="webview_contextmenu_email_clipboard_label">Email address</string>
<string name="image_saved_as">Saved image as \"<xliff:g id="filename">%s</xliff:g>\"</string>
<string name="image_saving_failed">Saving the image failed.</string>
</resources>

View File

@ -81,6 +81,17 @@
</PreferenceScreen>
<PreferenceScreen
android:title="@string/interaction_preferences">
<CheckBoxPreference
android:persistent="false"
android:key="mark_message_as_read_on_view"
android:title="@string/account_settings_mark_message_as_read_on_view_label"
android:summary="@string/account_settings_mark_message_as_read_on_view_summary" />
</PreferenceScreen>
<PreferenceScreen
android:title="@string/account_settings_sync"
android:key="incoming_prefs">

View File

@ -283,6 +283,11 @@
<incoming uri="pop3+ssl+://pop.mail.yahoo.co.jp" username="$user" />
<outgoing uri="smtp://smtp.mail.yahoo.co.jp:587" username="$user" />
</provider>
<provider id="auone" label="au one" domain="auone.jp"
note="@string/provider_note_auonejp">
<incoming uri="imap+ssl+://imap.gmail.com" username="$email" />
<outgoing uri="smtp+ssl+://smtp.gmail.com" username="$email" />
</provider>
<!-- Korean -->
<provider id="naver" label="Naver" domain="naver.com"

View File

@ -152,6 +152,7 @@ public class Account implements BaseAccount {
private String mCryptoApp;
private boolean mCryptoAutoSignature;
private boolean mCryptoAutoEncrypt;
private boolean mMarkMessageAsReadOnView;
private CryptoProvider mCryptoProvider = null;
@ -240,6 +241,7 @@ public class Account implements BaseAccount {
mCryptoAutoSignature = false;
mCryptoAutoEncrypt = false;
mEnabled = true;
mMarkMessageAsReadOnView = true;
searchableFolders = Searchable.ALL;
@ -396,6 +398,7 @@ public class Account implements BaseAccount {
mCryptoAutoSignature = prefs.getBoolean(mUuid + ".cryptoAutoSignature", false);
mCryptoAutoEncrypt = prefs.getBoolean(mUuid + ".cryptoAutoEncrypt", false);
mEnabled = prefs.getBoolean(mUuid + ".enabled", true);
mMarkMessageAsReadOnView = prefs.getBoolean(mUuid + ".markMessageAsReadOnView", true);
}
protected synchronized void delete(Preferences preferences) {
@ -478,6 +481,7 @@ public class Account implements BaseAccount {
editor.remove(mUuid + ".enabled");
editor.remove(mUuid + ".enableMoveButtons");
editor.remove(mUuid + ".hideMoveButtonsEnum");
editor.remove(mUuid + ".markMessageAsReadOnView");
for (String type : networkTypes) {
editor.remove(mUuid + ".useCompression." + type);
}
@ -639,6 +643,7 @@ public class Account implements BaseAccount {
editor.putBoolean(mUuid + ".cryptoAutoSignature", mCryptoAutoSignature);
editor.putBoolean(mUuid + ".cryptoAutoEncrypt", mCryptoAutoEncrypt);
editor.putBoolean(mUuid + ".enabled", mEnabled);
editor.putBoolean(mUuid + ".markMessageAsReadOnView", mMarkMessageAsReadOnView);
editor.putBoolean(mUuid + ".vibrate", mNotificationSetting.shouldVibrate());
editor.putInt(mUuid + ".vibratePattern", mNotificationSetting.getVibratePattern());
@ -1557,4 +1562,12 @@ Log.d("ASH", "setTrashFolderName() attempting change of folder.setLocalOnly()");
public synchronized void setEnabled(boolean enabled) {
mEnabled = enabled;
}
public synchronized boolean isMarkMessageAsReadOnView() {
return mMarkMessageAsReadOnView;
}
public synchronized void setMarkMessageAsReadOnView(boolean value) {
mMarkMessageAsReadOnView = value;
}
}

View File

@ -38,6 +38,9 @@ import com.fsck.k9.service.ShutdownReceiver;
import com.fsck.k9.service.StorageGoneReceiver;
public class K9 extends Application {
public static final int THEME_LIGHT = 0;
public static final int THEME_DARK = 1;
/**
* Components that are interested in knowing when the K9 instance is
* available and ready (Android invokes Application.onCreate() after other
@ -72,7 +75,7 @@ public class K9 extends Application {
}
private static String language = "";
private static int theme = android.R.style.Theme_Light;
private static int theme = THEME_LIGHT;
private static final FontSizes fontSizes = new FontSizes();
@ -606,7 +609,17 @@ public class K9 extends Application {
}
K9.setK9Language(sprefs.getString("language", ""));
K9.setK9Theme(sprefs.getInt("theme", android.R.style.Theme_Light));
int theme = sprefs.getInt("theme", THEME_LIGHT);
// We used to save the resource ID of the theme. So convert that to the new format if
// necessary.
if (theme == THEME_DARK || theme == android.R.style.Theme) {
theme = THEME_DARK;
} else {
theme = THEME_LIGHT;
}
K9.setK9Theme(theme);
}
private void maybeSetupStrictMode() {
@ -665,6 +678,14 @@ public class K9 extends Application {
language = nlanguage;
}
public static int getK9ThemeResourceId(int theme) {
return (theme == THEME_LIGHT) ? android.R.style.Theme_Light : android.R.style.Theme;
}
public static int getK9ThemeResourceId() {
return getK9ThemeResourceId(theme);
}
public static int getK9Theme() {
return theme;
}

View File

@ -12,6 +12,39 @@ import com.fsck.k9.mail.Flag;
* is defined by {@link com.fsck.k9.activity.SearchModifier}.
*/
public class SearchAccount implements BaseAccount, SearchSpecification, Serializable {
/**
* Create a {@code SearchAccount} instance for the Unified Inbox.
*
* @param context
* A {@link Context} instance that will be used to get localized strings and will be
* passed on to the {@code SearchAccount} instance.
*
* @return The {@link SearchAccount} instance for the Unified Inbox.
*/
public static SearchAccount createUnifiedInboxAccount(Context context) {
SearchAccount unifiedInbox = new SearchAccount(context, true, null, null);
unifiedInbox.setDescription(context.getString(R.string.integrated_inbox_title));
unifiedInbox.setEmail(context.getString(R.string.integrated_inbox_detail));
return unifiedInbox;
}
/**
* Create a {@code SearchAccount} instance for the special account "All messages".
*
* @param context
* A {@link Context} instance that will be used to get localized strings and will be
* passed on to the {@code SearchAccount} instance.
*
* @return The {@link SearchAccount} instance for the Unified Inbox.
*/
public static SearchAccount createAllMessagesAccount(Context context) {
SearchAccount allMessages = new SearchAccount(context, false, null, null);
allMessages.setDescription(context.getString(R.string.search_all_messages_title));
allMessages.setEmail(context.getString(R.string.search_all_messages_detail));
return allMessages;
}
private static final long serialVersionUID = -4388420303235543976L;
private Flag[] mRequiredFlags = null;
private Flag[] mForbiddenFlags = null;

View File

@ -73,16 +73,11 @@ public abstract class AccountList extends K9ListActivity implements OnItemClickL
List<BaseAccount> accounts = new ArrayList<BaseAccount>();
if (displaySpecialAccounts() && !K9.isHideSpecialAccounts()) {
BaseAccount integratedInboxAccount = new SearchAccount(this, true, null, null);
integratedInboxAccount.setDescription(getString(R.string.integrated_inbox_title));
integratedInboxAccount.setEmail(getString(R.string.integrated_inbox_detail));
BaseAccount unifiedInboxAccount = SearchAccount.createUnifiedInboxAccount(this);
BaseAccount allMessagesAccount = SearchAccount.createAllMessagesAccount(this);
BaseAccount unreadAccount = new SearchAccount(this, false, null, null);
unreadAccount.setDescription(getString(R.string.search_all_messages_title));
unreadAccount.setEmail(getString(R.string.search_all_messages_detail));
accounts.add(integratedInboxAccount);
accounts.add(unreadAccount);
accounts.add(unifiedInboxAccount);
accounts.add(allMessagesAccount);
}
accounts.addAll(Arrays.asList(realAccounts));

View File

@ -23,6 +23,7 @@ import android.app.ProgressDialog;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnMultiChoiceClickListener;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@ -47,12 +48,10 @@ import android.webkit.WebView;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.CheckedTextView;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.ScrollView;
@ -60,7 +59,6 @@ import android.widget.TextView;
import android.widget.Toast;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.CompoundButton.OnCheckedChangeListener;
import com.fsck.k9.Account;
@ -385,16 +383,11 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC
}
/**
* Creates and initializes the special accounts ('Integrated Inbox' and 'All Messages')
* Creates and initializes the special accounts ('Unified Inbox' and 'All Messages')
*/
private void createSpecialAccounts() {
unreadAccount = new SearchAccount(this, false, null, null);
unreadAccount.setDescription(getString(R.string.search_all_messages_title));
unreadAccount.setEmail(getString(R.string.search_all_messages_detail));
integratedInboxAccount = new SearchAccount(this, true, null, null);
integratedInboxAccount.setDescription(getString(R.string.integrated_inbox_title));
integratedInboxAccount.setEmail(getString(R.string.integrated_inbox_detail));
integratedInboxAccount = SearchAccount.createUnifiedInboxAccount(this);
unreadAccount = SearchAccount.createAllMessagesAccount(this);
}
@SuppressWarnings("unchecked")
@ -951,107 +944,128 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC
@Override
public Dialog onCreateDialog(int id) {
// Android recreates our dialogs on configuration changes even when they have been
// dismissed. Make sure we have all information necessary before creating a new dialog.
switch (id) {
case DIALOG_REMOVE_ACCOUNT:
return ConfirmationDialog.create(this, id,
R.string.account_delete_dlg_title,
getString(R.string.account_delete_dlg_instructions_fmt,
mSelectedContextAccount.getDescription()),
R.string.okay_action,
R.string.cancel_action,
new Runnable() {
@Override
public void run() {
if (mSelectedContextAccount instanceof Account) {
Account realAccount = (Account)mSelectedContextAccount;
try {
realAccount.getLocalStore().delete();
} catch (Exception e) {
// Ignore, this may lead to localStores on sd-cards that are
// currently not inserted to be left
}
MessagingController.getInstance(getApplication())
.notifyAccountCancel(Accounts.this, realAccount);
Preferences.getPreferences(Accounts.this).deleteAccount(realAccount);
K9.setServicesEnabled(Accounts.this);
refresh();
}
case DIALOG_REMOVE_ACCOUNT: {
if (mSelectedContextAccount == null) {
return null;
}
});
case DIALOG_CLEAR_ACCOUNT:
return ConfirmationDialog.create(this, id,
R.string.account_clear_dlg_title,
getString(R.string.account_clear_dlg_instructions_fmt,
mSelectedContextAccount.getDescription()),
R.string.okay_action,
R.string.cancel_action,
new Runnable() {
@Override
public void run() {
if (mSelectedContextAccount instanceof Account) {
Account realAccount = (Account)mSelectedContextAccount;
mHandler.workingAccount(realAccount, R.string.clearing_account);
MessagingController.getInstance(getApplication()).clear(realAccount, null);
}
return ConfirmationDialog.create(this, id,
R.string.account_delete_dlg_title,
getString(R.string.account_delete_dlg_instructions_fmt,
mSelectedContextAccount.getDescription()),
R.string.okay_action,
R.string.cancel_action,
new Runnable() {
@Override
public void run() {
if (mSelectedContextAccount instanceof Account) {
Account realAccount = (Account) mSelectedContextAccount;
try {
realAccount.getLocalStore().delete();
} catch (Exception e) {
// Ignore, this may lead to localStores on sd-cards that
// are currently not inserted to be left
}
MessagingController.getInstance(getApplication())
.notifyAccountCancel(Accounts.this, realAccount);
Preferences.getPreferences(Accounts.this)
.deleteAccount(realAccount);
K9.setServicesEnabled(Accounts.this);
refresh();
}
}
});
}
case DIALOG_CLEAR_ACCOUNT: {
if (mSelectedContextAccount == null) {
return null;
}
});
case DIALOG_RECREATE_ACCOUNT:
return ConfirmationDialog.create(this, id,
R.string.account_recreate_dlg_title,
getString(R.string.account_recreate_dlg_instructions_fmt,
mSelectedContextAccount.getDescription()),
R.string.okay_action,
R.string.cancel_action,
new Runnable() {
@Override
public void run() {
if (mSelectedContextAccount instanceof Account) {
Account realAccount = (Account)mSelectedContextAccount;
mHandler.workingAccount(realAccount, R.string.recreating_account);
MessagingController.getInstance(getApplication()).recreate(realAccount, null);
}
return ConfirmationDialog.create(this, id,
R.string.account_clear_dlg_title,
getString(R.string.account_clear_dlg_instructions_fmt,
mSelectedContextAccount.getDescription()),
R.string.okay_action,
R.string.cancel_action,
new Runnable() {
@Override
public void run() {
if (mSelectedContextAccount instanceof Account) {
Account realAccount = (Account) mSelectedContextAccount;
mHandler.workingAccount(realAccount,
R.string.clearing_account);
MessagingController.getInstance(getApplication())
.clear(realAccount, null);
}
}
});
}
case DIALOG_RECREATE_ACCOUNT: {
if (mSelectedContextAccount == null) {
return null;
}
});
case DIALOG_NO_FILE_MANAGER:
return ConfirmationDialog.create(this, id,
R.string.import_dialog_error_title,
getString(R.string.import_dialog_error_message),
R.string.open_market,
R.string.close,
new Runnable() {
@Override
public void run() {
Uri uri = Uri.parse(ANDROID_MARKET_URL);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
}
});
return ConfirmationDialog.create(this, id,
R.string.account_recreate_dlg_title,
getString(R.string.account_recreate_dlg_instructions_fmt,
mSelectedContextAccount.getDescription()),
R.string.okay_action,
R.string.cancel_action,
new Runnable() {
@Override
public void run() {
if (mSelectedContextAccount instanceof Account) {
Account realAccount = (Account) mSelectedContextAccount;
mHandler.workingAccount(realAccount,
R.string.recreating_account);
MessagingController.getInstance(getApplication())
.recreate(realAccount, null);
}
}
});
}
case DIALOG_NO_FILE_MANAGER: {
return ConfirmationDialog.create(this, id,
R.string.import_dialog_error_title,
getString(R.string.import_dialog_error_message),
R.string.open_market,
R.string.close,
new Runnable() {
@Override
public void run() {
Uri uri = Uri.parse(ANDROID_MARKET_URL);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
}
});
}
}
return super.onCreateDialog(id);
}
@Override
public void onPrepareDialog(int id, Dialog d) {
AlertDialog alert = (AlertDialog) d;
switch (id) {
case DIALOG_REMOVE_ACCOUNT:
alert.setMessage(getString(R.string.account_delete_dlg_instructions_fmt,
mSelectedContextAccount.getDescription()));
break;
case DIALOG_CLEAR_ACCOUNT:
alert.setMessage(getString(R.string.account_clear_dlg_instructions_fmt,
mSelectedContextAccount.getDescription()));
break;
case DIALOG_RECREATE_ACCOUNT:
alert.setMessage(getString(R.string.account_recreate_dlg_instructions_fmt,
mSelectedContextAccount.getDescription()));
break;
case DIALOG_NO_FILE_MANAGER:
alert.setMessage(getString(R.string.import_dialog_error_message));
break;
case DIALOG_REMOVE_ACCOUNT: {
alert.setMessage(getString(R.string.account_delete_dlg_instructions_fmt,
mSelectedContextAccount.getDescription()));
break;
}
case DIALOG_CLEAR_ACCOUNT: {
alert.setMessage(getString(R.string.account_clear_dlg_instructions_fmt,
mSelectedContextAccount.getDescription()));
break;
}
case DIALOG_RECREATE_ACCOUNT: {
alert.setMessage(getString(R.string.account_recreate_dlg_instructions_fmt,
mSelectedContextAccount.getDescription()));
break;
}
}
super.onPrepareDialog(id, d);
@ -1495,8 +1509,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC
private static class ImportSelectionDialog implements NonConfigurationInstance {
private ImportContents mImportContents;
private Uri mUri;
private Dialog mDialog;
private ListView mImportSelectionView;
private AlertDialog mDialog;
private SparseBooleanArray mSelection;
@ -1514,8 +1527,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC
public boolean retain() {
if (mDialog != null) {
// Save the selection state of each list item
mSelection = mImportSelectionView.getCheckedItemPositions();
mImportSelectionView = null;
mSelection = mDialog.getListView().getCheckedItemPositions();
mDialog.dismiss();
mDialog = null;
@ -1529,8 +1541,6 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC
}
public void show(final Accounts activity, SparseBooleanArray selection) {
final ListView importSelectionView = new ListView(activity);
mImportSelectionView = importSelectionView;
List<String> contents = new ArrayList<String>();
if (mImportContents.globalSettings) {
@ -1541,23 +1551,15 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC
contents.add(account.name);
}
importSelectionView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
importSelectionView.setAdapter(new ArrayAdapter<String>(activity,
android.R.layout.simple_list_item_checked, contents));
importSelectionView.setOnItemSelectedListener(new OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
CheckedTextView ctv = (CheckedTextView)view;
ctv.setChecked(!ctv.isChecked());
}
@Override
public void onNothingSelected(AdapterView<?> arg0) { /* Do nothing */ }
});
int count = contents.size();
boolean[] checkedItems = new boolean[count];
if (selection != null) {
for (int i = 0, end = contents.size(); i < end; i++) {
importSelectionView.setItemChecked(i, selection.get(i));
for (int i = 0; i < count; i++) {
checkedItems[i] = selection.get(i);
}
} else {
for (int i = 0; i < count; i++) {
checkedItems[i] = true;
}
}
@ -1565,23 +1567,29 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC
//TODO: listview footer: "Select all" / "Select none" buttons?
//TODO: listview footer: "Overwrite existing accounts?" checkbox
OnMultiChoiceClickListener listener = new OnMultiChoiceClickListener() {
@Override
public void onClick(DialogInterface dialog, int which, boolean isChecked) {
((AlertDialog) dialog).getListView().setItemChecked(which, isChecked);
}
};
final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setMultiChoiceItems(contents.toArray(new String[0]), checkedItems, listener);
builder.setTitle(activity.getString(R.string.settings_import_selection));
builder.setView(importSelectionView);
builder.setInverseBackgroundForced(true);
builder.setPositiveButton(R.string.okay_action,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ListAdapter adapter = importSelectionView.getAdapter();
int count = adapter.getCount();
SparseBooleanArray pos = importSelectionView.getCheckedItemPositions();
ListView listView = ((AlertDialog) dialog).getListView();
SparseBooleanArray pos = listView.getCheckedItemPositions();
boolean includeGlobals = mImportContents.globalSettings ? pos.get(0) : false;
List<String> accountUuids = new ArrayList<String>();
int start = mImportContents.globalSettings ? 1 : 0;
for (int i = start; i < count; i++) {
for (int i = start, end = listView.getCount(); i < end; i++) {
if (pos.get(i)) {
accountUuids.add(mImportContents.accounts.get(i-start).uuid);
}

View File

@ -1,243 +0,0 @@
package com.fsck.k9.activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.BaseExpandableListAdapter;
import android.widget.ExpandableListAdapter;
import android.widget.ExpandableListView;
import android.widget.TextView;
import com.fsck.k9.Account;
import com.fsck.k9.Identity;
import com.fsck.k9.K9;
import com.fsck.k9.Preferences;
import com.fsck.k9.R;
import java.util.List;
/**
* Activity displaying list of accounts/identity for user choice
*
* @see K9ExpandableListActivity
*/
public class ChooseAccount extends K9ExpandableListActivity {
private static final Account[] EMPTY_ACCOUNT_ARRAY = new Account[0];
/**
* {@link Intent} extended data name for storing {@link Account#getUuid()
* account UUID}
*/
public static final String EXTRA_ACCOUNT = ChooseAccount.class.getName() + "_account";
/**
* {@link Intent} extended data name for storing serialized {@link Identity}
*/
public static final String EXTRA_IDENTITY = ChooseAccount.class.getName() + "_identity";
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
setContentView(R.layout.choose_account);
final ExpandableListView expandableListView = getExpandableListView();
expandableListView.setItemsCanFocus(false);
final IdentitiesAdapter adapter = createAdapter();
setListAdapter(adapter);
expandableListView.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {
@Override
public boolean onChildClick(ExpandableListView parent, View v, int groupPosition,
int childPosition, long id) {
final Identity identity = (Identity) adapter.getChild(groupPosition, childPosition);
final Account account = (Account) adapter.getGroup(groupPosition);
if (!account.isAvailable(v.getContext())) {
Log.i(K9.LOG_TAG, "Refusing selection of unavailable account");
return true;
}
final Intent intent = new Intent();
intent.putExtra(EXTRA_ACCOUNT, account.getUuid());
intent.putExtra(EXTRA_IDENTITY, identity);
setResult(RESULT_OK, intent);
finish();
return true;
}
});
final Bundle extras = getIntent().getExtras();
final String uuid = extras.getString(EXTRA_ACCOUNT);
if (uuid != null) {
final Account[] accounts = adapter.getAccounts();
final int length = accounts.length;
for (int i = 0; i < length; i++) {
final Account account = accounts[i];
if (uuid.equals(account.getUuid())) {
// setSelectedChild() doesn't seem to obey the
// shouldExpandGroup parameter (2.1), manually expanding
// group
expandableListView.expandGroup(i);
final List<Identity> identities = account.getIdentities();
final Identity identity = (Identity) extras.getSerializable(EXTRA_IDENTITY);
if (identity == null) {
expandableListView.setSelectedChild(i, 0, true);
break;
}
for (int j = 0; j < identities.size(); j++) {
final Identity loopIdentity = identities.get(j);
if (identity.equals(loopIdentity)) {
expandableListView.setSelectedChild(i, j, true);
break;
}
}
break;
}
}
}
}
private IdentitiesAdapter createAdapter() {
return new IdentitiesAdapter(this, getLayoutInflater());
}
/**
* Dynamically provides accounts/identities data for
* {@link ExpandableListView#setAdapter(ExpandableListAdapter)}:
*
* <ul>
* <li>Groups represent {@link Account accounts}</li>
* <li>Children represent {@link Identity identities} of the parent account</li>
* </ul>
*/
public static class IdentitiesAdapter extends BaseExpandableListAdapter {
private Context mContext;
private LayoutInflater mLayoutInflater;
private Account[] mAccounts;
public IdentitiesAdapter(final Context context, final LayoutInflater layoutInflater) {
mContext = context;
mLayoutInflater = layoutInflater;
Preferences prefs = Preferences.getPreferences(mContext);
mAccounts = prefs.getAvailableAccounts().toArray(EMPTY_ACCOUNT_ARRAY);
}
@Override
public Object getChild(int groupPosition, int childPosition) {
return getAccounts()[groupPosition].getIdentity(childPosition);
}
@Override
public long getChildId(int groupPosition, int childPosition) {
return Integer.valueOf(childPosition).longValue();
}
@Override
public int getChildrenCount(int groupPosition) {
return getAccounts()[groupPosition].getIdentities().size();
}
@Override
public Object getGroup(int groupPosition) {
return getAccounts()[groupPosition];
}
@Override
public int getGroupCount() {
return getAccounts().length;
}
@Override
public long getGroupId(int groupPosition) {
return Integer.valueOf(groupPosition).longValue();
}
@Override
public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
ViewGroup parent) {
final View v;
if (convertView == null) {
v = mLayoutInflater.inflate(R.layout.choose_account_item, parent, false);
} else {
v = convertView;
}
final TextView description = (TextView) v.findViewById(R.id.description);
final Account account = getAccounts()[groupPosition];
description.setText(account.getDescription());
description.setTextSize(TypedValue.COMPLEX_UNIT_SP, K9.getFontSizes().getAccountName());
// display unavailable accounts translucent
/*
* 20101030/fiouzy: NullPointerException on null getBackground()
*
if (account.isAvailable(parent.getContext()))
{
description.getBackground().setAlpha(255);
description.getBackground().setAlpha(255);
}
else
{
description.getBackground().setAlpha(127);
description.getBackground().setAlpha(127);
}
*/
v.findViewById(R.id.chip).setBackgroundColor(account.getChipColor());
return v;
}
@Override
public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
View convertView, ViewGroup parent) {
final Account account = getAccounts()[groupPosition];
final Identity identity = account.getIdentity(childPosition);
final View v;
if (convertView == null) {
v = mLayoutInflater.inflate(R.layout.choose_identity_item, parent, false);
} else {
v = convertView;
}
final TextView name = (TextView) v.findViewById(R.id.name);
final TextView description = (TextView) v.findViewById(R.id.description);
name.setTextSize(TypedValue.COMPLEX_UNIT_SP, K9.getFontSizes().getAccountName());
description.setTextSize(TypedValue.COMPLEX_UNIT_SP, K9.getFontSizes().getAccountDescription());
name.setText(identity.getDescription());
description.setText(String.format("%s <%s>", identity.getName(), identity.getEmail()));
v.findViewById(R.id.chip).setBackgroundColor(account.getChipColor());
return v;
}
@Override
public boolean hasStableIds() {
// returning false since accounts/identities are mutable
return false;
}
@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return true;
}
private Account[] getAccounts() {
return mAccounts;
}
}
}

View File

@ -1,6 +1,13 @@
package com.fsck.k9.activity;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
@ -21,8 +28,13 @@ import android.widget.Filter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import com.fsck.k9.*;
//import com.fsck.k9.*;
import com.fsck.k9.Account;
import com.fsck.k9.Account.FolderMode;
import com.fsck.k9.K9;
import com.fsck.k9.Preferences;
import com.fsck.k9.R;
import com.fsck.k9.controller.MessagingController;
import com.fsck.k9.controller.MessagingListener;
import com.fsck.k9.mail.Folder;
@ -31,38 +43,8 @@ import com.fsck.k9.mail.Store;
import com.fsck.k9.mail.store.ImapStore;
import com.fsck.k9.mail.store.Pop3Store;
import com.fsck.k9.mail.store.WebDavStore;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
public class ChooseFolder extends K9ListActivity {
String mFolder;
String mSelectFolder;
Account mAccount;
MessageReference mMessageReference;
ArrayAdapter<String> mAdapter;
private ChooseFolderHandler mHandler = new ChooseFolderHandler();
String heldInbox = null;
boolean hideCurrentFolder = true;
boolean showOptionNone = false;
boolean showDisplayableOnly = false;
/**
* What folders to display.<br/>
* Initialized to whatever is configured
* but can be overridden via {@link #onOptionsItemSelected(MenuItem)}
* while this activity is showing.
*/
private Account.FolderMode mMode;
/**
* Current filter used by our ArrayAdapter.<br/>
* Created on the fly and invalidated if a new
* set of folders is chosen via {@link #onOptionsItemSelected(MenuItem)}
*/
private FolderListFilter<String> myFilter = null;
public static final String EXTRA_ACCOUNT = "com.fsck.k9.ChooseFolder_account";
public static final String EXTRA_CUR_FOLDER = "com.fsck.k9.ChooseFolder_curfolder";
public static final String EXTRA_SEL_FOLDER = "com.fsck.k9.ChooseFolder_selfolder";
@ -72,6 +54,34 @@ public class ChooseFolder extends K9ListActivity {
public static final String EXTRA_SHOW_FOLDER_NONE = "com.fsck.k9.ChooseFolder_showOptionNone";
public static final String EXTRA_SHOW_DISPLAYABLE_ONLY = "com.fsck.k9.ChooseFolder_showDisplayableOnly";
String mFolder;
String mSelectFolder;
Account mAccount;
MessageReference mMessageReference;
ArrayAdapter<String> mAdapter;
private ChooseFolderHandler mHandler = new ChooseFolderHandler();
String mHeldInbox = null;
boolean mHideCurrentFolder = true;
boolean mShowOptionNone = false;
boolean mShowDisplayableOnly = false;
/**
* What folders to display.<br/>
* Initialized to whatever is configured
* but can be overridden via {@link #onOptionsItemSelected(MenuItem)}
* while this activity is showing.
*/
private Account.FolderMode mMode;
/**
* Current filter used by our ArrayAdapter.<br/>
* Created on the fly and invalidated if a new
* set of folders is chosen via {@link #onOptionsItemSelected(MenuItem)}
*/
private FolderListFilter<String> mMyFilter = null;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -87,13 +97,13 @@ public class ChooseFolder extends K9ListActivity {
mFolder = intent.getStringExtra(EXTRA_CUR_FOLDER);
mSelectFolder = intent.getStringExtra(EXTRA_SEL_FOLDER);
if (intent.getStringExtra(EXTRA_SHOW_CURRENT) != null) {
hideCurrentFolder = false;
mHideCurrentFolder = false;
}
if (intent.getStringExtra(EXTRA_SHOW_FOLDER_NONE) != null) {
showOptionNone = true;
mShowOptionNone = true;
}
if (intent.getStringExtra(EXTRA_SHOW_DISPLAYABLE_ONLY) != null) {
showDisplayableOnly = true;
mShowDisplayableOnly = true;
}
if (mFolder == null)
mFolder = "";
@ -112,55 +122,42 @@ public class ChooseFolder extends K9ListActivity {
setListAdapter(mAdapter);
mMode = mAccount.getFolderTargetMode();
onRefresh(false);
this.getListView().setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Intent intent = new Intent();
intent.putExtra(EXTRA_ACCOUNT, mAccount.getUuid());
intent.putExtra(EXTRA_CUR_FOLDER, mFolder);
Intent result = new Intent();
result.putExtra(EXTRA_ACCOUNT, mAccount.getUuid());
result.putExtra(EXTRA_CUR_FOLDER, mFolder);
String destFolderName = (String)((TextView)view).getText();
if (heldInbox != null && getString(R.string.special_mailbox_name_inbox).equals(destFolderName)) {
destFolderName = heldInbox;
if (mHeldInbox != null && getString(R.string.special_mailbox_name_inbox).equals(destFolderName)) {
destFolderName = mHeldInbox;
}
intent.putExtra(EXTRA_NEW_FOLDER, destFolderName);
intent.putExtra(EXTRA_MESSAGE, mMessageReference);
setResult(RESULT_OK, intent);
result.putExtra(EXTRA_NEW_FOLDER, destFolderName);
result.putExtra(EXTRA_MESSAGE, mMessageReference);
setResult(RESULT_OK, result);
finish();
}
});
}
class ChooseFolderHandler extends Handler {
private static final int MSG_PROGRESS = 2;
private static final int MSG_DATA_CHANGED = 3;
private static final int MSG_SET_SELECTED_FOLDER = 4;
private static final int MSG_PROGRESS = 1;
private static final int MSG_SET_SELECTED_FOLDER = 2;
@Override
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case MSG_PROGRESS:
setProgressBarIndeterminateVisibility(msg.arg1 != 0);
break;
case MSG_DATA_CHANGED:
mAdapter.notifyDataSetChanged();
/*
* Only enable the text filter after the list has been
* populated to avoid possible race conditions because our
* FolderListFilter isn't really thread-safe.
*/
getListView().setTextFilterEnabled(true);
break;
case MSG_SET_SELECTED_FOLDER:
getListView().setSelection(msg.arg1);
break;
case MSG_PROGRESS: {
setProgressBarIndeterminateVisibility(msg.arg1 != 0);
break;
}
case MSG_SET_SELECTED_FOLDER: {
getListView().setSelection(msg.arg1);
break;
}
}
}
@ -177,13 +174,10 @@ public class ChooseFolder extends K9ListActivity {
msg.arg1 = position;
sendMessage(msg);
}
public void dataChanged() {
sendEmptyMessage(MSG_DATA_CHANGED);
}
}
@Override public boolean onCreateOptionsMenu(Menu menu) {
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.folder_select_option, menu);
if (mAccount.getStoreUri().startsWith("webdav")) {
@ -192,55 +186,49 @@ public class ChooseFolder extends K9ListActivity {
return true;
}
@Override public boolean onOptionsItemSelected(MenuItem item) {
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.display_1st_class: {
setDisplayMode(FolderMode.FIRST_CLASS);
return true;
}
case R.id.display_1st_and_2nd_class: {
setDisplayMode(FolderMode.FIRST_AND_SECOND_CLASS);
return true;
}
case R.id.display_not_second_class: {
setDisplayMode(FolderMode.NOT_SECOND_CLASS);
return true;
}
case R.id.display_all: {
setDisplayMode(FolderMode.ALL);
return true;
}
case R.id.list_folders: {
onRefresh();
return true;
}
case R.id.filter_folders: {
onEnterFilter();
return true;
}
case R.id.create_folder: {
onCreateFolder();
return true;
}
default:
return super.onOptionsItemSelected(item);
case R.id.display_1st_class: {
setDisplayMode(FolderMode.FIRST_CLASS);
return true;
}
case R.id.display_1st_and_2nd_class: {
setDisplayMode(FolderMode.FIRST_AND_SECOND_CLASS);
return true;
}
case R.id.display_not_second_class: {
setDisplayMode(FolderMode.NOT_SECOND_CLASS);
return true;
}
case R.id.display_all: {
setDisplayMode(FolderMode.ALL);
return true;
}
case R.id.list_folders: {
onRefresh();
return true;
}
case R.id.filter_folders: {
onEnterFilter();
return true;
}
case R.id.create_folder: {
onCreateFolder();
return true;
}
default: {
return super.onOptionsItemSelected(item);
}
}
}
private void onRefresh() {
onRefresh(true);
}
private void onRefresh(final boolean forceRemote) {
MessagingController.getInstance(getApplication()).listFolders(mAccount, forceRemote, mListener);
}
/**
@ -248,44 +236,43 @@ public class ChooseFolder extends K9ListActivity {
* Filter {@link #mAdapter} with the user-input.
*/
private void onEnterFilter() {
final AlertDialog.Builder filterAlert = new AlertDialog.Builder(this);
final AlertDialog.Builder filterAlert = new AlertDialog.Builder(this);
final EditText input = new EditText(this);
input.addTextChangedListener(new TextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
mAdapter.getFilter().filter(input.getText().toString());
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
}
@Override
public void afterTextChanged(Editable s) {
}
});
input.setHint(R.string.folder_list_filter_hint);
filterAlert.setView(input);
final EditText input = new EditText(this);
input.addTextChangedListener(new TextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
mAdapter.getFilter().filter(input.getText().toString());
}
filterAlert.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
String value = input.getText().toString().trim();
mAdapter.getFilter().filter(value);
}
});
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
/* not used */ }
filterAlert.setNegativeButton("Cancel",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
mAdapter.getFilter().filter("");
}
});
@Override
public void afterTextChanged(Editable s) { /* not used */ }
});
input.setHint(R.string.folder_list_filter_hint);
filterAlert.setView(input);
filterAlert.show();
String okay = getString(R.string.okay_action);
filterAlert.setPositiveButton(okay, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int whichButton) {
String value = input.getText().toString().trim();
mAdapter.getFilter().filter(value);
}
});
String cancel = getString(R.string.cancel_action);
filterAlert.setNegativeButton(cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int whichButton) {
mAdapter.getFilter().filter("");
}
});
filterAlert.show();
}
/*
@ -350,8 +337,8 @@ public class ChooseFolder extends K9ListActivity {
private void setDisplayMode(FolderMode aMode) {
mMode = aMode;
// invalidate the current filter as it is working on an inval
if (myFilter != null) {
myFilter.invalidate();
if (mMyFilter != null) {
mMyFilter.invalidate();
}
//re-populate the list
onRefresh(false);
@ -394,34 +381,39 @@ public class ChooseFolder extends K9ListActivity {
String name = folder.getName();
// Inbox needs to be compared case-insensitively
if (hideCurrentFolder && (name.equals(mFolder) ||
(mAccount.getInboxFolderName().equalsIgnoreCase(mFolder) && mAccount.getInboxFolderName().equalsIgnoreCase(name)))) {
if (mHideCurrentFolder && (name.equals(mFolder) || (
mAccount.getInboxFolderName().equalsIgnoreCase(mFolder) &&
mAccount.getInboxFolderName().equalsIgnoreCase(name)))) {
continue;
}
try {
folder.refresh(prefs);
Folder.FolderClass fMode = folder.getDisplayClass();
if ((aMode == Account.FolderMode.FIRST_CLASS && fMode != Folder.FolderClass.FIRST_CLASS)
|| (aMode == Account.FolderMode.FIRST_AND_SECOND_CLASS &&
if ((aMode == Account.FolderMode.FIRST_CLASS &&
fMode != Folder.FolderClass.FIRST_CLASS) || (
aMode == Account.FolderMode.FIRST_AND_SECOND_CLASS &&
fMode != Folder.FolderClass.FIRST_CLASS &&
fMode != Folder.FolderClass.SECOND_CLASS)
|| (aMode == Account.FolderMode.NOT_SECOND_CLASS && fMode == Folder.FolderClass.SECOND_CLASS)) {
fMode != Folder.FolderClass.SECOND_CLASS) || (
aMode == Account.FolderMode.NOT_SECOND_CLASS &&
fMode == Folder.FolderClass.SECOND_CLASS)) {
continue;
}
} catch (MessagingException me) {
Log.e(K9.LOG_TAG, "Couldn't get prefs to check for displayability of folder " + folder.getName(), me);
Log.e(K9.LOG_TAG, "Couldn't get prefs to check for displayability of folder " +
folder.getName(), me);
}
localFolders.add(folder.getName());
}
if (showOptionNone) {
if (mShowOptionNone) {
localFolders.add(K9.FOLDER_NONE);
}
Collections.sort(localFolders, new Comparator<String>() {
@Override
public int compare(String aName, String bName) {
if (K9.FOLDER_NONE.equalsIgnoreCase(aName)) {
return -1;
@ -439,17 +431,22 @@ public class ChooseFolder extends K9ListActivity {
return aName.compareToIgnoreCase(bName);
}
});
mAdapter.setNotifyOnChange(false);
int selectedFolder = -1;
/*
* We're not allowed to change the adapter from a background thread, so we collect the
* folder names and update the adapter in the UI thread (see finally block).
*/
final List<String> folderList = new ArrayList<String>();
try {
mAdapter.clear();
int position = 0;
for (String name : localFolders) {
if (mAccount.getInboxFolderName().equalsIgnoreCase(name)) {
mAdapter.add(getString(R.string.special_mailbox_name_inbox));
heldInbox = name;
} else if (!K9.ERROR_FOLDER_NAME.equals(name) && !account.getOutboxFolderName().equals(name)) {
mAdapter.add(name);
folderList.add(getString(R.string.special_mailbox_name_inbox));
mHeldInbox = name;
} else if (!K9.ERROR_FOLDER_NAME.equals(name) &&
!account.getOutboxFolderName().equals(name)) {
folderList.add(name);
}
if (mSelectFolder != null) {
@ -461,24 +458,35 @@ public class ChooseFolder extends K9ListActivity {
if (name.equals(mSelectFolder)) {
selectedFolder = position;
}
} else if (name.equals(mFolder) ||
(mAccount.getInboxFolderName().equalsIgnoreCase(mFolder) && mAccount.getInboxFolderName().equalsIgnoreCase(name))) {
} else if (name.equals(mFolder) || (
mAccount.getInboxFolderName().equalsIgnoreCase(mFolder) &&
mAccount.getInboxFolderName().equalsIgnoreCase(name))) {
selectedFolder = position;
}
position++;
}
} finally {
mAdapter.setNotifyOnChange(true);
runOnUiThread(new Runnable() {
@Override
public void run() {
// runOnUiThread(
// Now we're in the UI-thread, we can safely change the contents of the adapter.
mAdapter.clear();
for (String folderName: folderList) {
mAdapter.add(folderName);
}
mAdapter.notifyDataSetChanged();
/*
* Only enable the text filter after the list has been
* populated to avoid possible race conditions because our
* FolderListFilter isn't really thread-safe.
*/
getListView().setTextFilterEnabled(true);
}
});
}
mHandler.dataChanged();
if (selectedFolder != -1) {
mHandler.setSelectedFolder(selectedFolder);
}

View File

@ -32,11 +32,20 @@ class InsertableHtmlContent implements Serializable {
}
public void setHeaderInsertionPoint(int headerInsertionPoint) {
this.headerInsertionPoint = headerInsertionPoint;
if (headerInsertionPoint < 0 || headerInsertionPoint > quotedContent.length()) {
this.headerInsertionPoint = 0;
} else {
this.headerInsertionPoint = headerInsertionPoint;
}
}
public void setFooterInsertionPoint(int footerInsertionPoint) {
this.footerInsertionPoint = footerInsertionPoint;
int len = quotedContent.length();
if (footerInsertionPoint < 0 || footerInsertionPoint > len) {
this.footerInsertionPoint = len;
} else {
this.footerInsertionPoint = footerInsertionPoint;
}
}
/**

View File

@ -23,14 +23,8 @@ public class K9Activity extends Activity {
@Override
public void onCreate(Bundle icicle) {
onCreate(icicle, true);
}
public void onCreate(Bundle icicle, boolean useTheme) {
setLanguage(this, K9.getK9Language());
if (useTheme) {
setTheme(K9.getK9Theme());
}
setTheme(K9.getK9ThemeResourceId());
super.onCreate(icicle);
setupFormats();

View File

@ -1,18 +0,0 @@
package com.fsck.k9.activity;
import android.app.ExpandableListActivity;
import android.os.Bundle;
import com.fsck.k9.K9;
/**
* @see ExpandableListActivity
*/
public class K9ExpandableListActivity extends ExpandableListActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(K9.getK9Theme());
super.onCreate(savedInstanceState);
}
}

View File

@ -13,7 +13,7 @@ public class K9ListActivity extends ListActivity {
@Override
public void onCreate(Bundle icicle) {
K9Activity.setLanguage(this, K9.getK9Language());
setTheme(K9.getK9Theme());
setTheme(K9.getK9ThemeResourceId());
super.onCreate(icicle);
setupFormats();
}

View File

@ -15,15 +15,18 @@ import com.fsck.k9.mail.*;
import com.fsck.k9.view.MessageWebView;
import org.apache.james.mime4j.codec.EncoderUtil;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.app.Dialog;
import android.content.ContentResolver;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Parcelable;
@ -31,14 +34,17 @@ import android.provider.OpenableColumns;
import android.text.util.Rfc822Tokenizer;
import android.util.Log;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnFocusChangeListener;
import android.view.ViewGroup;
import android.view.Window;
import android.webkit.WebView;
import android.widget.AutoCompleteTextView.Validator;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
@ -84,6 +90,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
private static final int DIALOG_REFUSE_TO_SAVE_DRAFT_MARKED_ENCRYPTED = 2;
private static final int DIALOG_CONTINUE_WITHOUT_PUBLIC_KEY = 3;
private static final int DIALOG_CONFIRM_DISCARD_ON_BACK = 4;
private static final int DIALOG_CHOOSE_IDENTITY = 5;
private static final long INVALID_DRAFT_ID = MessagingController.INVALID_MESSAGE_ID;
@ -128,12 +135,11 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
private static final int MSG_DISCARDED_DRAFT = 6;
private static final int ACTIVITY_REQUEST_PICK_ATTACHMENT = 1;
private static final int ACTIVITY_CHOOSE_IDENTITY = 2;
private static final int ACTIVITY_CHOOSE_ACCOUNT = 3;
private static final int CONTACT_PICKER_TO = 4;
private static final int CONTACT_PICKER_CC = 5;
private static final int CONTACT_PICKER_BCC = 6;
private static final int CONTACT_PICKER_TO = 2;
private static final int CONTACT_PICKER_CC = 3;
private static final int CONTACT_PICKER_BCC = 4;
private static final Account[] EMPTY_ACCOUNT_ARRAY = new Account[0];
/**
* Regular expression to remove the first localized "Re:" prefix in subjects.
@ -186,7 +192,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
private QuotedTextMode mQuotedTextMode = QuotedTextMode.NONE;
private TextView mFromView;
private Button mChooseIdentityButton;
private LinearLayout mCcWrapper;
private LinearLayout mBccWrapper;
private MultiAutoCompleteTextView mToView;
@ -410,7 +416,14 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
mAddressAdapter = EmailAddressAdapter.getInstance(this);
mAddressValidator = new EmailAddressValidator();
mFromView = (TextView) findViewById(R.id.from);
mChooseIdentityButton = (Button) findViewById(R.id.identity);
mChooseIdentityButton.setOnClickListener(this);
if (mAccount.getIdentities().size() == 1 &&
Preferences.getPreferences(this).getAvailableAccounts().size() == 1) {
mChooseIdentityButton.setVisibility(View.GONE);
}
mToView = (MultiAutoCompleteTextView) findViewById(R.id.to);
mCcView = (MultiAutoCompleteTextView) findViewById(R.id.cc);
mBccView = (MultiAutoCompleteTextView) findViewById(R.id.bcc);
@ -548,8 +561,6 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
mQuotedTextEdit.setOnClickListener(this);
mQuotedTextDelete.setOnClickListener(this);
mFromView.setVisibility(View.GONE);
mToView.setAdapter(mAddressAdapter);
mToView.setTokenizer(new Rfc822Tokenizer());
mToView.setValidator(mAddressValidator);
@ -607,8 +618,9 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
mReadReceipt = mAccount.isMessageReadReceiptAlways();
mQuoteStyle = mAccount.getQuoteStyle();
updateFrom();
if (!mSourceMessageProcessed) {
updateFrom();
updateSignature();
if (ACTION_REPLY.equals(action) ||
@ -941,7 +953,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
if (mQuotedTextMode != QuotedTextMode.NONE && mMessageFormat == MessageFormat.HTML) {
mQuotedHtmlContent = (InsertableHtmlContent) savedInstanceState.getSerializable(STATE_KEY_HTML_QUOTE);
if (mQuotedHtmlContent != null && mQuotedHtmlContent.getQuotedContent() != null) {
mQuotedHTML.loadDataWithBaseURL("http://", mQuotedHtmlContent.getQuotedContent(), "text/html", "utf-8", null);
mQuotedHTML.setText(mQuotedHtmlContent.getQuotedContent(), "text/html");
}
}
mDraftId = savedInstanceState.getLong(STATE_KEY_DRAFT_ID);
@ -1776,12 +1788,6 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
addAttachment(data.getData());
mDraftNeedsSaving = true;
break;
case ACTIVITY_CHOOSE_IDENTITY:
onIdentityChosen(data);
break;
case ACTIVITY_CHOOSE_ACCOUNT:
onAccountChosen(data);
break;
case CONTACT_PICKER_TO:
case CONTACT_PICKER_CC:
case CONTACT_PICKER_BCC:
@ -1811,15 +1817,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
startActivityForResult(mContacts.contactPickerIntent(), resultId);
}
private void onAccountChosen(final Intent intent) {
final Bundle extras = intent.getExtras();
final String uuid = extras.getString(ChooseAccount.EXTRA_ACCOUNT);
final Identity identity = (Identity) extras.getSerializable(ChooseAccount.EXTRA_IDENTITY);
final Account account = Preferences.getPreferences(this).getAccount(uuid);
private void onAccountChosen(Account account, Identity identity) {
if (!mAccount.equals(account)) {
if (K9.DEBUG) {
Log.v(K9.LOG_TAG, "Switching account from " + mAccount + " to " + account);
@ -1863,11 +1861,6 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
switchToIdentity(identity);
}
private void onIdentityChosen(Intent intent) {
Bundle bundle = intent.getExtras();
switchToIdentity((Identity) bundle.getSerializable(ChooseIdentity.EXTRA_IDENTITY));
}
private void switchToIdentity(Identity identity) {
mIdentity = identity;
mIdentityChanged = true;
@ -1877,10 +1870,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
}
private void updateFrom() {
if (mIdentityChanged) {
mFromView.setVisibility(View.VISIBLE);
}
mFromView.setText(getString(R.string.message_view_from_format, mIdentity.getName(), mIdentity.getEmail()));
mChooseIdentityButton.setText(mIdentity.getEmail());
}
private void updateSignature() {
@ -1923,6 +1913,9 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
MessagingController.getInstance(getApplication()).loadMessageForView(account, folderName, sourceMessageUid, null);
}
break;
case R.id.identity:
showDialog(DIALOG_CHOOSE_IDENTITY);
break;
}
}
@ -1989,9 +1982,6 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
case R.id.add_attachment_video:
onAddAttachment2("video/*");
break;
case R.id.choose_identity:
onChooseIdentity();
break;
case R.id.read_receipt:
onReadReceipt();
default:
@ -2000,25 +1990,6 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
return true;
}
private void onChooseIdentity() {
// keep things simple: trigger account choice only if there are more
// than 1 account
mIgnoreOnPause = true;
if (Preferences.getPreferences(this).getAvailableAccounts().size() > 1) {
final Intent intent = new Intent(this, ChooseAccount.class);
intent.putExtra(ChooseAccount.EXTRA_ACCOUNT, mAccount.getUuid());
intent.putExtra(ChooseAccount.EXTRA_IDENTITY, mIdentity);
startActivityForResult(intent, ACTIVITY_CHOOSE_ACCOUNT);
} else if (mAccount.getIdentities().size() > 1) {
Intent intent = new Intent(this, ChooseIdentity.class);
intent.putExtra(ChooseIdentity.EXTRA_ACCOUNT, mAccount.getUuid());
startActivityForResult(intent, ACTIVITY_CHOOSE_IDENTITY);
} else {
Toast.makeText(this, getString(R.string.no_identities),
Toast.LENGTH_LONG).show();
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
@ -2148,6 +2119,21 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
}
})
.create();
case DIALOG_CHOOSE_IDENTITY:
Context context = new ContextWrapper(this);
context.setTheme(K9.getK9ThemeResourceId(K9.THEME_LIGHT));
Builder builder = new AlertDialog.Builder(context);
builder.setTitle(R.string.send_as);
final IdentityAdapter adapter = new IdentityAdapter(context, getLayoutInflater());
builder.setAdapter(adapter, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
IdentityContainer container = (IdentityContainer) adapter.getItem(which);
onAccountChosen(container.account, container.identity);
}
});
return builder.create();
}
return super.onCreateDialog(id);
}
@ -2289,8 +2275,15 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
}
if (ACTION_REPLY_ALL.equals(action)) {
if (message.getReplyTo().length > 0) {
for (Address address : message.getFrom()) {
if (!mAccount.isAnIdentity(address)) {
addAddress(mToView, address);
}
}
}
for (Address address : message.getRecipients(RecipientType.TO)) {
if (!mAccount.isAnIdentity(address)) {
if (!mAccount.isAnIdentity(address) && !Utility.arrayContains(replyToAddresses, address)) {
addAddress(mToView, address);
}
@ -2395,7 +2388,15 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
if (k9identity.containsKey(IdentityField.ORIGINAL_MESSAGE)) {
mMessageReference = null;
try {
mMessageReference = new MessageReference(k9identity.get(IdentityField.ORIGINAL_MESSAGE));
String originalMessage = k9identity.get(IdentityField.ORIGINAL_MESSAGE);
MessageReference messageReference = new MessageReference(originalMessage);
// Check if this is a valid account in our database
Preferences prefs = Preferences.getPreferences(getApplicationContext());
Account account = prefs.getAccount(messageReference.accountUuid);
if (account != null) {
mMessageReference = messageReference;
}
} catch (MessagingException e) {
Log.e(K9.LOG_TAG, "Could not decode message reference in identity.", e);
}
@ -2420,19 +2421,19 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
updateFrom();
Integer bodyLength = k9identity.get(IdentityField.LENGTH) != null
? Integer.parseInt(k9identity.get(IdentityField.LENGTH))
? Integer.valueOf(k9identity.get(IdentityField.LENGTH))
: 0;
Integer bodyOffset = k9identity.get(IdentityField.OFFSET) != null
? Integer.parseInt(k9identity.get(IdentityField.OFFSET))
? Integer.valueOf(k9identity.get(IdentityField.OFFSET))
: 0;
Integer bodyFooterOffset = k9identity.get(IdentityField.FOOTER_OFFSET) != null
? Integer.parseInt(k9identity.get(IdentityField.FOOTER_OFFSET))
? Integer.valueOf(k9identity.get(IdentityField.FOOTER_OFFSET))
: null;
Integer bodyPlainLength = k9identity.get(IdentityField.PLAIN_LENGTH) != null
? Integer.parseInt(k9identity.get(IdentityField.PLAIN_LENGTH))
? Integer.valueOf(k9identity.get(IdentityField.PLAIN_LENGTH))
: null;
Integer bodyPlainOffset = k9identity.get(IdentityField.PLAIN_OFFSET) != null
? Integer.parseInt(k9identity.get(IdentityField.PLAIN_OFFSET))
? Integer.valueOf(k9identity.get(IdentityField.PLAIN_OFFSET))
: null;
mQuoteStyle = k9identity.get(IdentityField.QUOTE_STYLE) != null
? QuoteStyle.valueOf(k9identity.get(IdentityField.QUOTE_STYLE))
@ -2482,7 +2483,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
} else {
mQuotedHtmlContent.setFooterInsertionPoint(bodyOffset);
}
mQuotedHTML.loadDataWithBaseURL("http://", mQuotedHtmlContent.getQuotedContent(), "text/html", "utf-8", null);
mQuotedHTML.setText(mQuotedHtmlContent.getQuotedContent(), "text/html");
}
}
if (bodyPlainOffset != null && bodyPlainLength != null) {
@ -2665,7 +2666,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
// Add the HTML reply header to the top of the content.
mQuotedHtmlContent = quoteOriginalHtmlMessage(mSourceMessage, content, mQuoteStyle);
// Load the message with the reply header.
mQuotedHTML.loadDataWithBaseURL("http://", mQuotedHtmlContent.getQuotedContent(), "text/html", "utf-8", null);
mQuotedHTML.setText(mQuotedHtmlContent.getQuotedContent(), "text/html");
mQuotedText.setText(quoteOriginalTextMessage(mSourceMessage,
getBodyTextFromMessage(mSourceMessage, MessageFormat.TEXT), mQuoteStyle));
@ -3177,4 +3178,139 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
return insertable;
}
/**
* Used to store an {@link Identity} instance together with the {@link Account} it belongs to.
*
* @see IdentityAdapter
*/
static class IdentityContainer {
public final Identity identity;
public final Account account;
IdentityContainer(Identity identity, Account account) {
this.identity = identity;
this.account = account;
}
}
/**
* Adapter for the <em>Choose identity</em> list view.
*
* <p>
* Account names are displayed as section headers, identities as selectable list items.
* </p>
*/
static class IdentityAdapter extends BaseAdapter {
private LayoutInflater mLayoutInflater;
private List<Object> mItems;
private FontSizes mFontSizes;
public IdentityAdapter(Context context, LayoutInflater layoutInflater) {
mLayoutInflater = layoutInflater;
mFontSizes = K9.getFontSizes();
List<Object> items = new ArrayList<Object>();
Preferences prefs = Preferences.getPreferences(context.getApplicationContext());
Account[] accounts = prefs.getAvailableAccounts().toArray(EMPTY_ACCOUNT_ARRAY);
for (Account account : accounts) {
items.add(account);
List<Identity> identities = account.getIdentities();
for (Identity identity : identities) {
items.add(new IdentityContainer(identity, account));
}
}
mItems = items;
}
@Override
public int getCount() {
return mItems.size();
}
@Override
public int getViewTypeCount() {
return 2;
}
@Override
public int getItemViewType(int position) {
return (mItems.get(position) instanceof Account) ? 0 : 1;
}
@Override
public boolean isEnabled(int position) {
return (mItems.get(position) instanceof IdentityContainer);
}
@Override
public Object getItem(int position) {
return mItems.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public boolean hasStableIds() {
return false;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Object item = mItems.get(position);
View view = null;
if (item instanceof Account) {
if (convertView != null && convertView.getTag() instanceof AccountHolder) {
view = convertView;
} else {
view = mLayoutInflater.inflate(R.layout.choose_account_item, parent, false);
AccountHolder holder = new AccountHolder();
holder.name = (TextView) view.findViewById(R.id.name);
holder.chip = view.findViewById(R.id.chip);
view.setTag(holder);
}
Account account = (Account) item;
AccountHolder holder = (AccountHolder) view.getTag();
holder.name.setText(account.getDescription());
holder.chip.setBackgroundColor(account.getChipColor());
} else if (item instanceof IdentityContainer) {
if (convertView != null && convertView.getTag() instanceof IdentityHolder) {
view = convertView;
} else {
view = mLayoutInflater.inflate(R.layout.choose_identity_item, parent, false);
IdentityHolder holder = new IdentityHolder();
holder.name = (TextView) view.findViewById(R.id.name);
holder.description = (TextView) view.findViewById(R.id.description);
view.setTag(holder);
}
IdentityContainer identityContainer = (IdentityContainer) item;
Identity identity = identityContainer.identity;
IdentityHolder holder = (IdentityHolder) view.getTag();
holder.name.setText(identity.getDescription());
holder.description.setText(getIdentityDescription(identity));
}
return view;
}
static class AccountHolder {
public TextView name;
public View chip;
}
static class IdentityHolder {
public TextView name;
public TextView description;
}
}
private static String getIdentityDescription(Identity identity) {
return String.format("%s <%s>", identity.getName(), identity.getEmail());
}
}

View File

@ -7,6 +7,7 @@ import com.fsck.k9.mail.store.LocalStore.LocalMessage;
public class MessageInfoHolder {
public String date;
public Date compareDate;
public Date compareArrival;
public String compareSubject;
public CharSequence sender;
public String senderAddress;

View File

@ -185,6 +185,15 @@ public class MessageList
}
public static class ArrivalComparator implements Comparator<MessageInfoHolder> {
@Override
public int compare(MessageInfoHolder object1, MessageInfoHolder object2) {
return object1.compareArrival.compareTo(object2.compareArrival);
}
}
public static class SubjectComparator implements Comparator<MessageInfoHolder> {
@Override
@ -234,6 +243,7 @@ public class MessageList
final Map<SORT_TYPE, Comparator<MessageInfoHolder>> map = new EnumMap<SORT_TYPE, Comparator<MessageInfoHolder>>(SORT_TYPE.class);
map.put(SORT_TYPE.SORT_ATTACHMENT, new AttachmentComparator());
map.put(SORT_TYPE.SORT_DATE, new DateComparator());
map.put(SORT_TYPE.SORT_ARRIVAL, new ArrivalComparator());
map.put(SORT_TYPE.SORT_FLAGGED, new FlaggedComparator());
map.put(SORT_TYPE.SORT_SENDER, new SenderComparator());
map.put(SORT_TYPE.SORT_SUBJECT, new SubjectComparator());
@ -460,7 +470,7 @@ public class MessageList
{
// add the date comparator if not already specified
if (sortType != SORT_TYPE.SORT_DATE) {
if (sortType != SORT_TYPE.SORT_DATE && sortType != SORT_TYPE.SORT_ARRIVAL) {
final Comparator<MessageInfoHolder> comparator = SORT_COMPARATORS.get(SORT_TYPE.SORT_DATE);
if (sortDateAscending) {
chain.add(comparator);
@ -1447,6 +1457,10 @@ public class MessageList
changeSort(SORT_TYPE.SORT_DATE);
return true;
}
case R.id.set_sort_arrival: {
changeSort(SORT_TYPE.SORT_ARRIVAL);
return true;
}
case R.id.set_sort_subject: {
changeSort(SORT_TYPE.SORT_SUBJECT);
return true;

View File

@ -287,7 +287,7 @@ public class MessageView extends K9Activity implements OnClickListener {
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle, false);
super.onCreate(icicle);
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.message_view);
@ -731,17 +731,16 @@ public class MessageView extends K9Activity implements OnClickListener {
mPrevious.requestFocus();
}
private void onMarkAsUnread() {
private void onToggleRead() {
if (mMessage != null) {
mController.setFlag(mAccount, mMessage.getFolder().getName(),
new Message[] { mMessage }, Flag.SEEN, false);
new Message[] { mMessage }, Flag.SEEN, !mMessage.isSet(Flag.SEEN));
mMessageView.setHeaders(mMessage, mAccount);
String subject = mMessage.getSubject();
setTitle(subject);
}
}
private void onDownloadRemainder() {
if (mMessage.isSet(Flag.X_DOWNLOADED_FULL)) {
return;
@ -808,7 +807,7 @@ public class MessageView extends K9Activity implements OnClickListener {
onSendAlternate();
break;
case R.id.mark_as_unread:
onMarkAsUnread();
onToggleRead();
break;
case R.id.flag:
onFlag();
@ -923,6 +922,12 @@ public class MessageView extends K9Activity implements OnClickListener {
additionalHeadersItem.setTitle(mMessageView.additionalHeadersVisible() ?
R.string.hide_full_header_action : R.string.show_full_header_action);
}
if (mMessage != null) {
int actionTitle = mMessage.isSet(Flag.SEEN) ?
R.string.mark_as_unread_action : R.string.mark_as_read_action;
menu.findItem(R.id.mark_as_unread).setTitle(actionTitle);
}
}
return super.onPrepareOptionsMenu(menu);
}

View File

@ -48,6 +48,7 @@ public class AccountSettings extends K9PreferenceActivity {
private static final String PREFERENCE_SCREEN_PUSH_ADVANCED = "push_advanced";
private static final String PREFERENCE_DESCRIPTION = "account_description";
private static final String PREFERENCE_MARK_MESSAGE_AS_READ_ON_VIEW = "mark_message_as_read_on_view";
private static final String PREFERENCE_COMPOSITION = "composition";
private static final String PREFERENCE_MANAGE_IDENTITIES = "manage_identities";
private static final String PREFERENCE_FREQUENCY = "account_check_frequency";
@ -95,9 +96,7 @@ public class AccountSettings extends K9PreferenceActivity {
private static final String PREFERENCE_CRYPTO_APP = "crypto_app";
private static final String PREFERENCE_CRYPTO_AUTO_SIGNATURE = "crypto_auto_signature";
private static final String PREFERENCE_CRYPTO_AUTO_ENCRYPT = "crypto_auto_encrypt";
private static final String PREFERENCE_LOCAL_STORAGE_PROVIDER = "local_storage_provider";
private static final String PREFERENCE_CATEGORY_FOLDERS = "folders";
private static final String PREFERENCE_ARCHIVE_FOLDER = "archive_folder";
private static final String PREFERENCE_DRAFTS_FOLDER = "drafts_folder";
@ -116,6 +115,7 @@ public class AccountSettings extends K9PreferenceActivity {
private PreferenceScreen mComposingScreen;
private EditTextPreference mAccountDescription;
private CheckBoxPreference mMarkMessageAsReadOnView;
private ListPreference mCheckFrequency;
private ListPreference mDisplayCount;
private ListPreference mMessageAge;
@ -160,10 +160,7 @@ public class AccountSettings extends K9PreferenceActivity {
private ListPreference mCryptoApp;
private CheckBoxPreference mCryptoAutoSignature;
private CheckBoxPreference mCryptoAutoEncrypt;
private ListPreference mLocalStorageProvider;
private ListPreference mArchiveFolder;
private ListPreference mDraftsFolder;
private ListPreference mSentFolder;
@ -207,6 +204,9 @@ public class AccountSettings extends K9PreferenceActivity {
}
});
mMarkMessageAsReadOnView = (CheckBoxPreference) findPreference(PREFERENCE_MARK_MESSAGE_AS_READ_ON_VIEW);
mMarkMessageAsReadOnView.setChecked(mAccount.isMarkMessageAsReadOnView());
mMessageFormat = (ListPreference) findPreference(PREFERENCE_MESSAGE_FORMAT);
mMessageFormat.setValue(mAccount.getMessageFormat().name());
mMessageFormat.setSummary(mMessageFormat.getEntry());
@ -681,6 +681,7 @@ public class AccountSettings extends K9PreferenceActivity {
}
mAccount.setDescription(mAccountDescription.getText());
mAccount.setMarkMessageAsReadOnView(mMarkMessageAsReadOnView.isChecked());
mAccount.setNotifyNewMail(mAccountNotify.isChecked());
mAccount.setNotifySelfNewMail(mAccountNotifySelf.isChecked());
mAccount.setShowOngoing(mAccountNotifySync.isChecked());

View File

@ -150,7 +150,7 @@ public class Prefs extends K9PreferenceActivity {
entryVector.toArray(EMPTY_CHAR_SEQUENCE_ARRAY),
entryValueVector.toArray(EMPTY_CHAR_SEQUENCE_ARRAY));
final String theme = (K9.getK9Theme() == android.R.style.Theme) ? "dark" : "light";
final String theme = (K9.getK9Theme() == K9.THEME_DARK) ? "dark" : "light";
mTheme = setupListPreference(PREFERENCE_THEME, theme);
findPreference(PREFERENCE_FONT_SIZE).setOnPreferenceClickListener(
@ -353,7 +353,7 @@ public class Prefs extends K9PreferenceActivity {
SharedPreferences preferences = Preferences.getPreferences(this).getPreferences();
K9.setK9Language(mLanguage.getValue());
K9.setK9Theme(mTheme.getValue().equals("dark") ? android.R.style.Theme : android.R.style.Theme_Light);
K9.setK9Theme(mTheme.getValue().equals("dark") ? K9.THEME_DARK : K9.THEME_LIGHT);
K9.setAnimations(mAnimations.isChecked());
K9.setGesturesEnabled(mGestures.isChecked());
K9.setCompactLayouts(compactLayouts.isChecked());

View File

@ -117,6 +117,7 @@ public class MessagingController implements Runnable {
*/
private static final String PENDING_COMMAND_MOVE_OR_COPY_BULK = "com.fsck.k9.MessagingController.moveOrCopyBulk";
private static final String PENDING_COMMAND_MOVE_OR_COPY_BULK_NEW = "com.fsck.k9.MessagingController.moveOrCopyBulkNew";
private static final String PENDING_COMMAND_EMPTY_TRASH = "com.fsck.k9.MessagingController.emptyTrash";
private static final String PENDING_COMMAND_SET_FLAG_BULK = "com.fsck.k9.MessagingController.setFlagBulk";
private static final String PENDING_COMMAND_APPEND = "com.fsck.k9.MessagingController.append";
@ -144,6 +145,7 @@ public class MessagingController implements Runnable {
public enum SORT_TYPE {
SORT_DATE(R.string.sort_earliest_first, R.string.sort_latest_first, false),
SORT_ARRIVAL(R.string.sort_earliest_first, R.string.sort_latest_first, false),
SORT_SUBJECT(R.string.sort_subject_alpha, R.string.sort_subject_re_alpha, true),
SORT_SENDER(R.string.sort_sender_alpha, R.string.sort_sender_re_alpha, true),
SORT_UNREAD(R.string.sort_unread_first, R.string.sort_unread_last, true),
@ -446,7 +448,7 @@ public class MessagingController implements Runnable {
}
}
private void doRefreshRemote(final Account account, MessagingListener listener) {
private void doRefreshRemote(final Account account, final MessagingListener listener) {
put("doRefreshRemote", listener, new Runnable() {
@Override
public void run() {
@ -506,14 +508,14 @@ public class MessagingController implements Runnable {
localFolders = localStore.getPersonalNamespaces(false);
Folder[] folderArray = localFolders.toArray(EMPTY_FOLDER_ARRAY);
for (MessagingListener l : getListeners()) {
for (MessagingListener l : getListeners(listener)) {
l.listFolders(account, folderArray);
}
for (MessagingListener l : getListeners()) {
for (MessagingListener l : getListeners(listener)) {
l.listFoldersFinished(account);
}
} catch (Exception e) {
for (MessagingListener l : getListeners()) {
for (MessagingListener l : getListeners(listener)) {
l.listFoldersFailed(account, "");
}
addErrorMessage(account, null, e);
@ -645,7 +647,7 @@ public class MessagingController implements Runnable {
Log.i(K9.LOG_TAG, "searchLocalMessages ("
+ "accountUuids=" + Utility.combine(accountUuids, ',')
+ ", folderNames = " + Utility.combine(folderNames, ',')
+ ", messages.size() = " + (messages != null ? messages.length : null)
+ ", messages.size() = " + (messages != null ? messages.length : -1)
+ ", query = " + query
+ ", integrate = " + integrate
+ ", requiredFlags = " + Utility.combine(requiredFlags, ',')
@ -1934,6 +1936,8 @@ public class MessagingController implements Runnable {
} else if (PENDING_COMMAND_MARK_ALL_AS_READ.equals(command.command)) {
processPendingMarkAllAsRead(command, account);
} else if (PENDING_COMMAND_MOVE_OR_COPY_BULK.equals(command.command)) {
processPendingMoveOrCopyOld2(command, account);
} else if (PENDING_COMMAND_MOVE_OR_COPY_BULK_NEW.equals(command.command)) {
processPendingMoveOrCopy(command, account);
} else if (PENDING_COMMAND_EMPTY_TRASH.equals(command.command)) {
processPendingEmptyTrash(command, account);
@ -2118,10 +2122,30 @@ public class MessagingController implements Runnable {
closeFolder(localFolder);
}
}
private void queueMoveOrCopy(Account account, String srcFolder, String destFolder, boolean isCopy, String uids[]) {
// was:
private void queueMoveOrCopyOld(Account account, String srcFolder, String destFolder, boolean isCopy, String uids[]) {
if (account.getErrorFolderName().equals(srcFolder)) {
return;
}
PendingCommand command = new PendingCommand();
command.command = PENDING_COMMAND_MOVE_OR_COPY_BULK;
int length = 3 + uids.length;
command.arguments = new String[length];
command.arguments[0] = srcFolder;
command.arguments[1] = destFolder;
command.arguments[2] = Boolean.toString(isCopy);
System.arraycopy(uids, 0, command.arguments, 3, uids.length);
queuePendingCommand(account, command);
}
// ASH probably don't need this
private void queueMoveOrCopyAsh(Account account, String srcFolder, String destFolder, boolean isCopy, String uids[]) {
if (account.getErrorFolderName().equals(srcFolder)) {
return;
}
final ArrayList<String> remoteUids = new ArrayList<String>();
for (String uid : uids) {
// ignore unsynced messages
@ -2132,8 +2156,9 @@ public class MessagingController implements Runnable {
if (remoteUids.size() == 0) {
return;
}
PendingCommand command = new PendingCommand();
command.command = PENDING_COMMAND_MOVE_OR_COPY_BULK;
command.command = PENDING_COMMAND_MOVE_OR_COPY_BULK_NEW;
int length = 3 + remoteUids.size();
command.arguments = new String[length];
@ -2144,6 +2169,78 @@ public class MessagingController implements Runnable {
remoteUids.size());
queuePendingCommand(account, command);
}
private void queueMoveOrCopy(Account account, String srcFolder, String destFolder, boolean isCopy, String uids[]) {
if (account.getErrorFolderName().equals(srcFolder)) {
return;
}
PendingCommand command = new PendingCommand();
command.command = PENDING_COMMAND_MOVE_OR_COPY_BULK_NEW;
int length = 4 + uids.length;
command.arguments = new String[length];
command.arguments[0] = srcFolder;
command.arguments[1] = destFolder;
command.arguments[2] = Boolean.toString(isCopy);
command.arguments[3] = Boolean.toString(false);
System.arraycopy(uids, 0, command.arguments, 4, uids.length);
queuePendingCommand(account, command);
}
private void queueMoveOrCopy(Account account, String srcFolder, String destFolder, boolean isCopy, String uids[], Map<String, String> uidMap) {
if (uidMap == null || uidMap.isEmpty()) {
queueMoveOrCopy(account, srcFolder, destFolder, isCopy, uids);
} else {
if (account.getErrorFolderName().equals(srcFolder)) {
return;
}
PendingCommand command = new PendingCommand();
command.command = PENDING_COMMAND_MOVE_OR_COPY_BULK_NEW;
int length = 4 + uidMap.keySet().size() + uidMap.values().size();
command.arguments = new String[length];
command.arguments[0] = srcFolder;
command.arguments[1] = destFolder;
command.arguments[2] = Boolean.toString(isCopy);
command.arguments[3] = Boolean.toString(true);
System.arraycopy(uidMap.keySet().toArray(), 0, command.arguments, 4, uidMap.keySet().size());
System.arraycopy(uidMap.values().toArray(), 0, command.arguments, 4 + uidMap.keySet().size(), uidMap.values().size());
queuePendingCommand(account, command);
}
}
/**
* Convert pending command to new format and call
* {@link #processPendingMoveOrCopy(PendingCommand, Account)}.
*
* <p>
* TODO: This method is obsolete and is only for transition from K-9 4.0 to K-9 4.2
* Eventually, it should be removed.
* </p>
*
* @param command
* Pending move/copy command in old format.
* @param account
* The account the pending command belongs to.
*
* @throws MessagingException
* In case of an error.
*/
private void processPendingMoveOrCopyOld2(PendingCommand command, Account account)
throws MessagingException {
PendingCommand newCommand = new PendingCommand();
int len = command.arguments.length;
newCommand.command = PENDING_COMMAND_MOVE_OR_COPY_BULK_NEW;
newCommand.arguments = new String[len + 1];
newCommand.arguments[0] = command.arguments[0];
newCommand.arguments[1] = command.arguments[1];
newCommand.arguments[2] = command.arguments[2];
newCommand.arguments[3] = Boolean.toString(false);
System.arraycopy(command.arguments, 3, newCommand.arguments, 4, len - 3);
processPendingMoveOrCopy(newCommand, account);
}
/**
* Process a pending move or copy message command.
*
@ -2155,6 +2252,7 @@ public class MessagingController implements Runnable {
throws MessagingException {
Folder remoteSrcFolder = null;
Folder remoteDestFolder = null;
LocalFolder localDestFolder = null;
try {
String srcFolder = command.arguments[0];
if (account.getErrorFolderName().equals(srcFolder)) {
@ -2162,14 +2260,42 @@ public class MessagingController implements Runnable {
}
String destFolder = command.arguments[1];
String isCopyS = command.arguments[2];
String hasNewUidsS = command.arguments[3];
boolean hasNewUids = false;
if (hasNewUidsS != null) {
hasNewUids = Boolean.parseBoolean(hasNewUidsS);
}
Store remoteStore = account.getRemoteStore();
remoteSrcFolder = remoteStore.getFolder(srcFolder);
Store localStore = account.getLocalStore();
localDestFolder = (LocalFolder) localStore.getFolder(destFolder);
List<Message> messages = new ArrayList<Message>();
for (int i = 3; i < command.arguments.length; i++) {
String uid = command.arguments[i];
if (!uid.startsWith(K9.LOCAL_UID_PREFIX)) {
messages.add(remoteSrcFolder.getMessage(uid));
/*
* We split up the localUidMap into two parts while sending the command, here we assemble it back.
*/
Map<String, String> localUidMap = new HashMap<String, String>();
if (hasNewUids) {
int offset = (command.arguments.length - 4) / 2;
for (int i = 4; i < 4 + offset; i++) {
localUidMap.put(command.arguments[i], command.arguments[i + offset]);
String uid = command.arguments[i];
if (!uid.startsWith(K9.LOCAL_UID_PREFIX)) {
messages.add(remoteSrcFolder.getMessage(uid));
}
}
} else {
for (int i = 4; i < command.arguments.length; i++) {
String uid = command.arguments[i];
if (!uid.startsWith(K9.LOCAL_UID_PREFIX)) {
messages.add(remoteSrcFolder.getMessage(uid));
}
}
}
@ -2190,6 +2316,8 @@ public class MessagingController implements Runnable {
Log.d(K9.LOG_TAG, "processingPendingMoveOrCopy: source folder = " + srcFolder
+ ", " + messages.size() + " messages, destination folder = " + destFolder + ", isCopy = " + isCopy);
Map <String, String> remoteUidMap = null;
if (!isCopy && destFolder.equals(account.getTrashFolderName())) {
if (K9.DEBUG)
Log.d(K9.LOG_TAG, "processingPendingMoveOrCopy doing special case for deleting message");
@ -2206,9 +2334,9 @@ public class MessagingController implements Runnable {
remoteDestFolder = remoteStore.getFolder(destFolder);
if (isCopy) {
remoteSrcFolder.copyMessages(messages.toArray(EMPTY_MESSAGE_ARRAY), remoteDestFolder);
remoteUidMap = remoteSrcFolder.copyMessages(messages.toArray(EMPTY_MESSAGE_ARRAY), remoteDestFolder);
} else {
remoteSrcFolder.moveMessages(messages.toArray(EMPTY_MESSAGE_ARRAY), remoteDestFolder);
remoteUidMap = remoteSrcFolder.moveMessages(messages.toArray(EMPTY_MESSAGE_ARRAY), remoteDestFolder);
}
updateUids(account, destFolder);
}
@ -2219,12 +2347,34 @@ public class MessagingController implements Runnable {
remoteSrcFolder.expunge();
}
/*
* This next part is used to bring the local UIDs of the local destination folder
* upto speed with the remote UIDs of remote destionation folder.
*/
if (!localUidMap.isEmpty() && remoteUidMap != null && !remoteUidMap.isEmpty()) {
Set<String> remoteSrcUids = remoteUidMap.keySet();
Iterator<String> remoteSrcUidsIterator = remoteSrcUids.iterator();
while (remoteSrcUidsIterator.hasNext()) {
String remoteSrcUid = remoteSrcUidsIterator.next();
String localDestUid = localUidMap.get(remoteSrcUid);
String newUid = remoteUidMap.get(remoteSrcUid);
Message localDestMessage = localDestFolder.getMessage(localDestUid);
if (localDestMessage != null) {
localDestMessage.setUid(newUid);
localDestFolder.changeUid((LocalMessage)localDestMessage);
for (MessagingListener l : getListeners()) {
l.messageUidChanged(account, destFolder, localDestUid, newUid);
}
}
}
}
} finally {
closeFolder(remoteSrcFolder);
closeFolder(remoteDestFolder);
}
}
/**
@ -2862,7 +3012,7 @@ public class MessagingController implements Runnable {
|| message.getId() == 0) {
throw new IllegalArgumentException("Message not found: folder=" + folder + ", uid=" + uid);
}
if (!message.isSet(Flag.SEEN)) {
if (account.isMarkMessageAsReadOnView() && !message.isSet(Flag.SEEN)) {
message.setFlag(Flag.SEEN, true);
setFlag(new Message[] { message }, Flag.SEEN, true);
}
@ -3410,6 +3560,7 @@ public class MessagingController implements Runnable {
private void moveOrCopyMessageSynchronous(final Account account, final String srcFolder, final Message[] inMessages,
final String destFolder, final boolean isCopy, MessagingListener listener) {
try {
Map<String, String> uidMap = new HashMap<String, String>();
LocalStore localStore = account.getLocalStore();
Store remoteStore = account.getRemoteStore();
LocalFolder localSrcFolder = localStore.getFolder(srcFolder);
@ -3502,7 +3653,7 @@ public class MessagingController implements Runnable {
fp.add(FetchProfile.Item.ENVELOPE);
fp.add(FetchProfile.Item.BODY);
localSrcFolder.fetch(messages, fp, null);
localSrcFolder.copyMessages(messages, localDestFolder);
uidMap = localSrcFolder.copyMessages(messages, localDestFolder);
if (unreadCountAffected) {
// If this copy operation changes the unread count in the destination
@ -3576,7 +3727,7 @@ public class MessagingController implements Runnable {
}
if (!isCopy) {
localSrcFolder.moveMessages(localMessages, localDestFolder);
uidMap = localSrcFolder.moveMessages(localMessages, localDestFolder);
for (Map.Entry<String, Message> entry : origUidMap.entrySet()) {
String origUid = entry.getKey();
Message message = entry.getValue();
@ -3621,6 +3772,7 @@ public class MessagingController implements Runnable {
l.folderStatusChanged(account, destFolder, unreadMessageCount);
}
}
//ASH queueMoveOrCopy(account, srcFolder, destFolder, isCopy, origUidMap.keySet().toArray(EMPTY_STRING_ARRAY), uidMap);
}
processPendingCommands(account);
@ -3703,6 +3855,7 @@ public class MessagingController implements Runnable {
}
LocalStore localStore = account.getLocalStore();
localFolder = localStore.getFolder(folder);
Map<String, String> uidMap = null;
if (folder.equals(account.getTrashFolderName()) || K9.FOLDER_NONE.equals(account.getTrashFolderName())) {
if (K9.DEBUG)
Log.d(K9.LOG_TAG, "Deleting messages in trash folder or trash set to -None-, not copying");
@ -3774,8 +3927,7 @@ public class MessagingController implements Runnable {
return;
}
}
localFolder.moveMessages(messages, localTrashFolder);
uidMap = localFolder.moveMessages(messages, localTrashFolder);
}
}
@ -3816,8 +3968,7 @@ public class MessagingController implements Runnable {
queueSetFlag(account, folder, Boolean.toString(true), Flag.SEEN.toString(), uids);
processPendingCommands(account);
} else {
if (K9.DEBUG)
Log.d(K9.LOG_TAG, "Delete policy " + account.getDeletePolicy() + " prevents delete from server");
queueMoveOrCopy(account, folder, account.getTrashFolderName(), false, uids, uidMap);
}
}
for (String uid : uids) {
@ -4404,7 +4555,13 @@ public class MessagingController implements Runnable {
NotificationSetting n = account.getNotificationSetting();
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()) ? Integer.valueOf(n.getLedColor()) : null,
K9.NOTIFICATION_LED_BLINK_SLOW,
ringAndVibrate);
notifMgr.notify(account.getAccountNumber(), notif);
}

View File

@ -0,0 +1,63 @@
package com.fsck.k9.helper;
import android.content.Context;
import android.os.Build;
/**
* Helper class to access the system clipboard
*
* @see ClipboardManagerApi1
* @see ClipboardManagerApi11
*/
public abstract class ClipboardManager {
/**
* Instance of the API-specific class that interfaces with the clipboard API.
*/
private static ClipboardManager sInstance = null;
/**
* Get API-specific instance of the {@code ClipboardManager} class
*
* @param context
* A {@link Context} instance.
*
* @return Appropriate {@link ClipboardManager} instance for this device.
*/
public static ClipboardManager getInstance(Context context) {
Context appContext = context.getApplicationContext();
if (sInstance == null) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
sInstance = new ClipboardManagerApi1(appContext);
} else {
sInstance = new ClipboardManagerApi11(appContext);
}
}
return sInstance;
}
protected Context mContext;
/**
* Constructor
*
* @param context
* A {@link Context} instance.
*/
protected ClipboardManager(Context context) {
mContext = context;
}
/**
* Copy a text string to the system clipboard
*
* @param label
* User-visible label for the content.
* @param text
* The actual text to be copied to the clipboard.
*/
public abstract void setText(String label, String text);
}

View File

@ -0,0 +1,22 @@
package com.fsck.k9.helper;
import android.content.Context;
import android.text.ClipboardManager;
/**
* Access the system clipboard using the now deprecated {@link ClipboardManager}
*/
@SuppressWarnings("deprecation")
public class ClipboardManagerApi1 extends com.fsck.k9.helper.ClipboardManager {
public ClipboardManagerApi1(Context context) {
super(context);
}
@Override
public void setText(String label, String text) {
ClipboardManager clipboardManager =
(ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE);
clipboardManager.setText(text);
}
}

View File

@ -0,0 +1,23 @@
package com.fsck.k9.helper;
import android.content.ClipData;
import android.content.Context;
import android.content.ClipboardManager;
/**
* Access the system clipboard using the new {@link ClipboardManager} introduced with API 11
*/
public class ClipboardManagerApi11 extends com.fsck.k9.helper.ClipboardManager {
public ClipboardManagerApi11(Context context) {
super(context);
}
@Override
public void setText(String label, String text) {
ClipboardManager clipboardManager =
(ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText(label, text);
clipboardManager.setPrimaryClip(clip);
}
}

View File

@ -104,6 +104,14 @@ public abstract class Contacts {
*/
public abstract void createContact(Address email);
/**
* Start the activity to add a phone number to an existing contact or add a new one.
*
* @param phoneNumber
* The phone number to add to a contact, or to use when creating a new contact.
*/
public abstract void addPhoneContact(String phoneNumber);
/**
* Check whether the provided email address belongs to one of the contacts.
*

View File

@ -9,6 +9,8 @@ import android.provider.ContactsContract;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Intents;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.Intents.Insert;
import com.fsck.k9.mail.Address;
import com.fsck.k9.K9;
@ -85,6 +87,15 @@ public class ContactsSdk5 extends com.fsck.k9.helper.Contacts {
mContext.startActivity(contactIntent);
}
@Override
public void addPhoneContact(final String phoneNumber) {
Intent addIntent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
addIntent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE);
addIntent.putExtra(Insert.PHONE, Uri.decode(phoneNumber));
addIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(addIntent);
}
@Override
public boolean isInContacts(final String emailAddress) {
boolean result = false;

View File

@ -49,6 +49,7 @@ public class MessageHelper {
try {
LocalMessage message = (LocalMessage) m;
target.message = message;
target.compareArrival = message.getInternalDate();
target.compareDate = message.getSentDate();
if (target.compareDate == null) {
target.compareDate = message.getInternalDate();

View File

@ -19,6 +19,29 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Utility {
/**
* Regular expression that represents characters we won't allow in file names.
*
* <p>
* Allowed are:
* <ul>
* <li>word characters (letters, digits, and underscores): {@code \w}</li>
* <li>spaces: {@code " "}</li>
* <li>special characters: {@code !}, {@code #}, {@code $}, {@code %}, {@code &}, {@code '},
* {@code (}, {@code )}, {@code -}, {@code @}, {@code ^}, {@code `}, <code>&#123;</code>,
* <code>&#125;</code>, {@code ~}, {@code .}, {@code ,}</li>
* </ul></p>
*
* @see #sanitizeFilename(String)
*/
private static final String INVALID_CHARACTERS = "[^\\w !#$%&'()\\-@\\^`{}~.,]+";
/**
* Invalid characters in a file name are replaced by this character.
*
* @see #sanitizeFilename(String)
*/
private static final String REPLACEMENT_CHARACTER = "_";
// \u00A0 (non-breaking space) happens to be used by French MUA
@ -605,4 +628,16 @@ public class Utility {
cursor.close();
}
}
/**
* Replace characters we don't allow in file names with a replacement character.
*
* @param filename
* The original file name.
*
* @return The sanitized file name containing only allowed characters.
*/
public static String sanitizeFilename(String filename) {
return filename.replaceAll(INVALID_CHARACTERS, REPLACEMENT_CHARACTER);
}
}

View File

@ -1,6 +1,7 @@
package com.fsck.k9.mail;
import java.util.Date;
import java.util.Map;
import android.util.Log;
import com.fsck.k9.Account;
@ -98,11 +99,15 @@ public abstract class Folder {
public abstract Message[] getMessages(String[] uids, MessageRetrievalListener listener)
throws MessagingException;
public abstract void appendMessages(Message[] messages) throws MessagingException;
public abstract Map<String, String> appendMessages(Message[] messages) throws MessagingException;
public void copyMessages(Message[] msgs, Folder folder) throws MessagingException {}
public Map<String, String> copyMessages(Message[] msgs, Folder folder) throws MessagingException {
return null;
}
public void moveMessages(Message[] msgs, Folder folder) throws MessagingException {}
public Map<String, String> moveMessages(Message[] msgs, Folder folder) throws MessagingException {
return null;
}
public void delete(Message[] msgs, String trashFolderName) throws MessagingException {}

View File

@ -953,16 +953,20 @@ public class MimeUtility {
}
header = header.replaceAll("\r|\n", "");
String[] parts = header.split(";");
if (name == null) {
if (name == null && parts.length > 0) {
return parts[0];
}
for (String part : parts) {
if (part.trim().toLowerCase(Locale.US).startsWith(name.toLowerCase(Locale.US))) {
String parameter = part.split("=", 2)[1].trim();
if (parameter.startsWith("\"") && parameter.endsWith("\"")) {
return parameter.substring(1, parameter.length() - 1);
} else {
return parameter;
String[] partParts = part.split("=", 2);
if (partParts.length == 2) {
String parameter = partParts[1].trim();
int len = parameter.length();
if (len >= 2 && parameter.startsWith("\"") && parameter.endsWith("\"")) {
return parameter.substring(1, len - 1);
} else {
return parameter;
}
}
}
}
@ -2178,8 +2182,11 @@ public class MimeUtility {
}
private static String getJisVariantFromAddress(String address) {
if (address == null)
return null;
if (isInDomain(address, "docomo.ne.jp") || isInDomain(address, "dwmail.jp") ||
isInDomain(address, "pdx.ne.jp") || isInDomain(address, "willcom.com"))
isInDomain(address, "pdx.ne.jp") || isInDomain(address, "willcom.com") ||
isInDomain(address, "emnet.ne.jp") || isInDomain(address, "emobile.ne.jp"))
return "docomo";
else if (isInDomain(address, "softbank.ne.jp") || isInDomain(address, "vodafone.ne.jp") ||
isInDomain(address, "disney.ne.jp") || isInDomain(address, "vertuclub.ne.jp"))
@ -3256,4 +3263,80 @@ public class MimeUtility {
return charset;
}
public static ViewableContainer extractPartsFromDraft(Message message)
throws MessagingException {
Body body = message.getBody();
if (message.isMimeType("multipart/mixed") && body instanceof MimeMultipart) {
MimeMultipart multipart = (MimeMultipart) body;
ViewableContainer container;
int count = multipart.getCount();
if (count >= 1) {
// The first part is either a text/plain or a multipart/alternative
BodyPart firstPart = multipart.getBodyPart(0);
container = extractTextual(firstPart);
// The rest should be attachments
for (int i = 1; i < count; i++) {
BodyPart bodyPart = multipart.getBodyPart(i);
container.attachments.add(bodyPart);
}
} else {
container = new ViewableContainer("", "", new ArrayList<Part>());
}
return container;
}
return extractTextual(message);
}
private static ViewableContainer extractTextual(Part part) throws MessagingException {
String text = "";
String html = "";
List<Part> attachments = new ArrayList<Part>();
Body firstBody = part.getBody();
if (part.isMimeType("text/plain")) {
String bodyText = getTextFromPart(part);
if (bodyText != null) {
text = fixDraftTextBody(bodyText);
}
} else if (part.isMimeType("multipart/alternative") &&
firstBody instanceof MimeMultipart) {
MimeMultipart multipart = (MimeMultipart) firstBody;
for (int i = 0, count = multipart.getCount(); i < count; i++) {
BodyPart bodyPart = multipart.getBodyPart(i);
String bodyText = getTextFromPart(bodyPart);
if (bodyText != null) {
if (text.length() == 0 && bodyPart.isMimeType("text/plain")) {
text = fixDraftTextBody(bodyText);
} else if (html.length() == 0 && bodyPart.isMimeType("text/html")) {
html = fixDraftTextBody(bodyText);
}
}
}
}
return new ViewableContainer(text, html, attachments);
}
/**
* Fix line endings of text bodies in draft messages.
*
* <p>
* We create drafts with LF line endings. The values in the identity header are based on that.
* So we replace CRLF with LF when loading messages (from the server).
* </p>
*
* @param text
* The body text with CRLF line endings
*
* @return The text with LF line endings
*/
private static String fixDraftTextBody(String text) {
return text.replace("\r\n", "\n");
}
}

View File

@ -409,7 +409,7 @@ public class ImapResponseParser {
public Object getKeyedValue(Object key) {
for (int i = 0, count = size(); i < count; i++) {
for (int i = 0, count = size() - 1; i < count; i++) {
if (equalsIgnoreCase(get(i), key)) {
return get(i + 1);
}
@ -434,7 +434,7 @@ public class ImapResponseParser {
return false;
}
for (int i = 0, count = size(); i < count; i++) {
for (int i = 0, count = size() - 1; i < count; i++) {
if (equalsIgnoreCase(key, get(i))) {
return true;
}
@ -443,7 +443,7 @@ public class ImapResponseParser {
}
public int getKeyIndex(Object key) {
for (int i = 0, count = size(); i < count; i++) {
for (int i = 0, count = size() - 1; i < count; i++) {
if (equalsIgnoreCase(key, get(i))) {
return i;
}

View File

@ -61,6 +61,7 @@ import com.fsck.k9.Account;
import com.fsck.k9.K9;
import com.fsck.k9.R;
import com.fsck.k9.controller.MessageRetrievalListener;
import com.fsck.k9.helper.StringUtils;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.helper.power.TracingPowerManager;
import com.fsck.k9.helper.power.TracingPowerManager.TracingWakeLock;
@ -89,6 +90,7 @@ import com.fsck.k9.mail.internet.MimeMultipart;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.store.ImapResponseParser.ImapList;
import com.fsck.k9.mail.store.ImapResponseParser.ImapResponse;
import com.fsck.k9.mail.store.imap.ImapUtility;
import com.fsck.k9.mail.transport.imap.ImapSettings;
import com.jcraft.jzlib.JZlib;
import com.jcraft.jzlib.ZOutputStream;
@ -1153,24 +1155,57 @@ public class ImapStore extends Store {
}
}
/**
* Copies the given messages to the specified folder.
*
* <p>
* <strong>Note:</strong>
* Only the UIDs of the given {@link Message} instances are used. It is assumed that all
* UIDs represent valid messages in this folder.
* </p>
*
* @param messages
* The messages to copy to the specfied folder.
* @param folder
* The name of the target folder.
*
* @return The mapping of original message UIDs to the new server UIDs.
*/
@Override
public void copyMessages(Message[] messages, Folder folder) throws MessagingException {
public Map<String, String> copyMessages(Message[] messages, Folder folder)
throws MessagingException {
if (!(folder instanceof ImapFolder)) {
throw new MessagingException("ImapFolder.copyMessages passed non-ImapFolder");
}
if (messages.length == 0)
return;
if (messages.length == 0) {
return null;
}
ImapFolder iFolder = (ImapFolder)folder;
checkOpen();
String[] uids = new String[messages.length];
for (int i = 0, count = messages.length; i < count; i++) {
uids[i] = messages[i].getUid();
}
try {
String remoteDestName = encodeString(encodeFolderName(iFolder.getPrefixedName()));
//TODO: Split this into multiple commands if the command exceeds a certain length.
mConnection.sendCommand(String.format("UID COPY %s %s",
Utility.combine(uids, ','),
remoteDestName), false);
ImapResponse response;
do {
response = mConnection.readResponse();
handleUntaggedResponse(response);
} while (response.mTag == null);
Map<String, String> uidMap = null;
// ASH maybe this shouldn't be here at all.
if (!exists(remoteDestName)) {
/*
* If the remote folder doesn't exist we try to create it.
@ -1180,26 +1215,70 @@ public class ImapStore extends Store {
iFolder.create();
}
if (exists(remoteDestName)) {
executeSimpleCommand(String.format("UID COPY %s %s",
Utility.combine(uids, ','),
remoteDestName));
} else {
throw new MessagingException("IMAPMessage.copyMessages: remote destination folder " + folder.getName()
+ " does not exist and could not be created for " + getLogId()
, true);
if (response.size() > 1) {
/*
* If the server supports UIDPLUS, then along with the COPY response it will
* return an COPYUID response code, e.g.
*
* 24 OK [COPYUID 38505 304,319:320 3956:3958] Success
*
* COPYUID is followed by UIDVALIDITY, the set of UIDs of copied messages from
* the source folder and the set of corresponding UIDs assigned to them in the
* destination folder.
*
* We can use the new UIDs included in this response to update our records.
*/
Object responseList = response.get(1);
if (responseList instanceof ImapList) {
final ImapList copyList = (ImapList) responseList;
if (copyList.size() >= 4 && copyList.getString(0).equals("COPYUID")) {
List<String> srcUids = ImapUtility.getImapSequenceValues(
copyList.getString(2));
List<String> destUids = ImapUtility.getImapSequenceValues(
copyList.getString(3));
if (srcUids != null && destUids != null) {
if (srcUids.size() == destUids.size()) {
Iterator<String> srcUidsIterator = srcUids.iterator();
Iterator<String> destUidsIterator = destUids.iterator();
uidMap = new HashMap<String, String>();
while (srcUidsIterator.hasNext() &&
destUidsIterator.hasNext()) {
String srcUid = srcUidsIterator.next();
String destUid = destUidsIterator.next();
uidMap.put(srcUid, destUid);
}
} else {
if (K9.DEBUG) {
Log.v(K9.LOG_TAG, "Parse error: size of source UIDs " +
"list is not the same as size of destination " +
"UIDs list.");
}
}
} else {
if (K9.DEBUG) {
Log.v(K9.LOG_TAG, "Parsing of the sequence set failed.");
}
}
}
}
}
return uidMap;
} catch (IOException ioe) {
throw ioExceptionHandler(mConnection, ioe);
}
}
@Override
public void moveMessages(Message[] messages, Folder folder) throws MessagingException {
public Map<String, String> moveMessages(Message[] messages, Folder folder) throws MessagingException {
if (messages.length == 0)
return;
copyMessages(messages, folder);
return null;
Map<String, String> uidMap = copyMessages(messages, folder);
setFlags(messages, new Flag[] { Flag.DELETED }, true);
return uidMap;
}
@Override
@ -1675,13 +1754,16 @@ public class ImapStore extends Store {
if (fetchList.containsKey("BODY")) {
int index = fetchList.getKeyIndex("BODY") + 2;
result = fetchList.getObject(index);
int size = fetchList.size();
if (index < size) {
result = fetchList.getObject(index);
// Check if there's an origin octet
if (result instanceof String) {
String originOctet = (String)result;
if (originOctet.startsWith("<")) {
result = fetchList.getObject(index + 1);
// Check if there's an origin octet
if (result instanceof String) {
String originOctet = (String) result;
if (originOctet.startsWith("<") && (index + 1) < size) {
result = fetchList.getObject(index + 1);
}
}
}
}
@ -1938,20 +2020,30 @@ public class ImapStore extends Store {
}
/**
* Appends the given messages to the selected folder. This implementation also determines
* the new UID of the given message on the IMAP server and sets the Message's UID to the
* new server UID.
* Appends the given messages to the selected folder.
*
* <p>
* This implementation also determines the new UIDs of the given messages on the IMAP
* server and changes the messages' UIDs to the new server UIDs.
* </p>
*
* @param messages
* The messages to append to the folder.
*
* @return The mapping of original message UIDs to the new server UIDs.
*/
@Override
public void appendMessages(Message[] messages) throws MessagingException {
public Map<String, String> appendMessages(Message[] messages) throws MessagingException {
checkOpen();
try {
Map<String, String> uidMap = new HashMap<String, String>();
for (Message message : messages) {
mConnection.sendCommand(
String.format("APPEND %s (%s) {%d}",
encodeString(encodeFolderName(getPrefixedName())),
combineFlags(message.getFlags()),
message.calculateSize()), false);
ImapResponse response;
do {
response = mConnection.readResponse();
@ -1965,16 +2057,54 @@ public class ImapStore extends Store {
}
} while (response.mTag == null);
String newUid = getUidFromMessageId(message);
if (K9.DEBUG)
Log.d(K9.LOG_TAG, "Got UID " + newUid + " for message for " + getLogId());
if (response.size() > 1) {
/*
* If the server supports UIDPLUS, then along with the APPEND response it
* will return an APPENDUID response code, e.g.
*
* 11 OK [APPENDUID 2 238268] APPEND completed
*
* We can use the UID included in this response to update our records.
*/
Object responseList = response.get(1);
if (newUid != null) {
message.setUid(newUid);
if (responseList instanceof ImapList) {
ImapList appendList = (ImapList) responseList;
if (appendList.size() >= 3 &&
appendList.getString(0).equals("APPENDUID")) {
String newUid = appendList.getString(2);
if (!StringUtils.isNullOrEmpty(newUid)) {
message.setUid(newUid);
uidMap.put(message.getUid(), newUid);
continue;
}
}
}
}
/*
* This part is executed in case the server does not support UIDPLUS or does
* not implement the APPENDUID response code.
*/
String newUid = getUidFromMessageId(message);
if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "Got UID " + newUid + " for message for " + getLogId());
}
if (!StringUtils.isNullOrEmpty(newUid)) {
uidMap.put(message.getUid(), newUid);
message.setUid(newUid);
}
}
/*
* We need uidMap to be null if new UIDs are not available to maintain consistency
* with the behavior of other similar methods (copyMessages, moveMessages) which
* return null.
*/
return (uidMap.size() == 0) ? null : uidMap;
} catch (IOException ioe) {
throw ioExceptionHandler(mConnection, ioe);
}
@ -2693,13 +2823,8 @@ public class ImapStore extends Store {
private static final long serialVersionUID = 3725007182205882394L;
String mAlertText;
public ImapException(String message, String alertText, Throwable throwable) {
super(message, throwable);
this.mAlertText = alertText;
}
public ImapException(String message, String alertText) {
super(message);
super(message, true);
this.mAlertText = alertText;
}

View File

@ -2109,21 +2109,23 @@ Log.v("ASH", mAccount.getDescription() + ":" + name + " is " + (localOnly == 1 ?
}
@Override
public void copyMessages(Message[] msgs, Folder folder) throws MessagingException {
public Map<String, String> copyMessages(Message[] msgs, Folder folder) throws MessagingException {
if (!(folder instanceof LocalFolder)) {
throw new MessagingException("copyMessages called with incorrect Folder");
}
((LocalFolder) folder).appendMessages(msgs, true);
return ((LocalFolder) folder).appendMessages(msgs, true);
}
@Override
public void moveMessages(final Message[] msgs, final Folder destFolder) throws MessagingException {
public Map<String, String> moveMessages(final Message[] msgs, final Folder destFolder) throws MessagingException {
if (!(destFolder instanceof LocalFolder)) {
throw new MessagingException("moveMessages called with non-LocalFolder");
}
final LocalFolder lDestFolder = (LocalFolder)destFolder;
final Map<String, String> uidMap = new HashMap<String, String>();
try {
database.execute(false, new DbCallback<Void>() {
@Override
@ -2149,7 +2151,10 @@ Log.v("ASH", mAccount.getDescription() + ":" + name + " is " + (localOnly == 1 ?
Log.d(K9.LOG_TAG, "Updating folder_id to " + lDestFolder.getId() + " for message with UID "
+ message.getUid() + ", id " + lMessage.getId() + " currently in folder " + getName());
message.setUid(K9.LOCAL_UID_PREFIX + UUID.randomUUID().toString());
String newUid = K9.LOCAL_UID_PREFIX + UUID.randomUUID().toString();
message.setUid(newUid);
uidMap.put(oldUID, newUid);
db.execSQL("UPDATE messages " + "SET folder_id = ?, uid = ? " + "WHERE id = ?", new Object[] {
lDestFolder.getId(),
@ -2157,6 +2162,11 @@ Log.v("ASH", mAccount.getDescription() + ":" + name + " is " + (localOnly == 1 ?
lMessage.getId()
});
/*
* Add a placeholder message so we won't download the original
* message again if we synchronize before the remote move is
* complete.
*/
LocalMessage placeHolder = new LocalMessage(oldUID, LocalFolder.this);
placeHolder.setFlagInternal(Flag.DELETED, true);
placeHolder.setFlagInternal(Flag.SEEN, true);
@ -2168,6 +2178,7 @@ Log.v("ASH", mAccount.getDescription() + ":" + name + " is " + (localOnly == 1 ?
return null;
}
});
return uidMap;
} catch (WrappedException e) {
throw(MessagingException) e.getCause();
}
@ -2213,8 +2224,8 @@ Log.v("ASH", mAccount.getDescription() + ":" + name + " is " + (localOnly == 1 ?
* message, retrieve the appropriate local message instance first (if it already exists).
*/
@Override
public void appendMessages(Message[] messages) throws MessagingException {
appendMessages(messages, false);
public Map<String, String> appendMessages(Message[] messages) throws MessagingException {
return appendMessages(messages, false);
}
public void expunge() throws MessagingException {
@ -2261,10 +2272,12 @@ Log.v("ASH", mAccount.getDescription() + ":" + name + " is " + (localOnly == 1 ?
* message, retrieve the appropriate local message instance first (if it already exists).
* @param messages
* @param copy
* @return Map<String, String> uidMap of srcUids -> destUids
*/
private void appendMessages(final Message[] messages, final boolean copy) throws MessagingException {
private Map<String, String> appendMessages(final Message[] messages, final boolean copy) throws MessagingException {
open(OpenMode.READ_WRITE);
try {
final Map<String, String> uidMap = new HashMap<String, String>();
database.execute(true, new DbCallback<Void>() {
@Override
public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
@ -2277,11 +2290,26 @@ Log.v("ASH", mAccount.getDescription() + ":" + name + " is " + (localOnly == 1 ?
long oldMessageId = -1;
String uid = message.getUid();
if (uid == null || copy) {
uid = K9.LOCAL_UID_PREFIX + UUID.randomUUID().toString();
if (!copy) {
message.setUid(uid);
/*
* Create a new message in the database
*/
String randomLocalUid = K9.LOCAL_UID_PREFIX +
UUID.randomUUID().toString();
if (copy) {
// Save mapping: source UID -> target UID
uidMap.put(uid, randomLocalUid);
} else {
// Modify the Message instance to reference the new UID
message.setUid(randomLocalUid);
}
// The message will be saved with the newly generated UID
uid = randomLocalUid;
} else {
/*
* Replace an existing message in the database
*/
LocalMessage oldMessage = (LocalMessage) getMessage(uid);
if (oldMessage != null) {
@ -2298,12 +2326,29 @@ Log.v("ASH", mAccount.getDescription() + ":" + name + " is " + (localOnly == 1 ?
deleteAttachments(message.getUid());
}
ViewableContainer container =
MimeUtility.extractTextAndAttachments(mApplication, message);
boolean isDraft = (message.getHeader(K9.IDENTITY_HEADER) != null);
List<Part> attachments = container.attachments;
String text = container.text;
String html = HtmlConverter.convertEmoji2Img(container.html);
List<Part> attachments;
String text;
String html;
if (isDraft) {
// Don't modify the text/plain or text/html part of our own
// draft messages because this will cause the values stored in
// the identity header to be wrong.
ViewableContainer container =
MimeUtility.extractPartsFromDraft(message);
text = container.text;
html = container.html;
attachments = container.attachments;
} else {
ViewableContainer container =
MimeUtility.extractTextAndAttachments(mApplication, message);
attachments = container.attachments;
text = container.text;
html = HtmlConverter.convertEmoji2Img(container.html);
}
String preview = calculateContentPreview(text);
@ -2361,6 +2406,7 @@ Log.v("ASH", mAccount.getDescription() + ":" + name + " is " + (localOnly == 1 ?
return null;
}
});
return uidMap;
} catch (WrappedException e) {
throw(MessagingException) e.getCause();
}

View File

@ -24,6 +24,7 @@ import java.util.LinkedList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
public class Pop3Store extends Store {
public static final String STORE_TYPE = "POP3";
@ -889,7 +890,8 @@ public class Pop3Store extends Store {
}
@Override
public void appendMessages(Message[] messages) throws MessagingException {
public Map<String, String> appendMessages(Message[] messages) throws MessagingException {
return null;
}
@Override

View File

@ -1339,13 +1339,15 @@ public class WebDavStore extends Store {
}
@Override
public void copyMessages(Message[] messages, Folder folder) throws MessagingException {
public Map<String, String> copyMessages(Message[] messages, Folder folder) throws MessagingException {
moveOrCopyMessages(messages, folder.getName(), false);
return null;
}
@Override
public void moveMessages(Message[] messages, Folder folder) throws MessagingException {
public Map<String, String> moveMessages(Message[] messages, Folder folder) throws MessagingException {
moveOrCopyMessages(messages, folder.getName(), true);
return null;
}
@Override
@ -1920,8 +1922,9 @@ public class WebDavStore extends Store {
}
@Override
public void appendMessages(Message[] messages) throws MessagingException {
public Map<String, String> appendMessages(Message[] messages) throws MessagingException {
appendWebDavMessages(messages);
return null;
}
public Message[] appendWebDavMessages(Message[] messages) throws MessagingException {

View File

@ -0,0 +1,113 @@
/*
* Copyright (C) 2012 The K-9 Dog Walkers
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fsck.k9.mail.store.imap;
import android.util.Log;
import com.fsck.k9.K9;
import java.util.ArrayList;
import java.util.List;
/**
* Utility methods for use with IMAP.
*/
public class ImapUtility {
/**
* Gets all of the values in a sequence set per RFC 3501.
*
* <p>
* Any ranges are expanded into a list of individual numbers.
* </p>
*
* <pre>
* sequence-number = nz-number / "*"
* sequence-range = sequence-number ":" sequence-number
* sequence-set = (sequence-number / sequence-range) *("," sequence-set)
* </pre>
*
* @param set
* The sequence set string as received by the server.
*
* @return The list of IDs as strings in this sequence set. If the set is invalid, an empty
* list is returned.
*/
public static List<String> getImapSequenceValues(String set) {
ArrayList<String> list = new ArrayList<String>();
if (set != null) {
String[] setItems = set.split(",");
for (String item : setItems) {
if (item.indexOf(':') == -1) {
// simple item
try {
Integer.parseInt(item); // Don't need the value; just ensure it's valid
list.add(item);
} catch (NumberFormatException e) {
Log.d(K9.LOG_TAG, "Invalid UID value", e);
}
} else {
// range
list.addAll(getImapRangeValues(item));
}
}
}
return list;
}
/**
* Expand the given number range into a list of individual numbers.
*
* <pre>
* sequence-number = nz-number / "*"
* sequence-range = sequence-number ":" sequence-number
* sequence-set = (sequence-number / sequence-range) *("," sequence-set)
* </pre>
*
* @param range
* The range string as received by the server.
*
* @return The list of IDs as strings in this range. If the range is not valid, an empty list
* is returned.
*/
public static List<String> getImapRangeValues(String range) {
ArrayList<String> list = new ArrayList<String>();
try {
if (range != null) {
int colonPos = range.indexOf(':');
if (colonPos > 0) {
int first = Integer.parseInt(range.substring(0, colonPos));
int second = Integer.parseInt(range.substring(colonPos + 1));
if (first < second) {
for (int i = first; i <= second; i++) {
list.add(Integer.toString(i));
}
} else {
for (int i = first; i >= second; i--) {
list.add(Integer.toString(i));
}
}
}
}
} catch (NumberFormatException e) {
Log.d(K9.LOG_TAG, "Invalid range value", e);
}
return list;
}
}

View File

@ -96,6 +96,9 @@ public class AccountSettings {
s.put("localStorageProvider", Settings.versions(
new V(1, new StorageProviderSetting())
));
s.put("markMessageAsReadOnView", Settings.versions(
new V(7, new BooleanSetting(true))
));
s.put("maxPushFolders", Settings.versions(
new V(1, new IntegerRangeSetting(0, 100, 10))
));

View File

@ -7,6 +7,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
public class Editor implements android.content.SharedPreferences.Editor {
private Storage storage;
@ -138,4 +139,9 @@ public class Editor implements android.content.SharedPreferences.Editor {
return this;
}
@Override
public android.content.SharedPreferences.Editor putStringSet(String arg0, Set<String> arg1) {
throw new RuntimeException("Not implemented");
}
}

View File

@ -184,7 +184,7 @@ public class GlobalSettings {
new V(1, new BooleanSetting(false))
));
s.put("theme", Settings.versions(
new V(1, new ThemeSetting(android.R.style.Theme_Light))
new V(1, new ThemeSetting(K9.THEME_LIGHT))
));
s.put("useGalleryBugWorkaround", Settings.versions(
new V(1, new GalleryBugWorkaroundSetting())
@ -294,34 +294,47 @@ public class GlobalSettings {
/**
* The theme setting.
*/
public static class ThemeSetting extends PseudoEnumSetting<Integer> {
private final Map<Integer, String> mMapping;
public static class ThemeSetting extends SettingsDescription {
private static final String THEME_LIGHT = "light";
private static final String THEME_DARK = "dark";
public ThemeSetting(int defaultValue) {
super(defaultValue);
Map<Integer, String> mapping = new HashMap<Integer, String>();
mapping.put(android.R.style.Theme_Light, "light");
mapping.put(android.R.style.Theme, "dark");
mMapping = Collections.unmodifiableMap(mapping);
}
@Override
protected Map<Integer, String> getMapping() {
return mMapping;
}
@Override
public Object fromString(String value) throws InvalidSettingValueException {
try {
Integer theme = Integer.parseInt(value);
if (mMapping.containsKey(theme)) {
return theme;
if (theme == K9.THEME_LIGHT ||
// We used to store the resource ID of the theme in the preference storage,
// but don't use the database upgrade mechanism to update the values. So
// we have to deal with the old format here.
theme == android.R.style.Theme_Light) {
return K9.THEME_LIGHT;
} else if (theme == K9.THEME_DARK || theme == android.R.style.Theme) {
return K9.THEME_DARK;
}
} catch (NumberFormatException e) { /* do nothing */ }
throw new InvalidSettingValueException();
}
@Override
public Object fromPrettyString(String value) throws InvalidSettingValueException {
if (THEME_LIGHT.equals(value)) {
return K9.THEME_LIGHT;
} else if (THEME_DARK.equals(value)) {
return K9.THEME_DARK;
}
throw new InvalidSettingValueException();
}
@Override
public String toPrettyString(Object value) {
return (((Integer)value).intValue() == K9.THEME_LIGHT) ? THEME_LIGHT : THEME_DARK;
}
}
/**

View File

@ -35,7 +35,7 @@ public class Settings {
*
* @see SettingsExporter
*/
public static final int VERSION = 6;
public static final int VERSION = 7;
public static Map<String, Object> validate(int version, Map<String,
TreeMap<Integer, SettingsDescription>> settings,

View File

@ -15,6 +15,7 @@ import java.net.URI;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
@ -399,4 +400,10 @@ public class Storage implements SharedPreferences {
Log.e(K9.LOG_TAG, "Error writing key '" + key + "', value = '" + value + "'");
}
}
@Override
public Set<String> getStringSet(String arg0, Set<String> arg1) {
throw new RuntimeException("Not implemented");
}
}

View File

@ -1,18 +1,5 @@
package com.fsck.k9.provider;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.Semaphore;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentValues;
@ -46,6 +33,19 @@ import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.store.LocalStore;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.Semaphore;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
public class MessageProvider extends ContentProvider {
public static interface MessageColumns extends BaseColumns {
@ -60,7 +60,7 @@ public class MessageProvider extends ContentProvider {
* <P>Type: TEXT</P>
*/
String SENDER = "sender";
/**
* <P>Type: TEXT</P>
*/
@ -245,7 +245,7 @@ public class MessageProvider extends ContentProvider {
final BlockingQueue<List<MessageInfoHolder>> queue = new SynchronousQueue<List<MessageInfoHolder>>();
// new code for integrated inbox, only execute this once as it will be processed afterwards via the listener
final SearchAccount integratedInboxAccount = new SearchAccount(getContext(), true, null, null);
final SearchAccount integratedInboxAccount = SearchAccount.createUnifiedInboxAccount(getContext());
final MessagingController msgController = MessagingController.getInstance(K9.app);
msgController.searchLocalMessages(integratedInboxAccount, null,
@ -714,6 +714,12 @@ public class MessageProvider extends ContentProvider {
checkClosed();
mCursor.unregisterDataSetObserver(observer);
}
@Override
public int getType(int columnIndex) {
checkClosed();
return mCursor.getType(columnIndex);
}
}
protected class ThrottlingQueryHandler implements QueryHandler {

View File

@ -123,7 +123,7 @@ public class RemoteControlService extends CoreService {
String theme = intent.getStringExtra(K9_THEME);
if (theme != null) {
K9.setK9Theme(K9RemoteControl.K9_THEME_DARK.equals(theme) ? android.R.style.Theme : android.R.style.Theme_Light);
K9.setK9Theme(K9RemoteControl.K9_THEME_DARK.equals(theme) ? K9.THEME_DARK : K9.THEME_LIGHT);
}
SharedPreferences sPrefs = preferences.getPreferences();

View File

@ -43,27 +43,6 @@ import com.fsck.k9.mail.store.LocalStore.LocalAttachmentBodyPart;
import com.fsck.k9.provider.AttachmentProvider;
public class AttachmentView extends FrameLayout implements OnClickListener, OnLongClickListener {
/**
* Regular expression that represents characters we won't allow in file names.
*
* <p>
* Allowed are:
* <ul>
* <li>word characters (letters, digits, and underscores): {@code \w}</li>
* <li>spaces: {@code " "}</li>
* <li>special characters: {@code !}, {@code #}, {@code $}, {@code %}, {@code &}, {@code '},
* {@code (}, {@code )}, {@code -}, {@code @}, {@code ^}, {@code `}, <code>&#123;</code>,
* <code>&#125;</code>, {@code ~}, {@code .}, {@code ,}</li>
* </ul></p>
*/
private static final String INVALID_CHARACTERS = "[^\\w !#$%&'()\\-@\\^`{}~.,]+";
/**
* Invalid characters in a file name are replaced by this character.
*/
private static final String REPLACEMENT_CHARACTER = "_";
private Context mContext;
public Button viewButton;
public Button downloadButton;
@ -259,7 +238,7 @@ public class AttachmentView extends FrameLayout implements OnClickListener, OnLo
*/
public void writeFile(File directory) {
try {
String filename = sanitizeFilename(name);
String filename = Utility.sanitizeFilename(name);
File file = Utility.createUniqueFile(directory, filename);
Uri uri = AttachmentProvider.getAttachmentUri(mAccount, part.getAttachmentId());
InputStream in = mContext.getContentResolver().openInputStream(uri);
@ -278,18 +257,6 @@ public class AttachmentView extends FrameLayout implements OnClickListener, OnLo
}
}
/**
* Replace characters we don't allow in file names with a replacement character.
*
* @param filename
* The original file name.
*
* @return The sanitized file name containing only allowed characters.
*/
private String sanitizeFilename(String filename) {
return filename.replaceAll(INVALID_CHARACTERS, REPLACEMENT_CHARACTER);
}
/**
* saves the file to the defaultpath setting in the config, or if the config
* is not set => to the Environment

View File

@ -50,6 +50,7 @@ public class MessageHeader extends ScrollView implements OnClickListener {
private View mChip;
private View mChip2;
private View mChip3;
private CheckBox mFlagged;
private LinearLayout mToContainerView;
private LinearLayout mCcContainerView;
@ -62,6 +63,8 @@ public class MessageHeader extends ScrollView implements OnClickListener {
private ImageView mShowAdditionalHeadersIcon;
private SavedState mSavedState;
private OnLayoutChangedListener mOnLayoutChangedListener;
/**
* Pair class is only available since API Level 5, so we need
* this helper class unfortunately
@ -95,6 +98,7 @@ public class MessageHeader extends ScrollView implements OnClickListener {
mAdditionalHeadersView = (TextView) findViewById(R.id.additional_headers_view);
mChip = findViewById(R.id.chip);
mChip2 = findViewById(R.id.chip2);
mChip3 = findViewById(R.id.chip3);
mDateView = (TextView) findViewById(R.id.date);
mTimeView = (TextView) findViewById(R.id.time);
mFlagged = (CheckBox) findViewById(R.id.flagged);
@ -115,12 +119,16 @@ public class MessageHeader extends ScrollView implements OnClickListener {
((TextView) findViewById(R.id.cc_label)).setTextSize(TypedValue.COMPLEX_UNIT_SP, mFontSizes.getMessageViewCC());
findViewById(R.id.show_additional_headers_area).setOnClickListener(this);
findViewById(R.id.additional_headers_row).setOnClickListener(this);
mFromView.setOnClickListener(this);
mToView.setOnClickListener(this);
mCcView.setOnClickListener(this);
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.additional_headers_row:
case R.id.show_additional_headers_area: {
onShowAdditionalHeaders();
break;
@ -129,6 +137,11 @@ public class MessageHeader extends ScrollView implements OnClickListener {
onAddSenderToContacts();
break;
}
case R.id.to:
case R.id.cc: {
expand((TextView)view, ((TextView)view).getEllipsize() != null);
layoutChanged();
}
}
}
@ -254,6 +267,8 @@ public class MessageHeader extends ScrollView implements OnClickListener {
mChip.getBackground().setAlpha(chipColorAlpha);
mChip2.setBackgroundColor(chipColor);
mChip2.getBackground().setAlpha(chipColorAlpha);
mChip3.setBackgroundColor(chipColor);
mChip3.getBackground().setAlpha(chipColorAlpha);
setVisibility(View.VISIBLE);
@ -271,9 +286,27 @@ public class MessageHeader extends ScrollView implements OnClickListener {
int currentVisibility = mAdditionalHeadersView.getVisibility();
if (currentVisibility == View.VISIBLE) {
hideAdditionalHeaders();
expand(mToView, false);
expand(mCcView, false);
} else {
showAdditionalHeaders();
expand(mToView, true);
expand(mCcView, true);
}
layoutChanged();
}
/**
* Expand or collapse a TextView by removing or adding the 2 lines limitation
*/
private void expand(TextView v, boolean expand) {
if (expand) {
v.setMaxLines(Integer.MAX_VALUE);
v.setEllipsize(null);
} else {
v.setMaxLines(2);
v.setEllipsize(android.text.TextUtils.TruncateAt.END);
}
}
private List<HeaderEntry> getAdditionalHeaders(final Message message)
@ -380,4 +413,18 @@ public class MessageHeader extends ScrollView implements OnClickListener {
out.writeInt((this.additionalHeadersVisible) ? 1 : 0);
}
}
public interface OnLayoutChangedListener {
void onLayoutChanged();
}
public void setOnLayoutChangedListener(OnLayoutChangedListener listener) {
mOnLayoutChangedListener = listener;
}
private void layoutChanged() {
if (mOnLayoutChangedListener != null) {
mOnLayoutChangedListener.onLayoutChanged();
}
}
}

View File

@ -72,6 +72,12 @@ public class MessageWebView extends WebView {
this.setScrollBarStyle(SCROLLBARS_INSIDE_OVERLAY);
this.setLongClickable(true);
if (K9.getK9Theme() == K9.THEME_DARK) {
// Black theme should get a black webview background
// we'll set the background of the messages on load
this.setBackgroundColor(0xff000000);
}
final WebSettings webSettings = this.getSettings();
webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);
@ -104,6 +110,19 @@ public class MessageWebView extends WebView {
}
public void setText(String text, String contentType) {
String content = text;
if (K9.getK9Theme() == K9.THEME_DARK) {
// It's a little wrong to just throw in the <style> before the opening <html>
// but it's less wrong than trying to edit the html stream
content = "<style>* { background: black ! important; color: white !important }" +
":link, :link * { color: #CCFF33 !important }" +
":visited, :visited * { color: #551A8B !important }</style> "
+ content;
}
loadDataWithBaseURL("http://", content, contentType, "utf-8", null);
}
/*
* Emulate the shift key being pressed to trigger the text selection mode
* of a WebView.

View File

@ -7,16 +7,26 @@ import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.*;
import android.webkit.WebView;
import android.webkit.WebView.HitTestResult;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.Toast;
import com.fsck.k9.Account;
import com.fsck.k9.K9;
import com.fsck.k9.R;
@ -25,15 +35,51 @@ import com.fsck.k9.controller.MessagingController;
import com.fsck.k9.controller.MessagingListener;
import com.fsck.k9.crypto.CryptoProvider;
import com.fsck.k9.crypto.PgpData;
import com.fsck.k9.helper.ClipboardManager;
import com.fsck.k9.helper.Contacts;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.*;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.store.LocalStore;
import com.fsck.k9.mail.store.LocalStore.LocalMessage;
import com.fsck.k9.provider.AttachmentProvider.AttachmentProviderColumns;
import org.apache.commons.io.IOUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLDecoder;
import java.util.List;
public class SingleMessageView extends LinearLayout implements OnClickListener {
public class SingleMessageView extends LinearLayout implements OnClickListener,
MessageHeader.OnLayoutChangedListener, OnCreateContextMenuListener {
private static final int MENU_ITEM_LINK_VIEW = Menu.FIRST;
private static final int MENU_ITEM_LINK_SHARE = Menu.FIRST + 1;
private static final int MENU_ITEM_LINK_COPY = Menu.FIRST + 2;
private static final int MENU_ITEM_IMAGE_VIEW = Menu.FIRST;
private static final int MENU_ITEM_IMAGE_SAVE = Menu.FIRST + 1;
private static final int MENU_ITEM_IMAGE_COPY = Menu.FIRST + 2;
private static final int MENU_ITEM_PHONE_CALL = Menu.FIRST;
private static final int MENU_ITEM_PHONE_SAVE = Menu.FIRST + 1;
private static final int MENU_ITEM_PHONE_COPY = Menu.FIRST + 2;
private static final int MENU_ITEM_EMAIL_SEND = Menu.FIRST;
private static final int MENU_ITEM_EMAIL_SAVE = Menu.FIRST + 1;
private static final int MENU_ITEM_EMAIL_COPY = Menu.FIRST + 2;
private static final String[] ATTACHMENT_PROJECTION = new String[] {
AttachmentProviderColumns._ID,
AttachmentProviderColumns.DISPLAY_NAME
};
private static final int DISPLAY_NAME_INDEX = 1;
private boolean mScreenReaderEnabled;
private MessageCryptoView mCryptoView;
private MessageWebView mMessageContentView;
@ -56,15 +102,20 @@ public class SingleMessageView extends LinearLayout implements OnClickListener {
private View mAttachmentsContainer;
private LinearLayout mInsideAttachmentsContainer;
private SavedState mSavedState;
private ClipboardManager mClipboardManager;
public void initialize(Activity activity) {
mMessageContentView = (MessageWebView) findViewById(R.id.message_content);
mAccessibleMessageContentView = (AccessibleWebView) findViewById(R.id.accessible_message_content);
mMessageContentView.configure();
activity.registerForContextMenu(mMessageContentView);
mMessageContentView.setOnCreateContextMenuListener(this);
mHeaderPlaceHolder = (LinearLayout) findViewById(R.id.message_view_header_container);
mHeaderContainer = (MessageHeader) findViewById(R.id.header_container);
mHeaderContainer.setOnLayoutChangedListener(this);
mAttachmentsContainer = findViewById(R.id.attachments_container);
mInsideAttachmentsContainer = (LinearLayout) findViewById(R.id.inside_attachments_container);
@ -112,6 +163,210 @@ public class SingleMessageView extends LinearLayout implements OnClickListener {
mShowMessageAction.setOnClickListener(this);
mShowAttachmentsAction.setOnClickListener(this);
mShowPicturesAction.setOnClickListener(this);
mClipboardManager = ClipboardManager.getInstance(activity);
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu);
WebView webview = (WebView) v;
WebView.HitTestResult result = webview.getHitTestResult();
int type = result.getType();
Context context = getContext();
switch (type) {
case HitTestResult.SRC_ANCHOR_TYPE: {
final String url = result.getExtra();
OnMenuItemClickListener listener = new OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case MENU_ITEM_LINK_VIEW: {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
getContext().startActivity(intent);
break;
}
case MENU_ITEM_LINK_SHARE: {
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_TEXT, url);
getContext().startActivity(intent);
break;
}
case MENU_ITEM_LINK_COPY: {
String label = getContext().getString(
R.string.webview_contextmenu_link_clipboard_label);
mClipboardManager.setText(label, url);
break;
}
}
return true;
}
};
menu.setHeaderTitle(url);
menu.add(Menu.NONE, MENU_ITEM_LINK_VIEW, 0,
context.getString(R.string.webview_contextmenu_link_view_action))
.setOnMenuItemClickListener(listener);
menu.add(Menu.NONE, MENU_ITEM_LINK_SHARE, 1,
context.getString(R.string.webview_contextmenu_link_share_action))
.setOnMenuItemClickListener(listener);
menu.add(Menu.NONE, MENU_ITEM_LINK_COPY, 2,
context.getString(R.string.webview_contextmenu_link_copy_action))
.setOnMenuItemClickListener(listener);
break;
}
case HitTestResult.IMAGE_TYPE:
case HitTestResult.SRC_IMAGE_ANCHOR_TYPE: {
final String url = result.getExtra();
final boolean externalImage = url.startsWith("http");
OnMenuItemClickListener listener = new OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case MENU_ITEM_IMAGE_VIEW: {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
if (!externalImage) {
// Grant read permission if this points to our
// AttachmentProvider
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
getContext().startActivity(intent);
break;
}
case MENU_ITEM_IMAGE_SAVE: {
new DownloadImageTask().execute(url);
break;
}
case MENU_ITEM_IMAGE_COPY: {
String label = getContext().getString(
R.string.webview_contextmenu_image_clipboard_label);
mClipboardManager.setText(label, url);
break;
}
}
return true;
}
};
menu.setHeaderTitle((externalImage) ?
url : context.getString(R.string.webview_contextmenu_image_title));
menu.add(Menu.NONE, MENU_ITEM_IMAGE_VIEW, 0,
context.getString(R.string.webview_contextmenu_image_view_action))
.setOnMenuItemClickListener(listener);
menu.add(Menu.NONE, MENU_ITEM_IMAGE_SAVE, 1,
(externalImage) ?
context.getString(R.string.webview_contextmenu_image_download_action) :
context.getString(R.string.webview_contextmenu_image_save_action))
.setOnMenuItemClickListener(listener);
if (externalImage) {
menu.add(Menu.NONE, MENU_ITEM_IMAGE_COPY, 2,
context.getString(R.string.webview_contextmenu_image_copy_action))
.setOnMenuItemClickListener(listener);
}
break;
}
case HitTestResult.PHONE_TYPE: {
final String phoneNumber = result.getExtra();
OnMenuItemClickListener listener = new OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case MENU_ITEM_PHONE_CALL: {
Uri uri = Uri.parse(WebView.SCHEME_TEL + phoneNumber);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
getContext().startActivity(intent);
break;
}
case MENU_ITEM_PHONE_SAVE: {
Contacts contacts = Contacts.getInstance(getContext());
contacts.addPhoneContact(phoneNumber);
break;
}
case MENU_ITEM_PHONE_COPY: {
String label = getContext().getString(
R.string.webview_contextmenu_phone_clipboard_label);
mClipboardManager.setText(label, phoneNumber);
break;
}
}
return true;
}
};
menu.setHeaderTitle(phoneNumber);
menu.add(Menu.NONE, MENU_ITEM_PHONE_CALL, 0,
context.getString(R.string.webview_contextmenu_phone_call_action))
.setOnMenuItemClickListener(listener);
menu.add(Menu.NONE, MENU_ITEM_PHONE_SAVE, 1,
context.getString(R.string.webview_contextmenu_phone_save_action))
.setOnMenuItemClickListener(listener);
menu.add(Menu.NONE, MENU_ITEM_PHONE_COPY, 2,
context.getString(R.string.webview_contextmenu_phone_copy_action))
.setOnMenuItemClickListener(listener);
break;
}
case WebView.HitTestResult.EMAIL_TYPE: {
final String email = result.getExtra();
OnMenuItemClickListener listener = new OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case MENU_ITEM_EMAIL_SEND: {
Uri uri = Uri.parse(WebView.SCHEME_MAILTO + email);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
getContext().startActivity(intent);
break;
}
case MENU_ITEM_EMAIL_SAVE: {
Contacts contacts = Contacts.getInstance(getContext());
contacts.createContact(new Address(email));
break;
}
case MENU_ITEM_EMAIL_COPY: {
String label = getContext().getString(
R.string.webview_contextmenu_email_clipboard_label);
mClipboardManager.setText(label, email);
break;
}
}
return true;
}
};
menu.setHeaderTitle(email);
menu.add(Menu.NONE, MENU_ITEM_EMAIL_SEND, 0,
context.getString(R.string.webview_contextmenu_email_send_action))
.setOnMenuItemClickListener(listener);
menu.add(Menu.NONE, MENU_ITEM_EMAIL_SAVE, 1,
context.getString(R.string.webview_contextmenu_email_save_action))
.setOnMenuItemClickListener(listener);
menu.add(Menu.NONE, MENU_ITEM_EMAIL_COPY, 2,
context.getString(R.string.webview_contextmenu_email_copy_action))
.setOnMenuItemClickListener(listener);
break;
}
}
}
@Override
@ -345,7 +600,7 @@ public class SingleMessageView extends LinearLayout implements OnClickListener {
if (mScreenReaderEnabled) {
mAccessibleMessageContentView.loadDataWithBaseURL("http://", emailText, contentType, "utf-8", null);
} else {
mMessageContentView.loadDataWithBaseURL("http://", emailText, contentType, "utf-8", null);
mMessageContentView.setText(emailText, contentType);
mMessageContentView.scrollTo(0, 0);
}
@ -508,6 +763,12 @@ public class SingleMessageView extends LinearLayout implements OnClickListener {
mSavedState = savedState;
}
public void onLayoutChanged() {
if (mMessageContentView != null) {
mMessageContentView.invalidate();
}
}
static class SavedState extends BaseSavedState {
boolean attachmentViewVisible;
boolean hiddenAttachmentsVisible;
@ -547,4 +808,117 @@ public class SingleMessageView extends LinearLayout implements OnClickListener {
out.writeInt((this.showPictures) ? 1 : 0);
}
}
class DownloadImageTask extends AsyncTask<String, Void, String> {
@Override
protected String doInBackground(String... params) {
String urlString = params[0];
try {
boolean externalImage = urlString.startsWith("http");
String filename = null;
String mimeType = null;
InputStream in = null;
try {
if (externalImage) {
URL url = new URL(urlString);
URLConnection conn = url.openConnection();
in = conn.getInputStream();
String path = url.getPath();
// Try to get the filename from the URL
int start = path.lastIndexOf("/");
if (start != -1 && start + 1 < path.length()) {
filename = URLDecoder.decode(path.substring(start + 1), "UTF-8");
} else {
// Use a dummy filename if necessary
filename = "saved_image";
}
// Get the MIME type if we couldn't find a file extension
if (filename.indexOf('.') == -1) {
mimeType = conn.getContentType();
}
} else {
ContentResolver contentResolver = getContext().getContentResolver();
Uri uri = Uri.parse(urlString);
// Get the filename from AttachmentProvider
Cursor cursor = contentResolver.query(uri, ATTACHMENT_PROJECTION, null, null, null);
if (cursor != null) {
try {
if (cursor.moveToNext()) {
filename = cursor.getString(DISPLAY_NAME_INDEX);
}
} finally {
cursor.close();
}
}
// Use a dummy filename if necessary
if (filename == null) {
filename = "saved_image";
}
// Get the MIME type if we couldn't find a file extension
if (filename.indexOf('.') == -1) {
mimeType = contentResolver.getType(uri);
}
in = contentResolver.openInputStream(uri);
}
// Do we still need an extension?
if (filename.indexOf('.') == -1) {
// Use JPEG as fallback
String extension = "jpeg";
if (mimeType != null) {
// Try to find an extension for the given MIME type
String ext = MimeUtility.getExtensionByMimeType(mimeType);
if (ext != null) {
extension = ext;
}
}
filename += "." + extension;
}
String sanitized = Utility.sanitizeFilename(filename);
File directory = new File(K9.getAttachmentDefaultPath());
File file = Utility.createUniqueFile(directory, sanitized);
FileOutputStream out = new FileOutputStream(file);
try {
IOUtils.copy(in, out);
out.flush();
} finally {
out.close();
}
return file.getName();
} finally {
if (in != null) {
in.close();
}
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
@Override
protected void onPostExecute(String filename) {
String text;
if (filename == null) {
text = getContext().getString(R.string.image_saving_failed);
} else {
text = getContext().getString(R.string.image_saved_as, filename);
}
Toast.makeText(getContext(), text, Toast.LENGTH_LONG).show();
}
}
}

View File

@ -0,0 +1,39 @@
package com.fsck.k9.mail.internet;
import android.test.AndroidTestCase;
public class MimeUtilityTest extends AndroidTestCase {
public void testGetHeaderParameter() {
String result;
/* Test edge cases */
result = MimeUtility.getHeaderParameter(";", null);
assertEquals(null, result);
result = MimeUtility.getHeaderParameter("name", "name");
assertEquals(null, result);
result = MimeUtility.getHeaderParameter("name=", "name");
assertEquals("", result);
result = MimeUtility.getHeaderParameter("name=\"", "name");
assertEquals("\"", result);
/* Test expected cases */
result = MimeUtility.getHeaderParameter("name=value", "name");
assertEquals("value", result);
result = MimeUtility.getHeaderParameter("name = value", "name");
assertEquals("value", result);
result = MimeUtility.getHeaderParameter("name=\"value\"", "name");
assertEquals("value", result);
result = MimeUtility.getHeaderParameter("name = \"value\"" , "name");
assertEquals("value", result);
result = MimeUtility.getHeaderParameter("name=\"\"", "name");
assertEquals("", result);
}
}

View File

@ -59,6 +59,36 @@ public class ImapResponseParserTest extends TestCase {
assertEquals("token2", respTextCode.get(1));
}
public void testImapListMethods() throws IOException {
ImapList list = new ImapList();
list.add("ONE");
list.add("TWO");
list.add("THREE");
assertTrue(list.containsKey("ONE"));
assertTrue(list.containsKey("TWO"));
assertFalse(list.containsKey("THREE"));
assertFalse(list.containsKey("nonexistent"));
assertEquals("TWO", list.getKeyedValue("ONE"));
assertEquals("THREE", list.getKeyedValue("TWO"));
assertNull(list.getKeyedValue("THREE"));
assertNull(list.getKeyedValue("nonexistent"));
assertEquals(0, list.getKeyIndex("ONE"));
assertEquals(1, list.getKeyIndex("TWO"));
try {
list.getKeyIndex("THREE");
fail("IllegalArgumentException should have been thrown");
} catch (IllegalArgumentException e) { /* do nothing */ }
try {
list.getKeyIndex("nonexistent");
fail("IllegalArgumentException should have been thrown");
} catch (IllegalArgumentException e) { /* do nothing */ }
}
private ImapResponseParser createParser(String response) {
ByteArrayInputStream in = new ByteArrayInputStream(response.getBytes());
PeekableInputStream pin = new PeekableInputStream(in);

View File

@ -0,0 +1,125 @@
/*
* Copyright (C) 2012 The K-9 Dog Walkers
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fsck.k9.mail.store.imap;
import java.util.List;
import android.test.MoreAsserts;
import junit.framework.TestCase;
public class ImapUtilityTest extends TestCase {
/**
* Test getting elements of an IMAP sequence set.
*/
public void testGetImapSequenceValues() {
String[] expected;
List<String> actual;
// Test valid sets
expected = new String[] {"1"};
actual = ImapUtility.getImapSequenceValues("1");
MoreAsserts.assertEquals(expected, actual.toArray());
expected = new String[] {"1", "3", "2"};
actual = ImapUtility.getImapSequenceValues("1,3,2");
MoreAsserts.assertEquals(expected, actual.toArray());
expected = new String[] {"4", "5", "6"};
actual = ImapUtility.getImapSequenceValues("4:6");
MoreAsserts.assertEquals(expected, actual.toArray());
expected = new String[] {"9", "8", "7"};
actual = ImapUtility.getImapSequenceValues("9:7");
MoreAsserts.assertEquals(expected, actual.toArray());
expected = new String[] {"1", "2", "3", "4", "9", "8", "7"};
actual = ImapUtility.getImapSequenceValues("1,2:4,9:7");
MoreAsserts.assertEquals(expected, actual.toArray());
// Test partially invalid sets
expected = new String[] { "1", "5" };
actual = ImapUtility.getImapSequenceValues("1,x,5");
MoreAsserts.assertEquals(expected, actual.toArray());
expected = new String[] { "1", "2", "3" };
actual = ImapUtility.getImapSequenceValues("a:d,1:3");
MoreAsserts.assertEquals(expected, actual.toArray());
// Test invalid sets
expected = new String[0];
actual = ImapUtility.getImapSequenceValues("");
MoreAsserts.assertEquals(expected, actual.toArray());
expected = new String[0];
actual = ImapUtility.getImapSequenceValues(null);
MoreAsserts.assertEquals(expected, actual.toArray());
expected = new String[0];
actual = ImapUtility.getImapSequenceValues("a");
MoreAsserts.assertEquals(expected, actual.toArray());
expected = new String[0];
actual = ImapUtility.getImapSequenceValues("1:x");
MoreAsserts.assertEquals(expected, actual.toArray());
}
/**
* Test getting elements of an IMAP range.
*/
public void testGetImapRangeValues() {
String[] expected;
List<String> actual;
// Test valid ranges
expected = new String[] {"1", "2", "3"};
actual = ImapUtility.getImapRangeValues("1:3");
MoreAsserts.assertEquals(expected, actual.toArray());
expected = new String[] {"16", "15", "14"};
actual = ImapUtility.getImapRangeValues("16:14");
MoreAsserts.assertEquals(expected, actual.toArray());
// Test in-valid ranges
expected = new String[0];
actual = ImapUtility.getImapRangeValues("");
MoreAsserts.assertEquals(expected, actual.toArray());
expected = new String[0];
actual = ImapUtility.getImapRangeValues(null);
MoreAsserts.assertEquals(expected, actual.toArray());
expected = new String[0];
actual = ImapUtility.getImapRangeValues("a");
MoreAsserts.assertEquals(expected, actual.toArray());
expected = new String[0];
actual = ImapUtility.getImapRangeValues("6");
MoreAsserts.assertEquals(expected, actual.toArray());
expected = new String[0];
actual = ImapUtility.getImapRangeValues("1:3,6");
MoreAsserts.assertEquals(expected, actual.toArray());
expected = new String[0];
actual = ImapUtility.getImapRangeValues("1:x");
MoreAsserts.assertEquals(expected, actual.toArray());
expected = new String[0];
actual = ImapUtility.getImapRangeValues("1:*");
MoreAsserts.assertEquals(expected, actual.toArray());
}
}