1
0
mirror of https://github.com/moparisthebest/k-9 synced 2025-02-11 12:40:22 -05:00

Merge of remote branch 'origin/master' into ms-eas.

This commit is contained in:
Kris Wong 2012-03-03 16:56:02 -05:00
commit cad8390140
81 changed files with 4045 additions and 1730 deletions

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="15006"
android:versionName="4.106" package="com.fsck.k9"
android:versionCode="15011"
android:versionName="4.112" package="com.fsck.k9"
>
<uses-sdk
android:minSdkVersion="7"
@ -43,13 +43,13 @@
<uses-permission android:name="com.fsck.k9.permission.REMOTE_CONTROL"/>
<permission android:name="com.fsck.k9.permission.READ_MESSAGES"
android:permissionGroup="android.permission-group.MESSAGES"
android:protectionLevel="normal"
android:protectionLevel="dangerous"
android:label="@string/read_messages_label"
android:description="@string/read_messages_desc"/>
<uses-permission android:name="com.fsck.k9.permission.READ_MESSAGES"/>
<permission android:name="com.fsck.k9.permission.DELETE_MESSAGES"
android:permissionGroup="android.permission-group.MESSAGES"
android:protectionLevel="normal"
android:protectionLevel="dangerous"
android:label="@string/delete_messages_label"
android:description="@string/read_messages_desc"/>
<uses-permission android:name="com.fsck.k9.permission.DELETE_MESSAGES"/>
@ -208,6 +208,10 @@
android:launchMode="singleTask"
android:configChanges="locale"
>
<intent-filter>
<!-- This action is only to allow an entry point for launcher shortcuts -->
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
<activity
android:name="com.fsck.k9.activity.MessageView"
@ -387,5 +391,25 @@ otherwise it would make K-9 start at the wrong time
android:readPermission="com.fsck.k9.permission.READ_MESSAGES"
android:writePermission="com.fsck.k9.permission.DELETE_MESSAGES"
/>
<receiver
android:name=".provider.UnreadWidgetProvider"
android:label="@string/unread_widget_label"
android:icon="@drawable/icon">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/unread_widget_info" />
</receiver>
<activity android:name=".activity.UnreadWidgetConfiguration">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter>
</activity>
<activity
android:name=".activity.AccountList">
</activity>
</application>
</manifest>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.0"
width="168.45"
height="113.9081"
id="svg2393">
<defs
id="defs2395" />
<g
transform="translate(-41.490136,-32.550987)"
id="layer1">
<path
d="M 125.71429,102.3114 L 209.93844,32.550987 L 209.9357,76.729057 L 125.71429,146.45909 L 41.492876,76.729057 L 41.490136,32.550987 L 125.71429,102.3114 z"
id="path2439"
style="fill:#595959;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 735 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 393 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 389 B

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#a0000000"/>
<corners android:radius="7dp"/>
</shape>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:drawable/menuitem_background"
android:state_pressed="true" />
<item android:drawable="@android:drawable/menuitem_background"
android:state_focused="true"
android:state_enabled="true"
android:state_window_focused="true" />
<item android:drawable="@color/message_view_header_background" />
</selector>

BIN
res/drawable/show_less.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 B

BIN
res/drawable/show_more.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 299 B

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#ffcc0000"/>
<corners android:radius="17dp"/>
</shape>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- FIXME: find a nicer looking way than using 'menuitem_background' -->
<item android:drawable="@android:drawable/menuitem_background"
android:state_pressed="true" />
<item android:drawable="@android:drawable/menuitem_background"
android:state_focused="true"
android:state_enabled="true"
android:state_window_focused="true" />
<item android:drawable="@android:color/transparent" />
</selector>

View File

@ -1,75 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
android:id="@+id/scrolling_move_buttons"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical">
<Button
android:id="@+id/archive_scrolling"
android:text="@string/message_view_archive_action"
android:layout_height="wrap_content"
android:layout_width="0dip"
android:layout_weight="1" />
<Button
android:id="@+id/move_scrolling"
android:text="@string/message_view_move_action"
android:layout_height="wrap_content"
android:layout_width="0dip"
android:layout_weight="1" />
<Button
android:id="@+id/spam_scrolling"
android:text="@string/message_view_spam_action"
android:layout_height="wrap_content"
android:layout_width="0dip"
android:layout_weight="1" />
</LinearLayout>
<LinearLayout
android:id="@+id/scrolling_buttons"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="57dip"
android:background="@android:color/transparent"
android:gravity="center_vertical">
<Button
android:id="@+id/previous_scrolling"
android:text="@string/message_view_prev_action"
android:contentDescription="@string/previous_action"
android:textSize="35dip"
android:layout_height="wrap_content"
android:layout_width="0dip"
android:padding="0dip"
android:layout_weight="1" />
<Button
android:id="@+id/reply_scrolling"
android:text="@string/reply_action"
android:layout_height="wrap_content"
android:layout_width="0dip"
android:layout_weight="1" />
<Button
android:id="@+id/delete_scrolling"
android:text="@string/delete_action"
android:layout_height="wrap_content"
android:layout_width="0dip"
android:layout_weight="1" />
<Button
android:id="@+id/forward_scrolling"
android:text="@string/forward_action"
android:layout_height="wrap_content"
android:layout_width="0dip"
android:layout_weight="1" />
<Button
android:id="@+id/next_scrolling"
android:text="@string/message_view_next_action"
android:contentDescription="@string/next_action"
android:textSize="35dip"
android:layout_height="wrap_content"
android:layout_width="0dip"
android:padding="0dip"
android:layout_weight="1" />
</LinearLayout>
</merge>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_height="fill_parent"
android:layout_width="fill_parent">
<ListView
android:id="@android:id/list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>
<LinearLayout
android:id="@android:id/empty"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:gravity="center_vertical|center_horizontal">
<ProgressBar
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="true"/>
</LinearLayout>
</LinearLayout>

View File

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_height="fill_parent"
android:layout_width="fill_parent">
<ListView
android:id="@android:id/list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
</LinearLayout>

View File

@ -1,226 +1,75 @@
<?xml version="1.0" encoding="utf-8"?>
<com.fsck.k9.view.SingleMessageView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/message_view"
android:orientation="vertical"
android:layout_width="fill_parent"
android:background="@android:color/transparent"
android:layout_height="wrap_content"
android:layout_weight="1"
xmlns:android="http://schemas.android.com/apk/res/android">
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1">
<!-- Header area -->
<com.fsck.k9.view.MessageHeader
android:id="@+id/header_container"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingTop="2dip"
android:paddingRight="2dip"
>
<View
android:id="@+id/chip"
android:layout_marginTop="1dip"
android:layout_marginBottom="1dip"
android:layout_width="6dip"
android:layout_height="fill_parent" />
<LinearLayout
android:id="@+id/top_container"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="2dip"
android:paddingLeft="4dip"
>
<LinearLayout
android:layout_width="fill_parent"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:gravity="fill_horizontal"
android:layout_height="wrap_content">
<LinearLayout android:id="@+id/people"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingLeft="4dip"
android:layout_weight="5"
android:orientation="vertical">
<TextView android:id="@+id/subject"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="10sp"
android:textStyle="bold"
android:textColor="?android:attr/textColorSecondary"
android:textAppearance="?android:attr/textAppearanceSmall" />
>
<LinearLayout
android:id="@+id/from_container"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:baselineAligned="true" >
<TextView
android:id="@+id/from"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="6dip"
android:singleLine="true"
android:ellipsize="end"
android:textColor="?android:attr/textColorPrimary"
android:textAppearance="?android:attr/textAppearanceSmall" />
</LinearLayout>
<LinearLayout
android:id="@+id/to_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:baselineAligned="true" >
<TextView
android:id="@+id/to_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="4dip"
android:text="@string/message_to_label"
android:textSize="10sp"
android:textStyle="bold"
android:textColor="?android:attr/textColorSecondary" />
<TextView android:id="@+id/to"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:singleLine="false"
android:ellipsize="none"
android:textSize="10sp"
android:textColor="?android:attr/textColorSecondary"
android:textAppearance="?android:attr/textAppearanceSmall" />
</LinearLayout>
<LinearLayout android:id="@+id/cc_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:baselineAligned="true">
<TextView android:id="@+id/cc_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="4dip"
android:text="@string/message_view_cc_label"
android:textSize="10sp"
android:textStyle="bold"
android:textColor="?android:attr/textColorSecondary" />
<TextView android:id="@+id/cc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:singleLine="false"
android:ellipsize="none"
android:textSize="10sp"
android:textColor="?android:attr/textColorSecondary"
android:textAppearance="?android:attr/textAppearanceSmall" />
</LinearLayout>
<TextView android:id="@+id/additional_headers_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="4dip"
android:baselineAligned="true"
android:singleLine="false"
android:ellipsize="none"
android:textSize="10sp"
android:textColor="?android:attr/textColorSecondary"
android:textAppearance="?android:attr/textAppearanceSmall" >
</TextView>
</LinearLayout>
<LinearLayout android:id="@+id/topright_container"
android:orientation="vertical"
android:layout_weight="0.1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="right"
>
<TextView android:id="@+id/date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:singleLine="true"
android:ellipsize="none"
android:textSize="10sp"
android:textColor="?android:attr/textColorPrimary"
android:textAppearance="?android:attr/textAppearanceSmall" />
<TextView android:id="@+id/time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:textSize="10sp"
android:singleLine="true"
android:ellipsize="none"
android:textColor="?android:attr/textColorPrimary"
android:textAppearance="?android:attr/textAppearanceSmall" />
<LinearLayout android:id="@+id/icons_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:paddingTop="4dip"
>
<View android:id="@+id/answered"
android:layout_width="22sp"
android:layout_height="22sp"
android:layout_centerVertical="true"
android:paddingRight="4dip"
android:background="@drawable/ic_email_answered_small" />
<View android:id="@+id/attachment"
android:layout_width="22sp"
android:layout_height="22sp"
android:layout_centerVertical="true"
android:paddingRight="4dip"
android:background="@drawable/ic_email_attachment_small" />
<CheckBox android:id="@+id/flagged"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:focusable="false"
android:layout_alignParentRight="true"
style="?android:attr/starStyle" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</com.fsck.k9.view.MessageHeader>
<LinearLayout
android:id="@+id/show_pictures_section"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dip"
android:paddingLeft="6dip"
android:paddingRight="3dip"
android:paddingTop="4dip"
android:paddingBottom="4dip"
android:baselineAligned="false"
android:visibility="gone">
<TextView
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:attr/textColorSecondary"
android:text="@string/message_view_show_pictures_instructions"
android:layout_gravity="center"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1.0" />
<Button android:id="@+id/show_pictures"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/message_view_show_pictures_action" />
android:id="@+id/message_view_header_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<include layout="@layout/message_view_header"/>
</LinearLayout>
<include layout="@layout/message_view_crypto_layout"/>
<!-- Content area -->
<com.fsck.k9.view.MessageWebView
android:id="@+id/message_content"
android:layout_height="wrap_content"
android:layout_width="fill_parent" />
android:layout_height="0dip"
android:layout_weight="1"
android:layout_width="fill_parent"/>
<com.fsck.k9.view.AccessibleWebView
android:id="@+id/accessible_message_content"
android:layout_height="wrap_content"
android:layout_width="fill_parent" />
android:layout_width="fill_parent"/>
<!-- Attachments area -->
<LinearLayout
android:id="@+id/attachments"
android:orientation="vertical"
<ScrollView
android:id="@+id/attachments_container"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="4dip" />
android:layout_weight="1">
<LinearLayout
android:id="@+id/inside_attachments_container"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/attachments"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="4dip" />
<Button
android:id="@+id/show_hidden_attachments"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/message_view_show_more_attachments_action"/>
<LinearLayout
android:id="@+id/hidden_attachments"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="4dip"/>
</LinearLayout>
</ScrollView>
<Button android:id="@+id/download_remainder"
android:text="@string/message_view_download_remainder"
android:layout_height="wrap_content"
android:visibility="gone"
android:layout_width="fill_parent" />
android:layout_width="fill_parent"/>
</com.fsck.k9.view.SingleMessageView>

View File

@ -3,27 +3,12 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<com.fsck.k9.view.ToggleScrollView
android:id="@+id/top_view"
android:layout_width="fill_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:scrollbarStyle="outsideInset"
android:fillViewport="true"
android:background="@android:color/transparent"
android:fadingEdge="none">
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:background="@android:color/transparent"
android:layout_height="wrap_content"
android:layout_weight="1">
<include layout="@layout/message" />
<include layout="@layout/message_view_scrolling_buttons"/>
</LinearLayout>
</com.fsck.k9.view.ToggleScrollView>
android:layout_height="fill_parent">
<include layout="@layout/message"/>
<include layout="@layout/message_view_move_buttons"/>
<include layout="@layout/message_view_bottom_buttons"/>
</LinearLayout>

View File

@ -0,0 +1,289 @@
<?xml version="1.0" encoding="utf-8"?>
<com.fsck.k9.view.MessageHeader
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/header_container"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- Message header area -->
<TableLayout
android:id="@+id/top_container"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:stretchColumns="1"
android:shrinkColumns="1"
android:background="@color/message_view_header_background">
<TableRow>
<!-- Color chip -->
<View
android:id="@+id/chip"
android:layout_marginRight="6dip"
android:layout_width="6dip"
android:layout_height="fill_parent"/>
<LinearLayout
android:paddingTop="2dip"
android:layout_column="1"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- Subject -->
<TextView
android:id="@+id/subject"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="3"
android:ellipsize="end"
android:textSize="10sp"
android:textStyle="bold"
android:textColor="?android:attr/textColorSecondary"
android:textAppearance="?android:attr/textAppearanceSmall"/>
<!-- From -->
<LinearLayout
android:id="@+id/from_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/from"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="6dip"
android:singleLine="true"
android:ellipsize="end"
android:textColor="?android:attr/textColorPrimary"
android:textAppearance="?android:attr/textAppearanceSmall"/>
</LinearLayout>
<!-- To -->
<LinearLayout
android:id="@+id/to_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:baselineAligned="true">
<TextView
android:id="@+id/to_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="4dip"
android:text="@string/message_to_label"
android:textSize="10sp"
android:textStyle="bold"
android:textColor="?android:attr/textColorSecondary"/>
<TextView
android:id="@+id/to"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="2"
android:ellipsize="end"
android:textSize="10sp"
android:textColor="?android:attr/textColorSecondary"
android:textAppearance="?android:attr/textAppearanceSmall"/>
</LinearLayout>
<!-- CC -->
<LinearLayout
android:id="@+id/cc_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:baselineAligned="true">
<TextView
android:id="@+id/cc_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="4dip"
android:text="@string/message_view_cc_label"
android:textSize="10sp"
android:textStyle="bold"
android:textColor="?android:attr/textColorSecondary"/>
<TextView
android:id="@+id/cc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="2"
android:ellipsize="end"
android:textSize="10sp"
android:textColor="?android:attr/textColorSecondary"
android:textAppearance="?android:attr/textAppearanceSmall"/>
</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>
<!-- Date/Time + Icons -->
<LinearLayout
android:id="@+id/topright_container"
android:layout_marginTop="2dip"
android:layout_marginRight="6dip"
android:orientation="vertical"
android:layout_height="wrap_content"
android:layout_marginLeft="6dp"
android:gravity="right">
<LinearLayout
android:id="@+id/icons_container"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical">
<View
android:id="@+id/answered"
android:layout_width="22sp"
android:layout_height="22sp"
android:paddingRight="4dip"
android:background="@drawable/ic_email_answered_small"/>
<CheckBox
android:id="@+id/flagged"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:focusable="false"
style="?android:attr/starStyle"/>
</LinearLayout>
<TextView
android:id="@+id/date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="none"
android:textSize="10sp"
android:textColor="?android:attr/textColorPrimary"
android:textAppearance="?android:attr/textAppearanceSmall"/>
<TextView
android:id="@+id/time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="10sp"
android:singleLine="true"
android:ellipsize="none"
android:textColor="?android:attr/textColorPrimary"
android:textAppearance="?android:attr/textAppearanceSmall"/>
</LinearLayout>
</TableRow>
</TableLayout>
<!-- Separator -->
<!-- This layout has an explicit height because otherwise there will be strange
display issues when the additional headers are shown. -->
<LinearLayout
android:id="@+id/show_additional_headers_area"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="21.5dp"
android:focusable="true"
android:clickable="true"
android:background="@drawable/separator_area_background">
<RelativeLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="20dp">
<!-- Color chip 2 -->
<View
android:id="@+id/chip2"
android:layout_marginRight="6dip"
android:layout_width="6dip"
android:layout_height="fill_parent"
android:layout_alignParentLeft="true"/>
<!-- Show more/less indicator -->
<ImageView
android:id="@+id/show_additional_headers_icon"
android:src="@drawable/show_more"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:layout_marginRight="12dp"/>
</RelativeLayout>
<View
android:id="@+id/separator"
android:layout_width="fill_parent"
android:layout_height="1.5dp"
android:background="#59000000"/>
</LinearLayout>
<!-- Button area -->
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="right"
android:paddingLeft="6dip"
android:paddingRight="6dip"
android:paddingTop="4dip"
android:baselineAligned="false">
<Button
android:id="@+id/show_pictures"
android:layout_width="wrap_content"
android:visibility="gone"
android:layout_marginLeft="6dip"
android:layout_marginBottom="4dip"
android:layout_height="wrap_content"
android:text="@string/message_view_show_pictures_action"
style="?android:attr/buttonStyleSmall"/>
<Button
android:id="@+id/show_attachments"
android:visibility="gone"
android:layout_marginLeft="6dip"
android:layout_marginBottom="4dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/message_view_show_attachments_action"
style="?android:attr/buttonStyleSmall"/>
<Button
android:id="@+id/show_message"
android:visibility="gone"
android:layout_marginLeft="6dip"
android:layout_marginBottom="4dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/message_view_show_message_action"
style="?android:attr/buttonStyleSmall"/>
</LinearLayout>
<include layout="@layout/message_view_crypto_layout"/>
</LinearLayout>
</com.fsck.k9.view.MessageHeader>

View File

@ -1,88 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
android:id="@+id/extra_buttons"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingBottom="2dip"
android:gravity="center_vertical">
<Button
android:id="@+id/reply"
android:text="@string/reply_action"
android:layout_height="wrap_content"
android:layout_width="0dip"
android:layout_weight="1"/>
<Button
android:id="@+id/reply_all"
android:text="@string/reply_all_action"
android:layout_height="wrap_content"
android:layout_width="0dip"
android:layout_weight="1"/>
<Button
android:id="@+id/forward"
android:text="@string/forward_action"
android:layout_height="wrap_content"
android:layout_width="0dip"
android:layout_weight="1"/>
</LinearLayout>
<LinearLayout
android:id="@+id/scrolling_move_buttons"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingBottom="2dip"
android:gravity="center_vertical">
<Button
android:id="@+id/archive_scrolling"
android:text="@string/message_view_archive_action"
android:layout_height="wrap_content"
android:layout_width="0dip"
android:layout_weight="1"/>
<Button
android:id="@+id/move_scrolling"
android:text="@string/message_view_move_action"
android:layout_height="wrap_content"
android:layout_width="0dip"
android:layout_weight="1"/>
<Button
android:id="@+id/spam_scrolling"
android:text="@string/message_view_spam_action"
android:layout_height="wrap_content"
android:layout_width="0dip"
android:layout_weight="1"/>
</LinearLayout>
<LinearLayout
android:id="@+id/scrolling_buttons"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical">
<Button
android:id="@+id/previous_scrolling"
android:text="@string/message_view_prev_action"
android:contentDescription="@string/previous_action"
android:textSize="35dip"
android:padding="0dip"
android:layout_height="wrap_content"
android:layout_width="0dip"
android:layout_weight="1"/>
<ImageButton
android:id="@+id/delete_scrolling"
android:layout_height="wrap_content"
android:src="@drawable/ic_button_delete"
android:layout_width="0dip"
android:layout_weight="1"/>
<Button
android:id="@+id/next_scrolling"
android:padding="0dip"
android:text="@string/message_view_next_action"
android:textSize="35dip"
android:layout_height="wrap_content"
android:layout_width="0dip"
android:layout_weight="1"/>
</LinearLayout>
</merge>

View File

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/unread_widget_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="2dp"
android:orientation="vertical"
android:clickable="true"
android:focusable="true"
android:background="@drawable/unread_widget_background"
android:gravity="bottom|center_horizontal">
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:layout_width="@android:dimen/app_icon_size"
android:layout_height="@android:dimen/app_icon_size"
android:scaleType="fitCenter"
android:src="@drawable/icon" />
<TextView
android:id="@+id/unread_count"
android:visibility="gone"
android:textSize="10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right|bottom"
android:paddingTop="0.5dp"
android:paddingBottom="0.5dp"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:background="@drawable/unread_count_background"
android:textColor="#ffffff"/>
</FrameLayout>
<TextView
android:id="@+id/account_name"
android:text="@string/app_name"
android:ellipsize="marquee"
android:textSize="12dp"
android:singleLine="true"
android:paddingTop="1dp"
android:paddingBottom="1dp"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="3dp"
android:background="@drawable/rounded_corners"
android:textColor="#ffffff"
android:shadowColor="#000000"
android:shadowRadius="2.0"/>
</LinearLayout>

View File

@ -542,14 +542,8 @@ Benvingut a la configuració del K-9. El K-9 és un client de codi obert per An
<string name="account_settings_notification_unread_count_label">Mostra correu no llegit</string>
<string name="account_settings_notification_unread_count_summary">Mostra el nombre de missatges no llegits a la barra de notificació.</string>
<string name="account_settings_hide_buttons_label">Botons de desplaçament</string>
<string name="account_settings_hide_buttons_never">Mai</string>
<string name="account_settings_hide_buttons_keyboard_avail">Quan el teclat estigui disponible</string>
<string name="account_settings_hide_buttons_always">Sempre</string>
<string name="account_settings_enable_move_buttons_label">Habilita botons emplenat</string>
<string name="account_settings_enable_move_buttons_summary">Mostra els botons dArxiu, Mou, i Brossa.</string>
<string name="account_settings_hide_move_buttons_label">Navega botons emplenament</string>
<string name="account_settings_show_pictures_label">Sempre mostra imatges</string>
<string name="account_settings_show_pictures_never">No</string>

View File

@ -549,14 +549,8 @@ Vítejte v nastavení pošty K-9 Mail. K-9 je open source poštovní klient pro
<!-- NEW: <string name="account_settings_notification_unread_count_label">Show unread count</string>-->
<!-- NEW: <string name="account_settings_notification_unread_count_summary">Show the number of unread messages in the notification bar.</string>-->
<string name="account_settings_hide_buttons_label">Posunovat navigační tlačítka</string>
<string name="account_settings_hide_buttons_never">Nikdy</string>
<string name="account_settings_hide_buttons_keyboard_avail">Je-li dostupná klávesnice</string>
<string name="account_settings_hide_buttons_always">Vždy</string>
<string name="account_settings_enable_move_buttons_label">Povolit přesměrovací tlačítka</string>
<string name="account_settings_enable_move_buttons_summary">Zobrazit tlačítka Archív, Přesunout a Nevyžádaná.</string>
<string name="account_settings_hide_move_buttons_label">Posunovat přesměrovací tlačítka</string>
<string name="account_settings_show_pictures_label">Vždy zobrazovat obrázky</string>
<string name="account_settings_show_pictures_never">Ne</string>

View File

@ -545,14 +545,8 @@ Velkommen til K-9 Mail opsætning. K-9 er en open source mail klient for Androi
<string name="account_settings_notification_unread_count_label">Vis antal ulæste mails</string>
<string name="account_settings_notification_unread_count_summary">Vis antallet af ulæste mails i statusbar.</string>
<string name="account_settings_hide_buttons_label">Scroll navigations knapper</string>
<string name="account_settings_hide_buttons_never">Aldrig</string>
<string name="account_settings_hide_buttons_keyboard_avail">Når tastatur er tilgængeligt</string>
<string name="account_settings_hide_buttons_always">Altid</string>
<string name="account_settings_enable_move_buttons_label">Aktiver Flyt knapper</string>
<string name="account_settings_enable_move_buttons_summary">Vis Arkiver, Flyt og Spam knapper.</string>
<string name="account_settings_hide_move_buttons_label">Scroll Flyt knapper</string>
<string name="account_settings_show_pictures_label">Vis altid billeder</string>
<string name="account_settings_show_pictures_never">Aldrig</string>

View File

@ -285,6 +285,8 @@ 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_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>
<string name="message_view_no_viewer">Es wurde kein Anzeigeprogramm für <xliff:g id="mimetype">%s</xliff:g> gefunden.</string>
@ -543,14 +545,8 @@ Willkommen zum \"K-9 Mail\"-Setup. K-9 ist eine quelloffene E-Mail-Anwendung fü
<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_hide_buttons_label">Scrolle Navigationsleiste</string>
<string name="account_settings_hide_buttons_never">Leiste bleibt eingeblendet</string>
<string name="account_settings_hide_buttons_keyboard_avail">Nur wenn Tastatur aktiv</string>
<string name="account_settings_hide_buttons_always">Immer</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>
<string name="account_settings_hide_move_buttons_label">Scrolle Spam-Leiste</string>
<string name="account_settings_show_pictures_label">Bilder automatisch anzeigen</string>
<string name="account_settings_show_pictures_never">Niemals</string>
@ -564,15 +560,15 @@ Willkommen zum \"K-9 Mail\"-Setup. K-9 ist eine quelloffene E-Mail-Anwendung fü
<string name="account_settings_reply_after_quote_label">Antwort unter Zitat</string>
<string name="account_settings_reply_after_quote_summary">Die Antwort auf eine Nachricht unterhalb der Original-Nachricht platzieren.</string>
<string name="account_settings_strip_signature_label">Unterschrift von zitierter Antwort entfernen</string>
<string name="account_settings_strip_signature_summary">Beim Antworten von Nachrichten wird die Unterschrift vom zitiertem Textabschnitt entfernt</string>
<string name="account_settings_strip_signature_label">Entferne Signatur aus zitierter Antwort</string>
<string name="account_settings_strip_signature_summary">Beim Antworten wird die Signatur aus dem Zitat entfernt</string>
<string name="account_settings_message_format_label">Formatierung</string>
<string name="account_settings_message_format_text">Einfacher Text (Bilder und Formatierungen werden entfernt)</string>
<string name="account_settings_message_format_html">HTML (Bilder und Formatierungen bleiben erhalten)</string>
<string name="account_settings_message_format_auto">Automatisch (Einfacher Text, es sei denn bei Antwort auf HTML)</string>
<string name="account_settings_message_format_auto">Automatisch (Einfacher Text, es sei denn bei Antwort auf HTML)</string>
<string name="account_settings_message_read_receipt_label">Empfangsbestätigung</string>
<string name="account_settings_message_read_receipt_summary">Immer eine Empfangsbestätigung anfordern</string>
@ -595,11 +591,11 @@ Willkommen zum \"K-9 Mail\"-Setup. K-9 ist eine quelloffene E-Mail-Anwendung fü
<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_encrypt_summary">Verschlüsselung aktivieren falls für den Empfänger ein öffentlichen Schlüssel abgespeichert 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>
<string name="account_settings_storage_title">Speicher</string>
<string name="account_settings_color_label">Farbe des Kontos</string>
@ -1022,10 +1018,10 @@ 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="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>
<string name="continue_without_public_key_dlg_title">Ohne öffentlichen Schlüssel fortfahren?</string>
<string name="continue_without_public_key_instructions_fmt">Einer oder mehrere Empfänger besitzen keinen abgespeicherten öffentlichen Schlüssel. Fortfahren?</string>
@ -1053,7 +1049,7 @@ Willkommen zum \"K-9 Mail\"-Setup. K-9 ist eine quelloffene E-Mail-Anwendung fü
<string name="messagelist_sent_to_me_sigil">»</string>
<string name="messagelist_sent_cc_me_sigil"></string>
<string name="error_unable_to_connect">Verbindungsfehler.</string>
<string name="import_export_action">Einstellungen Importieren &amp; Exportieren</string>
<string name="settings_export_account">Kontoeinstellungen exportieren</string>
<string name="settings_export_all">Einstellungen und Konten exportieren</string>
@ -1104,5 +1100,5 @@ Willkommen zum \"K-9 Mail\"-Setup. K-9 ist eine quelloffene E-Mail-Anwendung fü
<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>
</resources>

View File

@ -541,14 +541,8 @@ Bienvenido a la Configuración de K-9. K-9 es un cliente de correo OpenSource pa
<string name="account_settings_notification_unread_count_label">Mostrar contador de correos sin leer</string>
<string name="account_settings_notification_unread_count_summary">Mostrar el número de mensajes sin leer en la barra de notificaciones.</string>
<string name="account_settings_hide_buttons_label">Botones de desplazamiento</string>
<string name="account_settings_hide_buttons_never">Nunca</string>
<string name="account_settings_hide_buttons_keyboard_avail">Cuando el teclado está disponible</string>
<string name="account_settings_hide_buttons_always">Siempre</string>
<string name="account_settings_enable_move_buttons_label">Habilitar botones de copia</string>
<string name="account_settings_enable_move_buttons_summary">Mostrar botones de archivar, mover y SPAM</string>
<string name="account_settings_hide_move_buttons_label">Botones de copia</string>
<string name="account_settings_show_pictures_label">Mostrar imágenes</string>
<string name="account_settings_show_pictures_never">Nunca</string>

View File

@ -537,14 +537,8 @@ Tervetuloa K-9 Mail asennukseen.  K-9 on avoimen lähdekoodin sähköpostiasiak
<string name="account_settings_notification_unread_count_label">Näytä lukemattomien viestien määrä</string>
<string name="account_settings_notification_unread_count_summary">Näytä lukemattomien viestien määrä osoiterivillä.</string>
<string name="account_settings_hide_buttons_label">Selauksen painikkeet</string>
<string name="account_settings_hide_buttons_never">Ei koskaan</string>
<string name="account_settings_hide_buttons_keyboard_avail">Kun näppäimistö on käytettävissä</string>
<string name="account_settings_hide_buttons_always">Aina</string>
<string name="account_settings_enable_move_buttons_label">Ota käyttöön Siirrä-painikkeet</string>
<string name="account_settings_enable_move_buttons_summary">Näytä Arkistoi-, Siirrä- ja Roskaposti-painikkeet.</string>
<string name="account_settings_hide_move_buttons_label">Vieritä Siirrä painikkeet</string>
<string name="account_settings_show_pictures_label">Näytä kuvat automaattisesti</string>
<string name="account_settings_show_pictures_never">Ei koskaan</string>

View File

@ -567,14 +567,8 @@ Bienvenue dans la configuration de K-9 Mail. K-9 Mail est un client de messageri
<string name="account_settings_notification_unread_count_label">Afficher le nombre de messages non lus</string>
<string name="account_settings_notification_unread_count_summary">Afficher le nombre de messages non lus dans la barre de notification</string>
<string name="account_settings_hide_buttons_label">Défiler les boutons de navigation</string>
<string name="account_settings_hide_buttons_never">Jamais</string>
<string name="account_settings_hide_buttons_keyboard_avail">Lorsque le clavier est disponible</string>
<string name="account_settings_hide_buttons_always">Toujours</string>
<string name="account_settings_enable_move_buttons_label">Activer les boutons de déplacement</string>
<string name="account_settings_enable_move_buttons_summary">Afficher les boutons Archiver, Déplacer ou Spam</string>
<string name="account_settings_hide_move_buttons_label">Défiler les boutons de déplacement</string>
<string name="account_settings_show_pictures_label">Afficher automatiquement les images</string>
<string name="account_settings_show_pictures_never">Jamais</string>
@ -589,13 +583,13 @@ Bienvenue dans la configuration de K-9 Mail. K-9 Mail est un client de messageri
<string name="account_settings_reply_after_quote_label">Réponse après la citation</string>
<string name="account_settings_reply_after_quote_summary">Faire précéder la réponse par le texte d\'origine</string>
<string name="account_settings_strip_signature_label">Retirer signature dans réponse citée</string>
<string name="account_settings_strip_signature_label">Retirer signature dans réponse citée</string>
<string name="account_settings_strip_signature_summary">Lorsque vous répondez à des messages, la signature du texte cité sera éffacée</string>
<string name="account_settings_message_format_label">Format du message</string>
<string name="account_settings_message_format_html">HTML (formattage et images conservés)</string>
<string name="account_settings_message_format_text">Text brut (formattage et images omis)</string>
<string name="account_settings_message_format_auto">Automatique (Text brut à moins de répondre à un message HTML)</string>
<string name="account_settings_message_format_auto">Automatique (Text brut à moins de répondre à un message HTML)</string>
<string name="account_settings_message_read_receipt_label">Accusé de réception</string>
<string name="account_settings_message_read_receipt_summary">Toujours demander un accusé de réception</string>
@ -617,9 +611,9 @@ Bienvenue dans la configuration de K-9 Mail. K-9 Mail est un client de messageri
<string name="account_settings_crypto_app_not_available">Non disponible</string>
<string name="account_settings_crypto_auto_signature">Signature automatique</string>
<string name="account_settings_crypto_auto_signature_summary">Utiliser l\'adresse e-mail du compte pour déduire la clé de signature</string>
<string name="account_settings_crypto_auto_encrypt">Cryptation automatique</string>
<string name="account_settings_crypto_auto_encrypt_summary">Cryptation automatique si une clé publique correspond au destinataire.</string>
<string name="account_settings_crypto_auto_encrypt">Cryptation automatique</string>
<string name="account_settings_crypto_auto_encrypt_summary">Cryptation automatique si une clé publique correspond au destinataire.</string>
<string name="account_settings_mail_check_frequency_label">Fréquence de vérification du dossier</string>
<string name="account_settings_second_class_check_frequency_label">Fréquence de vérification pour 2ème classe</string>
@ -1045,12 +1039,12 @@ Bienvenue dans la configuration de K-9 Mail. K-9 Mail est un client de messageri
<string name="save_or_discard_draft_message_dlg_title">Enregistrer le brouillon\u00A0?</string>
<string name="save_or_discard_draft_message_instructions_fmt">Enregistrer ou abandonner ce message\u00A0?</string>
<string name="refuse_to_save_draft_marked_encrypted_dlg_title">Refuser d\'enregistrer des brouillons.</string>
<string name="refuse_to_save_draft_marked_encrypted_instructions_fmt">Refuser d\'enregistrer un message marqué comme crypté.</string>
<string name="continue_without_public_key_dlg_title">Continuer sans clé publique?</string>
<string name="continue_without_public_key_instructions_fmt">Un ou plusieurs destinataires n\'ont pas de clé publique enregistrée. Continuer?</string>
<string name="refuse_to_save_draft_marked_encrypted_dlg_title">Refuser d\'enregistrer des brouillons.</string>
<string name="refuse_to_save_draft_marked_encrypted_instructions_fmt">Refuser d\'enregistrer un message marqué comme crypté.</string>
<string name="continue_without_public_key_dlg_title">Continuer sans clé publique?</string>
<string name="continue_without_public_key_instructions_fmt">Un ou plusieurs destinataires n\'ont pas de clé publique enregistrée. Continuer?</string>
<string name="charset_not_found">Ce message ne peut être affiché parce que le jeu de caractères «\u00A0<xliff:g id="charset">%s</xliff:g>\u00A0» n\'a pu être trouvé.</string>
<string name="select_text_now">Sélectionnez le texte à copier</string>

View File

@ -541,14 +541,8 @@ Benvido á Configuración de K-9. K-9 é un cliente de correo OpenSource para An
<string name="account_settings_notification_unread_count_label">Amosar número de mensaxes non lidos</string>
<string name="account_settings_notification_unread_count_summary">Amosar número de mensaxen non lidos na barra de notificacións.</string>
<string name="account_settings_hide_buttons_label">Botóns de desprazamento</string>
<string name="account_settings_hide_buttons_never">Nunca</string>
<string name="account_settings_hide_buttons_keyboard_avail">Cando o teclado esté dispoñible</string>
<string name="account_settings_hide_buttons_always">Sempre</string>
<string name="account_settings_enable_move_buttons_label">Habilitar botóns de copia</string>
<string name="account_settings_enable_move_buttons_summary">Amosar botóns de arquivar, mover e SPAM</string>
<string name="account_settings_hide_move_buttons_label">Botóns de copia</string>
<string name="account_settings_show_pictures_label">Amosar imaxes</string>
<string name="account_settings_show_pictures_never">Nunca</string>

View File

@ -154,7 +154,7 @@
<string name="special_mailbox_name_spam_fmt">%s</string>
<string name="send_failure_subject">Néhány üzenetet nem sikerült elküldeni</string>
<string name="send_failure_body_abbrev">Nézze meg a %s mappát a részletekért.</string>
<string name="send_failure_body_fmt">"A K-9 hibát észlelt üzenetküldés közben. A probléma miatt elképzelhető, hogy a levél nem érkezik meg, de az is lehet hogy igen.
<string name="send_failure_body_fmt">"A K-9 hibát észlelt üzenetküldés közben. A probléma miatt elképzelhető, hogy a levél nem érkezik meg, de az is lehet hogy igen.
Az ilyen hibás leveleket csillag jelzi a postázandó mappában. Ha eltávolítja a csillagot a K-9 megpróbálja újból elküldeni a levelet. Hosszan a Postázatlan mappára kattintva válassza az Üzenetek küldését.
@ -162,26 +162,26 @@ Az ilyen hibás leveleket csillag jelzi a postázandó mappában. Ha eltávolít
<string name="alert_header">K-9 riasztás</string>
<string name="no_connection_alert">A szinkronizáció és a levélküldés felfüggesztve, hálozati kapcsolat hiánya miatt.</string>
<string name="end_of_folder">Nincs több üzenet</string>
<string name="accounts_welcome">"Üdvözöljük a K-9 Mail beállításokban. A K-9 egy nyílt forráskódú levelezőprogram Androidra, a sima mail kliens alapjaira helyezve.
K-9 továbbfejlesztett funkciói:
* Push mail using IMAP IDLE
* Jobb teljesítmény
* Message refiling
* E-mail aláírások
* Titkos másolat
* Mappa-előfizetések
* Minden mappa szinkronizálása
* Válasz cím beállítása
* Gyorsbillentyűk
* Jobb IMAP támogatás
* Mellékletek mentése a memóriakártyára
* Kuka ürítése
* Üzenetek válogatás
<string name="accounts_welcome">"Üdvözöljük a K-9 Mail beállításokban. A K-9 egy nyílt forráskódú levelezőprogram Androidra, a sima mail kliens alapjaira helyezve.
K-9 továbbfejlesztett funkciói:
* Push mail using IMAP IDLE
* Jobb teljesítmény
* Message refiling
* E-mail aláírások
* Titkos másolat
* Mappa-előfizetések
* Minden mappa szinkronizálása
* Válasz cím beállítása
* Gyorsbillentyűk
* Jobb IMAP támogatás
* Mellékletek mentése a memóriakártyára
* Kuka ürítése
* Üzenetek válogatás
* ...és még sok más
Magyarította: Deák Tamás (maya98) és RootRulez
Magyarította: Deák Tamás (maya98) és RootRulez
Vegye figyelembe, hogy a K-9 nem támogatja a legtöbb ingyenes hotmail fiókot és még sok más klienst. Történnek furcsaságok, ha Microsoft Exchange-el kommunikál.
Hibajelentéseivel hozzájárul az újabb verziók tökéletesítéséhez, kérdéseit itt teheti fel http://k9mail.googlecode.com/"</string>
<string name="debug_version_fmt">Verzió: %s</string>
@ -218,7 +218,7 @@ Vegye figyelembe, hogy a K-9 nem támogatja a legtöbb ingyenes hotmail fiókot
<string name="message_compose_quoted_text_label">Idézet</string>
<string name="message_compose_error_no_recipients">Legalább egy címzetted adjon meg.</string>
<string name="error_contact_address_not_found">E-mail cím nem található.</string>
<string name="message_compose_downloading_attachments_toast">Néhány melléklet nem lett letöltve. Levélküldés előtt automatikusan letöltődnek.</string>
<string name="message_compose_downloading_attachments_toast">Néhány melléklet nem lett letöltve. Levélküldés előtt automatikusan letöltődnek.</string>
<string name="message_compose_attachments_skipped_toast">Néhány mellékletet nem lehet továbbítani, mert nem lettek letöltve.</string>
<string name="message_view_from_format" formatted="false">Feladó: %s &lt;%s></string>
<string name="message_to_label">Címzett:</string>
@ -278,7 +278,7 @@ Vegye figyelembe, hogy a K-9 nem támogatja a legtöbb ingyenes hotmail fiókot
<string name="global_settings_confirm_action_delete">Törlés (csak üzenetek nézet)</string>
<string name="global_settings_confirm_action_spam">Levélszemét</string>
<string name="global_settings_confirm_action_mark_all_as_read">Összes megjelölése olvasottként</string>
<string name="global_settings_confirm_action_send">Küldés</string>
<string name="global_settings_confirm_action_send">Küldés</string>
<string name="global_settings_privacy_mode_title">Képernyőzár értesítések</string>
<string name="global_settings_privacy_mode_summary">Lezárt képernyőnél ne mutassa a levelek tárgyát a állapotsoron</string>
<string name="quiet_time">Csendes mód</string>
@ -450,13 +450,8 @@ Levelek letöltése…"</string>
<string name="account_settings_notification_opens_unread_summary">Értesítésre kattintva megnyitja az olvasatlan üzeneteket</string>
<string name="account_settings_notification_unread_count_label">Olvasatlanok kijelzése</string>
<string name="account_settings_notification_unread_count_summary">Olvasatlan levelek száma az állapotsoron.</string>
<string name="account_settings_hide_buttons_label">Görgetés iránygombokkal</string>
<string name="account_settings_hide_buttons_never">Soha</string>
<string name="account_settings_hide_buttons_keyboard_avail">Ha van billentyűzet</string>
<string name="account_settings_hide_buttons_always">Mindig</string>
<string name="account_settings_enable_move_buttons_label">Művelet gombok megjelenítése</string>
<string name="account_settings_enable_move_buttons_summary">Mutassa a Mozgatás, Archív és Levélszemét gombokat</string>
<string name="account_settings_hide_move_buttons_label">Művelet gombok görgetése</string>
<string name="account_settings_show_pictures_label">Képek megjelenítése</string>
<string name="account_settings_show_pictures_never">Soha</string>
<string name="account_settings_show_pictures_only_from_contacts">Csak az ismerősökét</string>
@ -611,7 +606,7 @@ Levelek letöltése…"</string>
<string name="account_settings_signature_use_label">Aláírás használata</string>
<string name="account_settings_signature_label">Aláírás</string>
<string name="account_settings_signature_summary">Az aláírás hozzáfűzése minden elküldött levélhez</string>
<string name="default_signature">"--
<string name="default_signature">"--
Ezt a levelet mobiltelefonról küldtem, K-9 Mail-el. Elnézést a levél rövidségéért és az esetleges hibákért."</string>
<string name="default_identity_description">Elsődleges személyazonosságom</string>
<string name="choose_identity">Személyazonosság választása</string>
@ -654,7 +649,7 @@ Ezt a levelet mobiltelefonról küldtem, K-9 Mail-el. Elnézést a levél rövid
<string name="provider_note_hanmail">Ha POP3-at vagy IMAP-ot szeretne használni ehhez a szolgáltatóhoz, akkor engedélyeznie kell az IMAP vagy POP3 beállításokat a Hanmail(Daum) oldalán.</string>
<string name="provider_note_paran">Ha POP3-at vagy IMAP-ot szeretne használni ehhez a szolgáltatóhoz, akkor engedélyeznie kell az IMAP vagy POP3 beállításokat a Paran oldalán.</string>
<string name="provider_note_nate">Ha POP3-at vagy IMAP-ot szeretne használni ehhez a szolgáltatóhoz, akkor engedélyeznie kell az IMAP vagy POP3 beállításokat a Nate oldalán.</string>
<string name="account_setup_failed_dlg_invalid_certificate_title">Felismerhetetlen tanúsítvány</string>
<string name="account_setup_failed_dlg_invalid_certificate_title">Felismerhetetlen tanúsítvány</string>
<string name="account_setup_failed_dlg_invalid_certificate_accept">Kulcs elfogadva</string>
<string name="account_setup_failed_dlg_invalid_certificate_reject">Kulcs elutasítva</string>
<string name="message_help_key">"Del (or D) - Delete
@ -752,7 +747,7 @@ S - Select/deselect"</string>
<string name="gestures_summary">Engedélyezi a kézmozdulakkal való vezérlést.</string>
<string name="compact_layouts_title">Kompakt elrendezés</string>
<string name="compact_layouts_summary">Adjust layouts to display more on each page</string>
<string name="volume_navigation_title">Hangerő gomb vezérlés</string>
<string name="volume_navigation_title">Hangerő gomb vezérlés</string>
<string name="volume_navigation_summary">Léptetés az hangerő gombokkal</string>
<string name="volume_navigation_message">Leveleknél</string>
<string name="volume_navigation_list">Lista nézetek váltása</string>
@ -766,7 +761,7 @@ S - Select/deselect"</string>
<string name="count_search_summary">Kikcsapcsolva gyorsabb műkodés</string>
<string name="hide_special_accounts_title">Különleges fiókok elrejtése</string>
<string name="hide_special_accounts_summary">Egységesen elrejti a fiókok bejövő mappáit</string>
<string name="search_title" formatted="false">%s %s</string>
<string name="search_title" formatted="false">%s %s</string>
<string name="flagged_modifier">- Csillagos</string>
<string name="unread_modifier">- Olvasatlan</string>
<string name="search_all_messages_title">Minden levél</string>
@ -846,11 +841,11 @@ S - Select/deselect"</string>
<string name="dialog_confirm_spam_confirm_button">Igen</string>
<string name="dialog_confirm_spam_cancel_button">Nem</string>
<string name="dialog_attachment_progress_title">Csatolmányok letöltése</string>
<string name="debug_logging_enabled">Hibakereső naplózás bekapcsolva</string>
<string name="debug_logging_enabled">Hibakereső naplózás bekapcsolva</string>
<string name="messagelist_sent_to_me_sigil">»</string>
<string name="messagelist_sent_cc_me_sigil"></string>
<string name="error_unable_to_connect">Nem lehet kapcsolódni.</string>
<string name="account_unavailable"> \"%s\" fiók nem elérhető ellenőríze a tárhelyet</string>
<string name="account_unavailable"> \"%s\" fiók nem elérhető ellenőríze a tárhelyet</string>
<string name="settings_attachment_default_path">Csatolményok mentése ide:</string>
<string name="attachment_save_title">Csatolményok mentése</string>
<string name="attachment_save_desc">Nincs fájlkezelő. Hova szeretné mentni a csatolmányt?</string>

View File

@ -543,14 +543,8 @@ Benvenuto nella configurazione della posta di K-9. K-9 è un client di posta ope
<string name="account_settings_notification_unread_count_label">Vedi numero messaggi non letti</string>
<string name="account_settings_notification_unread_count_summary">Mostra il numero dei messaggi non letti nella barra di notifica.</string>
<string name="account_settings_hide_buttons_label">Scorri pulsanti navigazione</string>
<string name="account_settings_hide_buttons_never">Mai</string>
<string name="account_settings_hide_buttons_keyboard_avail">Quando la tastiera è disponibile</string>
<string name="account_settings_hide_buttons_always">Sempre</string>
<string name="account_settings_enable_move_buttons_label">Abilita pulsanti archiviazione</string>
<string name="account_settings_enable_move_buttons_summary">Mostra i pulsanti Archivia, Sposta e Spam.</string>
<string name="account_settings_hide_move_buttons_label">Scorri pulsanti archiviazione</string>
<string name="account_settings_show_pictures_label">Mostra sempre le immagini</string>
<string name="account_settings_show_pictures_never">No</string>

View File

@ -71,6 +71,7 @@
<string name="send_messages_action">メール送信</string>
<string name="list_folders_action">フォルダ一覧</string>
<string name="refresh_folders_action">フォルダ再読込</string>
<string name="filter_folders_action">フォルダを探す</string>
<string name="mark_all_as_read_action">すべてのメールを既読にする</string>
<string name="add_account_action">アカウント追加</string>
<string name="compose_action">作成</string>
@ -544,14 +545,8 @@ K-9 Mail セットアップにようこそ。\nK-9 は標準のAndroidメール
<string name="account_settings_notification_unread_count_label">未読件数の表示</string>
<string name="account_settings_notification_unread_count_summary">通知バーに未読メッセージの件数を表示する</string>
<string name="account_settings_hide_buttons_label">ナビゲーションボタンのスクロール</string>
<string name="account_settings_hide_buttons_never">しない</string>
<string name="account_settings_hide_buttons_keyboard_avail">キーボード利用時</string>
<string name="account_settings_hide_buttons_always">常に</string>
<string name="account_settings_enable_move_buttons_label">メール整理ボタンを有効にする</string>
<string name="account_settings_enable_move_buttons_summary">アーカイブ、移動、迷惑メールボタンを表示</string>
<string name="account_settings_hide_move_buttons_label">メール整理ボタンをスクロール</string>
<string name="account_settings_show_pictures_label">画像を自動で表示</string>
<string name="account_settings_show_pictures_never">しない</string>
@ -822,6 +817,8 @@ K-9 Mail セットアップにようこそ。\nK-9 は標準のAndroidメール
Q - アカウント設定に戻る\u000A
S - アカウント設定編集</string>
<string name="folder_list_filter_hint">フォルダ名に含まれる文字</string>
<string name="folder_list_display_mode_label">フォルダ表示</string>
<string name="folder_list_display_mode_all">全フォルダ表示</string>
<string name="folder_list_display_mode_first_class">1st クラスフォルダ表示</string>
@ -973,6 +970,9 @@ K-9 Mail セットアップにようこそ。\nK-9 は標準のAndroidメール
<string name="font_size_message_view_date">日付</string>
<string name="font_size_message_view_content">本文</string>
<string name="font_size_message_compose">メッセージ作成</string>
<string name="font_size_message_compose_input">入力テキスト</string>
<string name="font_size_tiniest">極小</string>
<string name="font_size_tiny">かなり小</string>
<string name="font_size_smaller">やや小</string>
@ -1014,6 +1014,9 @@ K-9 Mail セットアップにようこそ。\nK-9 は標準のAndroidメール
<string name="save_or_discard_draft_message_dlg_title">メッセージの下書き保存</string>
<string name="save_or_discard_draft_message_instructions_fmt">メッセージを保存しますか?</string>
<string name="confirm_discard_draft_message_title">メッセージ破棄?</string>
<string name="confirm_discard_draft_message">このメッセージを破棄しますか?</string>
<string name="refuse_to_save_draft_marked_encrypted_dlg_title">下書き保存の拒否</string>
<string name="refuse_to_save_draft_marked_encrypted_instructions_fmt">暗号化したメッセージは下書き保存できません</string>
@ -1102,4 +1105,11 @@ K-9 Mail セットアップにようこそ。\nK-9 は標準のAndroidメール
<string name="manage_accounts_move_down_action">下に移動</string>
<string name="manage_accounts_moving_message">アカウントを移動しています</string>
<string name="unread_widget_label">未読件数</string>
<string name="unread_widget_select_account">アカウントの未読件数を表示</string>
<string name="import_dialog_error_title">ファイルマネージャがありません</string>
<string name="import_dialog_error_message">設定をインポートするためのアプリケーションがありません。Androidマーケットからファイルマネージャをインストールしてください。</string>
<string name="open_market">マーケット</string>
<string name="close">閉じる</string>
</resources>

View File

@ -542,14 +542,8 @@ K-9 메일 설치를 환영합니다. K-9은 표준 안드로이드 메일 클
<string name="account_settings_notification_unread_count_label">읽지 않은 메일 수 세기</string>
<string name="account_settings_notification_unread_count_summary">읽지 않은 메시지의 수를 상태바에 보여줍니다.</string>
<string name="account_settings_hide_buttons_label">네비게이션 버튼을 스크롤</string>
<string name="account_settings_hide_buttons_never">하지않음</string>
<string name="account_settings_hide_buttons_keyboard_avail">키보드를 이용가능할 경우</string>
<string name="account_settings_hide_buttons_always">항상</string>
<string name="account_settings_enable_move_buttons_label">재정리(refile) 버튼 활성화</string>
<string name="account_settings_enable_move_buttons_summary">보관, 이동, 스팸 버튼을 보이기</string>
<string name="account_settings_hide_move_buttons_label">재정리(refile) 보튼을 스크롤</string>
<string name="account_settings_show_pictures_label">항상 그림 보기</string>
<string name="account_settings_show_pictures_never">아니오</string>

View File

@ -541,14 +541,8 @@ Welkom bij K-9 Mail setup. K-9 is een open source mail cliënt voor Android, ge
<string name="account_settings_notification_unread_count_label">Toon aantal ongelezen</string>
<string name="account_settings_notification_unread_count_summary">Toon het aantal ongelezen berichten in de \'notification bar\'.</string>
<string name="account_settings_hide_buttons_label">Scroll navigatie knoppen</string>
<string name="account_settings_hide_buttons_never">Nooit</string>
<string name="account_settings_hide_buttons_keyboard_avail">Wanneer toetsenbord beschikbaar is</string>
<string name="account_settings_hide_buttons_always">Altijd</string>
<string name="account_settings_enable_move_buttons_label">Inschakelen verplaats knoppen</string>
<string name="account_settings_enable_move_buttons_summary">Laat de Archief, Verplaats, en Spam knoppen zien.</string>
<string name="account_settings_hide_move_buttons_label">Scroll verplaats knoppen</string>
<string name="account_settings_show_pictures_label">Laat afbeeldingen automatisch zien</string>
<string name="account_settings_show_pictures_never">Nooit</string>

View File

@ -553,14 +553,8 @@ Witaj w K-9 Mail, darmowym programie pocztowym dla systemu Android. Najistotniej
<string name="account_settings_notification_unread_count_label">Pokaż liczbę nieprzeczytanych</string>
<string name="account_settings_notification_unread_count_summary">Pokaż liczbę nieprzeczytanych wiadomości w pasku powiadomień.</string>
<string name="account_settings_hide_buttons_label">Przyciski nawigacyjne</string>
<string name="account_settings_hide_buttons_never">Przyciski nie są nigdy przesuwalne</string>
<string name="account_settings_hide_buttons_keyboard_avail">Przesuwalne, gdy jest klawiatura</string>
<string name="account_settings_hide_buttons_always">Przyciski są zawsze przesuwalne</string>
<string name="account_settings_enable_move_buttons_label">Użyj przycisków refile </string> <!-- FIXME -->
<string name="account_settings_enable_move_buttons_summary">Pokaż przyciski Archiwum, Przenieś, Spam.</string>
<string name="account_settings_hide_move_buttons_label">Przewijaj przyciski refile </string> <!-- FIXME -->
<string name="account_settings_show_pictures_label">Zawsze pokazuj obrazki</string>
<string name="account_settings_show_pictures_never">Nie</string>

View File

@ -539,14 +539,8 @@ Bem-vindo à configuração do K-9 Mail. K-9 é um cliente de e-mail com código
<string name="account_settings_notification_unread_count_label">Mostrar contagem de não lidas</string>
<string name="account_settings_notification_unread_count_summary">Mostrar o número de mensagens não lidas na barra de notificação.</string>
<string name="account_settings_hide_buttons_label">Navegação com botões de scroll</string>
<string name="account_settings_hide_buttons_never">Nunca</string>
<string name="account_settings_hide_buttons_keyboard_avail">Quando teclado estiver disponível</string>
<string name="account_settings_hide_buttons_always">Sempre</string>
<string name="account_settings_enable_move_buttons_label">Habilitar botão de ações</string>
<string name="account_settings_enable_move_buttons_summary">Mostrar botões de Arquivar, Mover e Span.</string>
<string name="account_settings_hide_move_buttons_label">Scroll para botões de ação </string>
<string name="account_settings_show_pictures_label">Sempre mostrar imagens</string>
<string name="account_settings_show_pictures_never">Não</string>

View File

@ -533,14 +533,8 @@
<string name="account_settings_notification_unread_count_label">Показать количество непрочитанных</string>
<string name="account_settings_notification_unread_count_summary">Показать количество непрочитанных в строке уведомлений.</string>
<string name="account_settings_hide_buttons_label">Прокрутка навигационных кнопок</string>
<string name="account_settings_hide_buttons_never">Никогда</string>
<string name="account_settings_hide_buttons_keyboard_avail">Когда присутствует клавиатура</string>
<string name="account_settings_hide_buttons_always">Всегда</string>
<string name="account_settings_enable_move_buttons_label">Разрешить кнопки переноса сообщений</string>
<string name="account_settings_enable_move_buttons_summary">Показывает кнопки: Архив, Переместить, Спам.</string>
<string name="account_settings_hide_move_buttons_label">Спрятать кнопки перемещения</string>
<string name="account_settings_show_pictures_label">Показывать изображения</string>
<string name="account_settings_show_pictures_never">Никогда</string>

View File

@ -543,14 +543,8 @@ Välkommen till installationen av K-9 E-post. K-9 är en e-postklient med öppen
<string name="account_settings_notification_unread_count_label">Visa antal olästa</string>
<string name="account_settings_notification_unread_count_summary">Visar antalet olästa brev i notifieringsytan.</string>
<string name="account_settings_hide_buttons_label">Scrolla navigationsknappar</string>
<string name="account_settings_hide_buttons_never">Aldrig</string>
<string name="account_settings_hide_buttons_keyboard_avail">När ett tangentbord är tillgängligt</string>
<string name="account_settings_hide_buttons_always">Alltid</string>
<string name="account_settings_enable_move_buttons_label">Aktivera flyttknappar</string>
<string name="account_settings_enable_move_buttons_summary">Visa knappar för att arkivera, flytta och spam-markera e-post.</string>
<string name="account_settings_hide_move_buttons_label">Scrolla flyttknappar</string>
<string name="account_settings_show_pictures_label">Visa bilder automatiskt</string>
<string name="account_settings_show_pictures_never">Aldrig</string>

View File

@ -516,14 +516,8 @@
<string name="account_settings_notification_unread_count_label">Okunmamış sayısını göster</string>
<string name="account_settings_notification_unread_count_summary">Bildirim çubuğunda okunmamış mesaj numarasını göster.</string>
<string name="account_settings_hide_buttons_label">Gezinme tuşlarını kaydırma</string>
<string name="account_settings_hide_buttons_never">Asla</string>
<string name="account_settings_hide_buttons_keyboard_avail">Klavye olduğu zaman</string>
<string name="account_settings_hide_buttons_always">Daima</string>
<string name="account_settings_enable_move_buttons_label">İşaretleme düğmelerini etkinleştir</string>
<string name="account_settings_enable_move_buttons_summary">Arşiv, Taşıma ve Spam düğmelerini göster.</string>
<string name="account_settings_hide_move_buttons_label">İşaretleme düğmelerini kaydır</string>
<string name="account_settings_show_pictures_label">Daima imajları göster</string>
<string name="account_settings_show_pictures_never">Hayır</string>

View File

@ -530,14 +530,8 @@
<!-- NEW: <string name="account_settings_notification_unread_count_label">Show unread count</string>-->
<!-- NEW: <string name="account_settings_notification_unread_count_summary">Show the number of unread messages in the notification bar.</string>-->
<string name="account_settings_hide_buttons_label">滚动导航按钮</string>
<string name="account_settings_hide_buttons_never">从不</string>
<string name="account_settings_hide_buttons_keyboard_avail">使用键盘时</string>
<string name="account_settings_hide_buttons_always">总是</string>
<string name="account_settings_enable_move_buttons_label">启用整理按钮</string>
<string name="account_settings_enable_move_buttons_summary">显示归档、移动和标记为垃圾按钮</string>
<string name="account_settings_hide_move_buttons_label">滚动整理按钮</string>
<string name="account_settings_show_pictures_label">显示图片</string>
<string name="account_settings_show_pictures_never">从不</string>

View File

@ -539,14 +539,8 @@
<string name="account_settings_notification_unread_count_label">顯示未讀郵件數</string>
<string name="account_settings_notification_unread_count_summary">在通知欄上顯示未讀郵件數</string>
<string name="account_settings_hide_buttons_label">滾動導航按鈕</string>
<string name="account_settings_hide_buttons_never">從不</string>
<string name="account_settings_hide_buttons_keyboard_avail">使用鍵盤時</string>
<string name="account_settings_hide_buttons_always">總是</string>
<string name="account_settings_enable_move_buttons_label">啟用整理按鈕</string>
<string name="account_settings_enable_move_buttons_summary">顯示歸檔、移動和標記為垃圾按鈕</string>
<string name="account_settings_hide_move_buttons_label">滾動整理按鈕</string>
<string name="account_settings_show_pictures_label">顯示圖片</string>
<string name="account_settings_show_pictures_never">從不</string>
@ -799,7 +793,7 @@
<string name="provider_note_hanmail">要使用此提供者的IMAP或POP3請先至Hanmail(Daum)郵箱設置頁設定允許使用IMAP或POP3。</string>
<string name="provider_note_paran">要使用此提供者的IMAP或POP3請先至Paran郵箱設置頁設定允許使用IMAP或POP3。</string>
<string name="provider_note_nate">要使用此提供者的IMAP或POP3請先至Nate郵箱設置頁設定允許使用IMAP或POP3。</string>
<string name="account_setup_failed_dlg_invalid_certificate_title">無法識別的證書訊息</string>
<string name="account_setup_failed_dlg_invalid_certificate_accept">接收密鑰</string>
<string name="account_setup_failed_dlg_invalid_certificate_reject">拒絕密鑰</string>
@ -1060,5 +1054,5 @@
<string name="manage_accounts_move_up_action">上移</string>
<string name="manage_accounts_move_down_action">下移</string>
</resources>

View File

@ -149,30 +149,6 @@
<item>NOT_SECOND_CLASS</item>
</string-array>
<string-array name="account_settings_hide_buttons_entries">
<item>@string/account_settings_hide_buttons_never</item>
<item>@string/account_settings_hide_buttons_keyboard_avail</item>
<item>@string/account_settings_hide_buttons_always</item>
</string-array>
<string-array name="account_settings_hide_buttons_values">
<item>NEVER</item>
<item>KEYBOARD_AVAILABLE</item>
<item>ALWAYS</item>
</string-array>
<string-array name="account_settings_hide_move_buttons_entries">
<item>@string/account_settings_hide_buttons_never</item>
<item>@string/account_settings_hide_buttons_keyboard_avail</item>
<item>@string/account_settings_hide_buttons_always</item>
</string-array>
<string-array name="account_settings_hide_move_buttons_values">
<item>NEVER</item>
<item>KEYBOARD_AVAILABLE</item>
<item>ALWAYS</item>
</string-array>
<string-array name="account_settings_show_pictures_entries">
<item>@string/account_settings_show_pictures_never</item>
<item>@string/account_settings_show_pictures_only_from_contacts</item>

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="message_list_item_background">#ffffff</color>
<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>
</resources>

View File

@ -292,6 +292,9 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin
<string name="message_view_status_attachment_not_saved">Unable to save attachment to SD card.</string>
<string name="message_view_show_pictures_instructions">Select \"Show pictures\" to display embedded pictures.</string>
<string name="message_view_show_pictures_action">Show pictures</string>
<string name="message_view_show_message_action">Show message</string>
<string name="message_view_show_attachments_action">Show attachments</string>
<string name="message_view_show_more_attachments_action">More&#8230;</string>
<string name="message_view_fetching_attachment_toast">Fetching attachment.</string>
<string name="message_view_no_viewer">Unable to find viewer for <xliff:g id="mimetype">%s</xliff:g>.</string>
@ -552,14 +555,8 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin
<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_hide_buttons_label">Scroll navigation buttons</string>
<string name="account_settings_hide_buttons_never">Never</string>
<string name="account_settings_hide_buttons_keyboard_avail">When keyboard is available</string>
<string name="account_settings_hide_buttons_always">Always</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>
<string name="account_settings_hide_move_buttons_label">Scroll refile buttons</string>
<string name="account_settings_show_pictures_label">Always show images</string>
<string name="account_settings_show_pictures_never">No</string>
@ -1036,6 +1033,9 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin
<string name="save_or_discard_draft_message_dlg_title">Save draft message?</string>
<string name="save_or_discard_draft_message_instructions_fmt">Save or Discard this message?</string>
<string name="confirm_discard_draft_message_title">Discard message?</string>
<string name="confirm_discard_draft_message">Are you sure you want to discard this message?</string>
<string name="refuse_to_save_draft_marked_encrypted_dlg_title">Refuse to save draft message.</string>
<string name="refuse_to_save_draft_marked_encrypted_instructions_fmt">Refuse to save draft message marked encrypted.</string>
@ -1129,4 +1129,12 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin
<string name="manage_accounts_move_down_action">Move down</string>
<string name="manage_accounts_moving_message">Moving account...</string>
<string name="unread_widget_label">K-9 Unread</string>
<string name="unread_widget_select_account">Show unread count for…</string>
<string name="import_dialog_error_title">Missing File Manager Application</string>
<string name="import_dialog_error_message">There is no suitable application to handle
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>
</resources>

View File

@ -70,14 +70,6 @@
android:entryValues="@array/account_settings_show_pictures_values"
android:dialogTitle="@string/account_settings_show_pictures_label" />
<ListPreference
android:persistent="false"
android:key="hide_buttons_enum"
android:title="@string/account_settings_hide_buttons_label"
android:entries="@array/account_settings_hide_buttons_entries"
android:entryValues="@array/account_settings_hide_buttons_values"
android:dialogTitle="@string/account_settings_hide_buttons_label" />
<CheckBoxPreference
android:persistent="false"
android:key="enable_move_buttons"
@ -85,15 +77,6 @@
android:defaultValue="true"
android:summary="@string/account_settings_enable_move_buttons_summary" />
<ListPreference
android:persistent="false"
android:key="hide_move_buttons_enum"
android:dependency="enable_move_buttons"
android:title="@string/account_settings_hide_move_buttons_label"
android:entries="@array/account_settings_hide_move_buttons_entries"
android:entryValues="@array/account_settings_hide_move_buttons_values"
android:dialogTitle="@string/account_settings_hide_move_buttons_label" />
</PreferenceCategory>
</PreferenceScreen>
@ -233,10 +216,10 @@
android:entryValues="@array/account_settings_message_format_values" />
<CheckBoxPreference
android:persistent="false"
android:key="message_read_receipt"
android:title="@string/account_settings_message_read_receipt_label"
android:summary="@string/account_settings_message_read_receipt_summary" />
android:persistent="false"
android:key="message_read_receipt"
android:title="@string/account_settings_message_read_receipt_label"
android:summary="@string/account_settings_message_read_receipt_summary" />
<ListPreference
android:persistent="false"

View File

@ -170,6 +170,14 @@
<incoming uri="pop3://incoming.verizon.net" username="$user" />
<outgoing uri="smtp://outgoing.verizon.net" username="$user" />
</provider>
<provider id="montclair.edu" label="MSU" domain="montclair.edu">
<incoming uri="imap+ssl+://mail.montclair.edu" username="$user" />
<outgoing uri="smtp+tls+://smtp.montclair.edu" username="$user" />
</provider>
<provider id="gmx.com" label="GMX" domain="gmx.com">
<incoming uri="imap+ssl+://imap.gmx.com" username="$email" />
<outgoing uri="smtp+ssl+://mail.gmx.com" username="$email" />
</provider>
<!-- Yahoo! Mail Variants -->
<provider id="yahoo" label="Yahoo" domain="yahoo.com">

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/unread_widget_layout"
android:minHeight="72dp"
android:minWidth="72dp"
android:configure="com.fsck.k9.activity.UnreadWidgetConfiguration"
android:updatePeriodMillis="0">
</appwidget-provider>

View File

@ -120,8 +120,6 @@ public class Account implements BaseAccount {
private boolean mSaveAllHeaders;
private boolean mPushPollOnConnect;
private boolean mNotifySync;
private ScrollButtons mScrollMessageViewButtons;
private ScrollButtons mScrollMessageViewMoveButtons;
private ShowPictures mShowPictures;
private boolean mEnableMoveButtons;
private boolean mIsSignatureBeforeQuotedText;
@ -182,10 +180,6 @@ public class Account implements BaseAccount {
NONE, ALL, FIRST_CLASS, FIRST_AND_SECOND_CLASS, NOT_SECOND_CLASS
}
public enum ScrollButtons {
NEVER, ALWAYS, KEYBOARD_AVAILABLE
}
public enum ShowPictures {
NEVER, ALWAYS, ONLY_FROM_CONTACTS
}
@ -218,8 +212,6 @@ public class Account implements BaseAccount {
mFolderSyncMode = FolderMode.FIRST_CLASS;
mFolderPushMode = FolderMode.FIRST_CLASS;
mFolderTargetMode = FolderMode.NOT_SECOND_CLASS;
mScrollMessageViewButtons = ScrollButtons.NEVER;
mScrollMessageViewMoveButtons = ScrollButtons.NEVER;
mShowPictures = ShowPictures.NEVER;
mEnableMoveButtons = false;
mIsSignatureBeforeQuotedText = false;
@ -341,20 +333,6 @@ public class Account implements BaseAccount {
(random.nextInt(0x70) * 0xffff) +
0xff000000);
try {
mScrollMessageViewButtons = ScrollButtons.valueOf(prefs.getString(mUuid + ".hideButtonsEnum",
ScrollButtons.NEVER.name()));
} catch (Exception e) {
mScrollMessageViewButtons = ScrollButtons.NEVER;
}
try {
mScrollMessageViewMoveButtons = ScrollButtons.valueOf(prefs.getString(mUuid + ".hideMoveButtonsEnum",
ScrollButtons.NEVER.name()));
} catch (Exception e) {
mScrollMessageViewMoveButtons = ScrollButtons.NEVER;
}
try {
mShowPictures = ShowPictures.valueOf(prefs.getString(mUuid + ".showPicturesEnum",
ShowPictures.NEVER.name()));
@ -617,8 +595,6 @@ public class Account implements BaseAccount {
editor.putString(mUuid + ".spamFolderName", mSpamFolderName);
editor.putString(mUuid + ".autoExpandFolderName", mAutoExpandFolderName);
editor.putInt(mUuid + ".accountNumber", mAccountNumber);
editor.putString(mUuid + ".hideButtonsEnum", mScrollMessageViewButtons.name());
editor.putString(mUuid + ".hideMoveButtonsEnum", mScrollMessageViewMoveButtons.name());
editor.putString(mUuid + ".showPicturesEnum", mShowPictures.name());
editor.putBoolean(mUuid + ".enableMoveButtons", mEnableMoveButtons);
editor.putString(mUuid + ".folderDisplayMode", mFolderDisplayMode.name());
@ -1055,22 +1031,6 @@ public class Account implements BaseAccount {
this.mNotifySync = showOngoing;
}
public synchronized ScrollButtons getScrollMessageViewButtons() {
return mScrollMessageViewButtons;
}
public synchronized void setScrollMessageViewButtons(ScrollButtons scrollMessageViewButtons) {
mScrollMessageViewButtons = scrollMessageViewButtons;
}
public synchronized ScrollButtons getScrollMessageViewMoveButtons() {
return mScrollMessageViewMoveButtons;
}
public synchronized void setScrollMessageViewMoveButtons(ScrollButtons scrollMessageViewButtons) {
mScrollMessageViewMoveButtons = scrollMessageViewButtons;
}
public synchronized ShowPictures getShowPictures() {
return mShowPictures;
}

View File

@ -48,7 +48,7 @@ public class EmailAddressAdapter extends ResourceCursorAdapter {
final String name = mContacts.getName(cursor);
final String address = mContacts.getEmail(cursor);
return new Address(address, name).toString();
return (address == null) ? "" : new Address(address, name).toString();
}
@Override

View File

@ -34,6 +34,7 @@ import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.internet.BinaryTempFileBody;
import com.fsck.k9.provider.UnreadWidgetProvider;
import com.fsck.k9.service.BootReceiver;
import com.fsck.k9.service.MailService;
import com.fsck.k9.service.ShutdownReceiver;
@ -478,19 +479,38 @@ public class K9 extends Application {
}
}
private void updateUnreadWidget() {
try {
UnreadWidgetProvider.updateUnreadCount(K9.this);
} catch (Exception e) {
if (K9.DEBUG) {
Log.e(LOG_TAG, "Error while updating unread widget(s)", e);
}
}
}
@Override
public void synchronizeMailboxRemovedMessage(Account account, String folder, Message message) {
broadcastIntent(K9.Intents.EmailReceived.ACTION_EMAIL_DELETED, account, folder, message);
updateUnreadWidget();
}
@Override
public void messageDeleted(Account account, String folder, Message message) {
broadcastIntent(K9.Intents.EmailReceived.ACTION_EMAIL_DELETED, account, folder, message);
updateUnreadWidget();
}
@Override
public void synchronizeMailboxNewMessage(Account account, String folder, Message message) {
broadcastIntent(K9.Intents.EmailReceived.ACTION_EMAIL_RECEIVED, account, folder, message);
updateUnreadWidget();
}
@Override
public void folderStatusChanged(Account account, String folderName,
int unreadMessageCount) {
updateUnreadWidget();
}
@Override

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

@ -0,0 +1,187 @@
package com.fsck.k9.activity;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import com.fsck.k9.Account;
import com.fsck.k9.BaseAccount;
import com.fsck.k9.FontSizes;
import com.fsck.k9.K9;
import com.fsck.k9.Preferences;
import com.fsck.k9.R;
import com.fsck.k9.SearchAccount;
/**
* Activity displaying the list of accounts.
*
* <p>
* Classes extending this abstract class have to provide an {@link #onAccountSelected(BaseAccount)}
* method to perform an action when an account is selected.
* </p>
*/
public abstract class AccountList extends K9ListActivity implements OnItemClickListener {
private FontSizes mFontSizes = K9.getFontSizes();
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setResult(RESULT_CANCELED);
setContentView(R.layout.account_list);
ListView listView = getListView();
listView.setOnItemClickListener(this);
listView.setItemsCanFocus(false);
}
/**
* Reload list of accounts when this activity is resumed.
*/
@Override
public void onResume() {
super.onResume();
new LoadAccounts().execute();
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
BaseAccount account = (BaseAccount) parent.getItemAtPosition(position);
onAccountSelected(account);
}
/**
* Create a new {@link AccountsAdapter} instance and assign it to the {@link ListView}.
*
* @param realAccounts
* An array of accounts to display.
*/
public void populateListView(Account[] realAccounts) {
List<BaseAccount> accounts = new ArrayList<BaseAccount>();
if (displaySpecialAccounts() && !K9.isHideSpecialAccounts()) {
BaseAccount unifiedInboxAccount = SearchAccount.createUnifiedInboxAccount(this);
BaseAccount allMessagesAccount = SearchAccount.createAllMessagesAccount(this);
accounts.add(unifiedInboxAccount);
accounts.add(allMessagesAccount);
}
accounts.addAll(Arrays.asList(realAccounts));
AccountsAdapter adapter = new AccountsAdapter(accounts);
ListView listView = getListView();
listView.setAdapter(adapter);
listView.invalidate();
}
/**
* Implementing decide whether or not to display special accounts in the list.
*
* @return {@code true}, if special accounts should be listed. {@code false}, otherwise.
*/
protected abstract boolean displaySpecialAccounts();
/**
* This method will be called when an account was selected.
*
* @param account
* The account the user selected.
*/
protected abstract void onAccountSelected(BaseAccount account);
class AccountsAdapter extends ArrayAdapter<BaseAccount> {
public AccountsAdapter(List<BaseAccount> accounts) {
super(AccountList.this, 0, accounts);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final BaseAccount account = getItem(position);
final View view;
if (convertView != null) {
view = convertView;
} else {
view = getLayoutInflater().inflate(R.layout.accounts_item, parent, false);
view.findViewById(R.id.active_icons).setVisibility(View.GONE);
view.findViewById(R.id.folders).setVisibility(View.GONE);
view.getBackground().setAlpha(0);
}
AccountViewHolder holder = (AccountViewHolder) view.getTag();
if (holder == null) {
holder = new AccountViewHolder();
holder.description = (TextView) view.findViewById(R.id.description);
holder.email = (TextView) view.findViewById(R.id.email);
holder.chip = view.findViewById(R.id.chip);
view.setTag(holder);
}
String description = account.getDescription();
if (account.getEmail().equals(description)) {
holder.email.setVisibility(View.GONE);
} else {
holder.email.setVisibility(View.VISIBLE);
holder.email.setText(account.getEmail());
}
if (description == null || description.length() == 0) {
description = account.getEmail();
}
holder.description.setText(description);
if (account instanceof Account) {
Account realAccount = (Account) account;
holder.chip.setBackgroundColor(realAccount.getChipColor());
} else {
holder.chip.setBackgroundColor(0xff999999);
}
holder.chip.getBackground().setAlpha(255);
holder.description.setTextSize(TypedValue.COMPLEX_UNIT_SP,
mFontSizes.getAccountName());
holder.email.setTextSize(TypedValue.COMPLEX_UNIT_SP,
mFontSizes.getAccountDescription());
return view;
}
class AccountViewHolder {
public TextView description;
public TextView email;
public View chip;
}
}
/**
* Load accounts in a background thread
*/
class LoadAccounts extends AsyncTask<Void, Void, Account[]> {
@Override
protected Account[] doInBackground(Void... params) {
Account[] accounts = Preferences.getPreferences(getApplicationContext()).getAccounts();
return accounts;
}
@Override
protected void onPostExecute(Account[] accounts) {
populateListView(accounts);
}
}
}

View File

@ -24,6 +24,7 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
@ -107,9 +108,21 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC
*/
private static final Flag[] EMPTY_FLAG_ARRAY = new Flag[0];
/**
* URL used to open Android Market application
*/
private static final String ANDROID_MARKET_URL = "https://market.android.com/search?q=oi+file+manager&c=apps";
/**
* Number of special accounts ('Unified Inbox' and 'All Messages')
*/
private static final int SPECIAL_ACCOUNTS_COUNT = 2;
private static final int DIALOG_REMOVE_ACCOUNT = 1;
private static final int DIALOG_CLEAR_ACCOUNT = 2;
private static final int DIALOG_RECREATE_ACCOUNT = 3;
private static final int DIALOG_NO_FILE_MANAGER = 4;
private ConcurrentHashMap<String, AccountStats> accountStats = new ConcurrentHashMap<String, AccountStats>();
private ConcurrentHashMap<BaseAccount, String> pendingWork = new ConcurrentHashMap<BaseAccount, String>();
@ -326,13 +339,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC
super.onCreate(icicle);
if (!K9.isHideSpecialAccounts()) {
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));
createSpecialAccounts();
}
Account[] accounts = Preferences.getPreferences(this).getAccounts();
@ -375,6 +382,14 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC
}
}
/**
* Creates and initializes the special accounts ('Unified Inbox' and 'All Messages')
*/
private void createSpecialAccounts() {
integratedInboxAccount = SearchAccount.createUnifiedInboxAccount(this);
unreadAccount = SearchAccount.createAllMessagesAccount(this);
}
@SuppressWarnings("unchecked")
private void restoreAccountStats(Bundle icicle) {
if (icicle != null) {
@ -459,9 +474,13 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC
accounts = Preferences.getPreferences(this).getAccounts();
List<BaseAccount> newAccounts;
if (!K9.isHideSpecialAccounts()
&& accounts.length > 0) {
newAccounts = new ArrayList<BaseAccount>(accounts.length + 2);
if (!K9.isHideSpecialAccounts() && accounts.length > 0) {
if (integratedInboxAccount == null || unreadAccount == null) {
createSpecialAccounts();
}
newAccounts = new ArrayList<BaseAccount>(accounts.length +
SPECIAL_ACCOUNTS_COUNT);
newAccounts.add(integratedInboxAccount);
newAccounts.add(unreadAccount);
} else {
@ -971,6 +990,20 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC
}
}
});
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);
}
@ -992,6 +1025,9 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC
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;
}
super.onPrepareDialog(id, d);
@ -1244,7 +1280,16 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType(MimeUtility.K9_SETTINGS_MIME_TYPE);
startActivityForResult(Intent.createChooser(i, null), ACTIVITY_REQUEST_PICK_SETTINGS_FILE);
PackageManager packageManager = getPackageManager();
List<ResolveInfo> infos = packageManager.queryIntentActivities(i, 0);
if (infos.size() > 0) {
startActivityForResult(Intent.createChooser(i, null),
ACTIVITY_REQUEST_PICK_SETTINGS_FILE);
} else {
showDialog(DIALOG_NO_FILE_MANAGER);
}
}
@Override

View File

@ -733,6 +733,15 @@ public class FolderList extends K9ListActivity {
menu.findItem(R.id.expunge).setVisible(false);
}
if (!MessagingController.getInstance(getApplication()).isMoveCapable(mAccount)) {
// FIXME: Really we want to do this for all local-only folders
if (!mAccount.getInboxFolderName().equals(folder.name)) {
menu.findItem(R.id.check_mail).setVisible(false);
}
menu.findItem(R.id.expunge).setVisible(false);
}
menu.setHeaderTitle(folder.displayName);
}

View File

@ -6,24 +6,21 @@ import java.util.Locale;
import android.app.Activity;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.os.Bundle;
import android.util.Log;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
import com.fsck.k9.K9;
import com.fsck.k9.view.ToggleScrollView;
public class K9Activity extends Activity {
private GestureDetector gestureDetector;
protected ToggleScrollView mTopView;
@Override
public void onCreate(Bundle icicle) {
onCreate(icicle, true);
@ -144,22 +141,6 @@ public class K9Activity extends Activity {
private static final float SWIPE_MAX_OFF_PATH_DIP = 250f;
private static final float SWIPE_THRESHOLD_VELOCITY_DIP = 325f;
@Override
public boolean onDoubleTap(MotionEvent ev) {
super.onDoubleTap(ev);
if (mTopView != null) {
int height = getResources().getDisplayMetrics().heightPixels;
if (ev.getRawY() < (height / 4)) {
mTopView.fullScroll(View.FOCUS_UP);
} else if (ev.getRawY() > (height - height / 4)) {
mTopView.fullScroll(View.FOCUS_DOWN);
}
}
return false;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
// Do fling-detection if gestures are force-enabled or we have system-wide gestures enabled.
@ -169,11 +150,11 @@ public class K9Activity extends Activity {
final float mGestureScale = getResources().getDisplayMetrics().density;
final int minVelocity = (int)(SWIPE_THRESHOLD_VELOCITY_DIP * mGestureScale + 0.5f);
final int maxOffPath = (int)(SWIPE_MAX_OFF_PATH_DIP * mGestureScale + 0.5f);
// Calculate how much was actually swiped.
final float deltaX = e2.getX() - e1.getX();
final float deltaY = e2.getY() - e1.getY();
// Calculate the minimum distance required for this to be considered a swipe.
final int minDistance = (int)Math.abs(deltaY * 4);
@ -216,4 +197,14 @@ public class K9Activity extends Activity {
return false;
}
}
public int getThemeBackgroundColor() {
TypedArray array = getTheme().obtainStyledAttributes(new int[] {
android.R.attr.colorBackground,
});
int backgroundColor = array.getColor(0, 0xFF00FF);
array.recycle();
return backgroundColor;
}
}

View File

@ -3,53 +3,42 @@ package com.fsck.k9.activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import com.fsck.k9.Account;
import com.fsck.k9.FontSizes;
import com.fsck.k9.K9;
import com.fsck.k9.Preferences;
import com.fsck.k9.BaseAccount;
import com.fsck.k9.R;
import com.fsck.k9.SearchSpecification;
public class LauncherShortcuts extends K9ListActivity implements OnItemClickListener {
private AccountsAdapter mAdapter;
private FontSizes mFontSizes = K9.getFontSizes();
public class LauncherShortcuts extends AccountList {
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
// finish() immediately if we aren't supposed to be here
if (!Intent.ACTION_CREATE_SHORTCUT.equals(getIntent().getAction())) {
finish();
return;
}
setContentView(R.layout.launcher_shortcuts);
ListView listView = getListView();
listView.setOnItemClickListener(this);
listView.setItemsCanFocus(false);
refresh();
super.onCreate(icicle);
}
private void refresh() {
Account[] accounts = Preferences.getPreferences(this).getAccounts();
mAdapter = new AccountsAdapter(accounts);
getListView().setAdapter(mAdapter);
@Override
protected boolean displaySpecialAccounts() {
return true;
}
private void setupShortcut(Account account) {
final Intent shortcutIntent = FolderList.actionHandleAccountIntent(this, account, null, true);
@Override
protected void onAccountSelected(BaseAccount account) {
Intent shortcutIntent = null;
if (account instanceof SearchSpecification) {
shortcutIntent = MessageList.actionHandleAccountIntent(this, account.getDescription(),
(SearchSpecification) account);
} else {
shortcutIntent = FolderList.actionHandleAccountIntent(this, (Account) account, null,
true);
}
shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
Intent intent = new Intent();
intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
String description = account.getDescription();
@ -63,66 +52,4 @@ public class LauncherShortcuts extends K9ListActivity implements OnItemClickList
setResult(RESULT_OK, intent);
finish();
}
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Account account = (Account) parent.getItemAtPosition(position);
setupShortcut(account);
}
class AccountsAdapter extends ArrayAdapter<Account> {
public AccountsAdapter(Account[] accounts) {
super(LauncherShortcuts.this, 0, accounts);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final Account account = getItem(position);
final View view;
if (convertView != null) {
view = convertView;
} else {
view = getLayoutInflater().inflate(R.layout.accounts_item, parent, false);
view.findViewById(R.id.active_icons).setVisibility(View.GONE);
}
AccountViewHolder holder = (AccountViewHolder) view.getTag();
if (holder == null) {
holder = new AccountViewHolder();
holder.description = (TextView) view.findViewById(R.id.description);
holder.email = (TextView) view.findViewById(R.id.email);
holder.chip = view.findViewById(R.id.chip);
view.setTag(holder);
}
String description = account.getDescription();
if (account.getEmail().equals(description)) {
holder.email.setVisibility(View.GONE);
} else {
holder.email.setVisibility(View.VISIBLE);
holder.email.setText(account.getEmail());
}
if (description == null || description.length() == 0) {
description = account.getEmail();
}
holder.description.setText(description);
holder.chip.setBackgroundColor(account.getChipColor());
holder.chip.getBackground().setAlpha(255);
holder.description.setTextSize(TypedValue.COMPLEX_UNIT_SP, mFontSizes.getAccountName());
holder.email.setTextSize(TypedValue.COMPLEX_UNIT_SP, mFontSizes.getAccountDescription());
return view;
}
class AccountViewHolder {
public TextView description;
public TextView email;
public View chip;
}
}
}

View File

@ -83,6 +83,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
private static final int DIALOG_SAVE_OR_DISCARD_DRAFT_MESSAGE = 1;
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 long INVALID_DRAFT_ID = MessagingController.INVALID_MESSAGE_ID;
@ -1609,7 +1610,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
final Account account = Preferences.getPreferences(this).getAccount(mMessageReference.accountUuid);
final String folderName = mMessageReference.folderName;
final String sourceMessageUid = mMessageReference.uid;
MessagingController.getInstance(getApplication()).setFlag(account, folderName, new String[] {sourceMessageUid}, mMessageReference.flag, true);
MessagingController.getInstance(getApplication()).setFlag(account, folderName, sourceMessageUid, mMessageReference.flag, true);
}
mDraftNeedsSaving = false;
@ -2050,13 +2051,21 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
@Override
public void onBackPressed() {
if (mEncryptCheckbox.isChecked()) {
showDialog(DIALOG_REFUSE_TO_SAVE_DRAFT_MARKED_ENCRYPTED);
} else if (!mDraftNeedsSaving || isDraftsFolderDisabled()) {
Toast.makeText(MessageCompose.this, getString(R.string.message_discarded_toast), Toast.LENGTH_LONG).show();
super.onBackPressed();
if (mDraftNeedsSaving) {
if (mEncryptCheckbox.isChecked()) {
showDialog(DIALOG_REFUSE_TO_SAVE_DRAFT_MARKED_ENCRYPTED);
} else if (isDraftsFolderDisabled()) {
showDialog(DIALOG_CONFIRM_DISCARD_ON_BACK);
} else {
showDialog(DIALOG_SAVE_OR_DISCARD_DRAFT_MESSAGE);
}
} else {
showDialog(DIALOG_SAVE_OR_DISCARD_DRAFT_MESSAGE);
// Check if editing an existing draft.
if (mDraftId == INVALID_DRAFT_ID) {
onDiscard();
} else {
super.onBackPressed();
}
}
}
@ -2117,6 +2126,27 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
}
})
.create();
case DIALOG_CONFIRM_DISCARD_ON_BACK:
return new AlertDialog.Builder(this)
.setTitle(R.string.confirm_discard_draft_message_title)
.setMessage(R.string.confirm_discard_draft_message)
.setPositiveButton(R.string.cancel_action, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int whichButton) {
dismissDialog(DIALOG_CONFIRM_DISCARD_ON_BACK);
}
})
.setNegativeButton(R.string.discard_action, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int whichButton) {
dismissDialog(DIALOG_CONFIRM_DISCARD_ON_BACK);
Toast.makeText(MessageCompose.this,
getString(R.string.message_discarded_toast),
Toast.LENGTH_LONG).show();
onDiscard();
}
})
.create();
}
return super.onCreateDialog(id);
}
@ -2678,7 +2708,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
if (part != null) {
if (K9.DEBUG)
Log.d(K9.LOG_TAG, "getBodyTextFromMessage: HTML requested, text found.");
return HtmlConverter.textToHtml(MimeUtility.getTextFromPart(part));
return HtmlConverter.textToHtml(MimeUtility.getTextFromPart(part), true);
}
} else if (format == MessageFormat.TEXT) {
// Text takes precedence, then html.
@ -2987,9 +3017,10 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
}
MessagingController.getInstance(getApplication()).sendMessage(mAccount, message, null);
if (mDraftId != INVALID_DRAFT_ID) {
MessagingController.getInstance(getApplication()).deleteDraft(mAccount, mDraftId);
long draftId = mDraftId;
if (draftId != INVALID_DRAFT_ID) {
mDraftId = INVALID_DRAFT_ID;
MessagingController.getInstance(getApplication()).deleteDraft(mAccount, draftId);
}
return null;

View File

@ -52,10 +52,12 @@ import android.widget.Toast;
import com.fsck.k9.Account;
import com.fsck.k9.AccountStats;
import com.fsck.k9.BaseAccount;
import com.fsck.k9.FontSizes;
import com.fsck.k9.K9;
import com.fsck.k9.Preferences;
import com.fsck.k9.R;
import com.fsck.k9.SearchAccount;
import com.fsck.k9.SearchSpecification;
import com.fsck.k9.activity.setup.AccountSettings;
import com.fsck.k9.activity.setup.FolderSettings;
@ -70,6 +72,7 @@ import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.store.LocalStore;
import com.fsck.k9.mail.store.LocalStore.LocalFolder;
import com.fsck.k9.mail.store.LocalStore.LocalMessage;
import com.fsck.k9.mail.store.StorageManager;
@ -616,7 +619,11 @@ public class MessageList
context.startActivity(intent);
}
public static void actionHandle(Context context, String title, SearchSpecification searchSpecification) {
/**
* Creates and returns an intent that opens Unified Inbox or All Messages screen.
*/
public static Intent actionHandleAccountIntent(Context context, String title,
SearchSpecification searchSpecification) {
Intent intent = new Intent(context, MessageList.class);
intent.putExtra(EXTRA_QUERY, searchSpecification.getQuery());
if (searchSpecification.getRequiredFlags() != null) {
@ -632,6 +639,13 @@ public class MessageList
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
return intent;
}
public static void actionHandle(Context context, String title,
SearchSpecification searchSpecification) {
Intent intent = actionHandleAccountIntent(context, title, searchSpecification);
context.startActivity(intent);
}
@ -1388,13 +1402,21 @@ public class MessageList
}
private void onToggleRead(MessageInfoHolder holder) {
mController.setFlag(holder.message.getFolder().getAccount(), holder.message.getFolder().getRemoteName(), new String[] { holder.uid }, Flag.SEEN, !holder.read);
LocalMessage message = holder.message;
Folder folder = message.getFolder();
Account account = folder.getAccount();
String folderName = folder.getRemoteName();
mController.setFlag(account, folderName, new Message[] { message }, Flag.SEEN, !holder.read);
holder.read = !holder.read;
mHandler.sortMessages();
}
private void onToggleFlag(MessageInfoHolder holder) {
mController.setFlag(holder.message.getFolder().getAccount(), holder.message.getFolder().getRemoteName(), new String[] { holder.uid }, Flag.FLAGGED, !holder.flagged);
LocalMessage message = holder.message;
Folder folder = message.getFolder();
Account account = folder.getAccount();
String folderName = folder.getRemoteName();
mController.setFlag(account, folderName, new Message[] { message }, Flag.FLAGGED, !holder.flagged);
holder.flagged = !holder.flagged;
mHandler.sortMessages();
}
@ -1593,6 +1615,19 @@ public class MessageList
if (K9.FOLDER_NONE.equalsIgnoreCase(mAccount.getSpamFolderName())) {
menu.findItem(R.id.batch_spam_op).setVisible(false);
}
if (!mController.isMoveCapable(mAccount)) {
// FIXME: Really we want to do this for all local-only folders
if (mCurrentFolder != null &&
!mAccount.getInboxFolderName().equals(mCurrentFolder.name)) {
menu.findItem(R.id.check_mail).setVisible(false);
}
menu.findItem(R.id.batch_archive_op).setVisible(false);
menu.findItem(R.id.batch_spam_op).setVisible(false);
menu.findItem(R.id.batch_move_op).setVisible(false);
menu.findItem(R.id.batch_copy_op).setVisible(false);
menu.findItem(R.id.expunge).setVisible(false);
}
}
boolean newFlagState = computeBatchDirection(true);

View File

@ -4,11 +4,9 @@ import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.util.Config;
import android.util.Log;
import android.view.*;
import android.view.View.OnClickListener;
@ -20,9 +18,9 @@ import com.fsck.k9.crypto.PgpData;
import com.fsck.k9.helper.FileBrowserHelper;
import com.fsck.k9.helper.FileBrowserHelper.FileBrowserFailOverCallback;
import com.fsck.k9.mail.*;
import com.fsck.k9.mail.store.LocalStore.LocalMessage;
import com.fsck.k9.mail.store.StorageManager;
import com.fsck.k9.view.AttachmentView;
import com.fsck.k9.view.ToggleScrollView;
import com.fsck.k9.view.SingleMessageView;
import com.fsck.k9.view.AttachmentView.AttachmentFileDownloadCallback;
@ -34,8 +32,6 @@ public class MessageView extends K9Activity implements OnClickListener {
private static final String EXTRA_MESSAGE_REFERENCES = "com.fsck.k9.MessageView_messageReferences";
private static final String EXTRA_NEXT = "com.fsck.k9.MessageView_next";
private static final String EXTRA_MESSAGE_LIST_EXTRAS = "com.fsck.k9.MessageView_messageListExtras";
private static final String EXTRA_SCROLL_PERCENTAGE = "com.fsck.k9.MessageView_scrollPercentage";
private static final String SHOW_PICTURES = "showPictures";
private static final String STATE_PGP_DATA = "pgpData";
private static final int ACTIVITY_CHOOSE_FOLDER_MOVE = 1;
private static final int ACTIVITY_CHOOSE_FOLDER_COPY = 2;
@ -45,7 +41,6 @@ public class MessageView extends K9Activity implements OnClickListener {
private PgpData mPgpData;
private View mNext;
private View mPrevious;
private View mDelete;
@ -106,24 +101,6 @@ public class MessageView extends K9Activity implements OnClickListener {
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_UP) {
// Text selection is finished. Allow scrolling again.
mTopView.setScrolling(true);
} else if (K9.zoomControlsEnabled()) {
// If we have system zoom controls enabled, disable scrolling so the screen isn't wiggling around while
// trying to zoom.
if (ev.getAction() == MotionEvent.ACTION_POINTER_2_DOWN) {
mTopView.setScrolling(false);
} else if (ev.getAction() == MotionEvent.ACTION_POINTER_2_UP) {
mTopView.setScrolling(true);
}
}
return super.dispatchTouchEvent(ev);
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
boolean ret = false;
@ -167,15 +144,6 @@ public class MessageView extends K9Activity implements OnClickListener {
}
break;
}
case KeyEvent.KEYCODE_SHIFT_LEFT:
case KeyEvent.KEYCODE_SHIFT_RIGHT: {
/*
* Selecting text started via shift key. Disable scrolling as
* this causes problems when selecting text.
*/
mTopView.setScrolling(false);
break;
}
case KeyEvent.KEYCODE_DEL: {
onDelete();
return true;
@ -326,7 +294,6 @@ public class MessageView extends K9Activity implements OnClickListener {
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.message_view);
mTopView = (ToggleScrollView) findViewById(R.id.top_view);
mMessageView = (SingleMessageView) findViewById(R.id.message_view);
//set a callback for the attachment view. With this callback the attachmentview
@ -358,10 +325,6 @@ public class MessageView extends K9Activity implements OnClickListener {
mMessageView.initialize(this);
// Register the ScrollView's listener to handle scrolling to last known location on resume.
mController.addListener(mTopView.getListener());
mMessageView.setListeners(mController.getListeners());
setTitle("");
final Intent intent = getIntent();
@ -433,57 +396,29 @@ public class MessageView extends K9Activity implements OnClickListener {
setOnClickListener(R.id.archive);
setOnClickListener(R.id.move);
setOnClickListener(R.id.spam);
// To show full header
setOnClickListener(R.id.header_container);
setOnClickListener(R.id.reply_scrolling);
// setOnClickListener(R.id.reply_all_scrolling);
setOnClickListener(R.id.delete_scrolling);
setOnClickListener(R.id.forward_scrolling);
setOnClickListener(R.id.next_scrolling);
setOnClickListener(R.id.previous_scrolling);
setOnClickListener(R.id.archive_scrolling);
setOnClickListener(R.id.move_scrolling);
setOnClickListener(R.id.spam_scrolling);
setOnClickListener(R.id.show_pictures);
setOnClickListener(R.id.download_remainder);
// Perhaps the ScrollButtons should be global, instead of account-specific
Account.ScrollButtons scrollButtons = mAccount.getScrollMessageViewButtons();
if ((Account.ScrollButtons.ALWAYS == scrollButtons)
|| (Account.ScrollButtons.KEYBOARD_AVAILABLE == scrollButtons &&
(this.getResources().getConfiguration().hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO))) {
scrollButtons();
} else { // never or the keyboard is open
staticButtons();
}
Account.ScrollButtons scrollMoveButtons = mAccount.getScrollMessageViewMoveButtons();
if ((Account.ScrollButtons.ALWAYS == scrollMoveButtons)
|| (Account.ScrollButtons.KEYBOARD_AVAILABLE == scrollMoveButtons &&
(this.getResources().getConfiguration().hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO))) {
scrollMoveButtons();
} else {
staticMoveButtons();
}
mNext = findViewById(R.id.next);
mPrevious = findViewById(R.id.previous);
mDelete = findViewById(R.id.delete);
mArchive = findViewById(R.id.archive);
mMove = findViewById(R.id.move);
mSpam = findViewById(R.id.spam);
if (!mAccount.getEnableMoveButtons()) {
View buttons = findViewById(R.id.move_buttons);
if (buttons != null) {
buttons.setVisibility(View.GONE);
}
buttons = findViewById(R.id.scrolling_move_buttons);
if (buttons != null) {
buttons.setVisibility(View.GONE);
}
buttons.setVisibility(View.GONE);
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(EXTRA_MESSAGE_REFERENCE, mMessageReference);
outState.putParcelableArrayList(EXTRA_MESSAGE_REFERENCES, mMessageReferences);
outState.putSerializable(STATE_PGP_DATA, mPgpData);
outState.putBoolean(SHOW_PICTURES, mMessageView.showPictures());
outState.putDouble(EXTRA_SCROLL_PERCENTAGE, mTopView.getScrollPercentage());
}
@Override
@ -491,8 +426,6 @@ public class MessageView extends K9Activity implements OnClickListener {
super.onRestoreInstanceState(savedInstanceState);
mPgpData = (PgpData) savedInstanceState.getSerializable(STATE_PGP_DATA);
mMessageView.updateCryptoLayout(mAccount.getCryptoProvider(), mPgpData, mMessage);
mMessageView.setLoadPictures(savedInstanceState.getBoolean(SHOW_PICTURES));
mTopView.setScrollPercentage(savedInstanceState.getDouble(EXTRA_SCROLL_PERCENTAGE));
}
private void displayMessage(MessageReference ref) {
@ -500,22 +433,18 @@ public class MessageView extends K9Activity implements OnClickListener {
if (K9.DEBUG)
Log.d(K9.LOG_TAG, "MessageView displaying message " + mMessageReference);
mAccount = Preferences.getPreferences(this).getAccount(mMessageReference.accountUuid);
clearMessageDisplay();
findSurroundingMessagesUid();
// start with fresh, empty PGP data
mPgpData = new PgpData();
mTopView.setVisibility(View.VISIBLE);
// Clear previous message
mMessageView.resetView();
mMessageView.resetHeaderView();
mController.loadMessageForView(mAccount, mMessageReference.folderName, mMessageReference.uid, mListener);
setupDisplayMessageButtons();
}
private void clearMessageDisplay() {
mTopView.setVisibility(View.GONE);
mTopView.scrollTo(0, 0);
mMessageView.resetView();
}
private void setupDisplayMessageButtons() {
mDelete.setEnabled(true);
mNext.setEnabled(mNextMessage != null);
@ -533,45 +462,6 @@ public class MessageView extends K9Activity implements OnClickListener {
disableMoveButtons();
}
}
private void staticButtons() {
View buttons = findViewById(R.id.scrolling_buttons);
if (buttons != null) {
buttons.setVisibility(View.GONE);
}
mNext = findViewById(R.id.next);
mPrevious = findViewById(R.id.previous);
mDelete = findViewById(R.id.delete);
}
private void scrollButtons() {
View buttons = findViewById(R.id.bottom_buttons);
if (buttons != null) {
buttons.setVisibility(View.GONE);
}
mNext = findViewById(R.id.next_scrolling);
mPrevious = findViewById(R.id.previous_scrolling);
mDelete = findViewById(R.id.delete_scrolling);
}
private void staticMoveButtons() {
View buttons = findViewById(R.id.scrolling_move_buttons);
if (buttons != null) {
buttons.setVisibility(View.GONE);
}
mArchive = findViewById(R.id.archive);
mMove = findViewById(R.id.move);
mSpam = findViewById(R.id.spam);
}
private void scrollMoveButtons() {
View buttons = findViewById(R.id.move_buttons);
if (buttons != null) {
buttons.setVisibility(View.GONE);
}
mArchive = findViewById(R.id.archive_scrolling);
mMove = findViewById(R.id.move_scrolling);
mSpam = findViewById(R.id.spam_scrolling);
}
private void disableButtons() {
mMessageView.setLoadPictures(false);
@ -612,13 +502,11 @@ public class MessageView extends K9Activity implements OnClickListener {
onAccountUnavailable();
return;
}
mController.addListener(mTopView.getListener());
StorageManager.getInstance(getApplication()).addListener(mStorageListener);
}
@Override
protected void onPause() {
mController.removeListener(mTopView.getListener());
StorageManager.getInstance(getApplication()).removeListener(mStorageListener);
super.onPause();
}
@ -728,14 +616,10 @@ public class MessageView extends K9Activity implements OnClickListener {
private void onFlag() {
if (mMessage != null) {
mController.setFlag(mAccount,
mMessage.getFolder().getRemoteName(), new String[] {mMessage.getUid()}, Flag.FLAGGED, !mMessage.isSet(Flag.FLAGGED));
try {
mMessage.setFlag(Flag.FLAGGED, !mMessage.isSet(Flag.FLAGGED));
mMessageView.setHeaders(mMessage, mAccount);
} catch (MessagingException me) {
Log.e(K9.LOG_TAG, "Could not set flag on local message", me);
}
boolean newState = !mMessage.isSet(Flag.FLAGGED);
mController.setFlag(mAccount, mMessage.getFolder().getRemoteName(),
new Message[] { mMessage }, Flag.FLAGGED, newState);
mMessageView.setHeaders(mMessage, mAccount);
}
}
@ -846,7 +730,6 @@ public class MessageView extends K9Activity implements OnClickListener {
protected void onNext() {
// Reset scroll percentage when we change messages
mTopView.setScrollPercentage(0);
if (mNextMessage == null) {
Toast.makeText(this, getString(R.string.end_of_folder), Toast.LENGTH_SHORT).show();
return;
@ -854,7 +737,7 @@ public class MessageView extends K9Activity implements OnClickListener {
mLastDirection = NEXT;
disableButtons();
if (K9.showAnimations()) {
mTopView.startAnimation(outToLeftAnimation());
mMessageView.startAnimation(outToLeftAnimation());
}
displayMessage(mNextMessage);
mNext.requestFocus();
@ -862,7 +745,6 @@ public class MessageView extends K9Activity implements OnClickListener {
protected void onPrevious() {
// Reset scroll percentage when we change messages
mTopView.setScrollPercentage(0);
if (mPreviousMessage == null) {
Toast.makeText(this, getString(R.string.end_of_folder), Toast.LENGTH_SHORT).show();
return;
@ -870,7 +752,7 @@ public class MessageView extends K9Activity implements OnClickListener {
mLastDirection = PREVIOUS;
disableButtons();
if (K9.showAnimations()) {
mTopView.startAnimation(inFromRightAnimation());
mMessageView.startAnimation(inFromRightAnimation());
}
displayMessage(mPreviousMessage);
mPrevious.requestFocus();
@ -878,15 +760,11 @@ public class MessageView extends K9Activity implements OnClickListener {
private void onMarkAsUnread() {
if (mMessage != null) {
// (Issue 3319) mController.setFlag(mAccount, mMessageReference.folderName, new String[] { mMessage.getUid() }, Flag.SEEN, false);
try {
mMessage.setFlag(Flag.SEEN, false);
mMessageView.setHeaders(mMessage, mAccount);
String subject = mMessage.getSubject();
setTitle(subject);
} catch (Exception e) {
Log.e(K9.LOG_TAG, "Unable to unset SEEN flag on message", e);
}
mController.setFlag(mAccount, mMessage.getFolder().getName(),
new Message[] { mMessage }, Flag.SEEN, false);
mMessageView.setHeaders(mMessage, mAccount);
String subject = mMessage.getSubject();
setTitle(subject);
}
}
@ -903,46 +781,35 @@ public class MessageView extends K9Activity implements OnClickListener {
public void onClick(View view) {
switch (view.getId()) {
case R.id.reply:
case R.id.reply_scrolling:
onReply();
break;
case R.id.reply_all:
onReplyAll();
break;
case R.id.delete:
case R.id.delete_scrolling:
onDelete();
break;
case R.id.forward:
case R.id.forward_scrolling:
onForward();
break;
case R.id.archive:
case R.id.archive_scrolling:
onRefile(mAccount.getArchiveFolderName());
break;
case R.id.spam:
case R.id.spam_scrolling:
onRefile(mAccount.getSpamFolderName());
break;
case R.id.move:
case R.id.move_scrolling:
onMove();
break;
case R.id.next:
case R.id.next_scrolling:
onNext();
break;
case R.id.previous:
case R.id.previous_scrolling:
onPrevious();
break;
case R.id.download:
((AttachmentView)view).saveFile();
break;
case R.id.show_pictures:
mMessageView.setLoadPictures(true);
break;
case R.id.download_remainder:
onDownloadRemainder();
break;
@ -994,7 +861,6 @@ public class MessageView extends K9Activity implements OnClickListener {
});
break;
case R.id.select_text:
mTopView.setScrolling(false);
mMessageView.beginSelectingText();
break;
default:
@ -1083,29 +949,7 @@ public class MessageView extends K9Activity implements OnClickListener {
}
return super.onPrepareOptionsMenu(menu);
}
public void displayMessageBody(final Account account, final String folder, final String uid, final Message message) {
runOnUiThread(new Runnable() {
public void run() {
mTopView.scrollTo(0, 0);
try {
if (MessageView.this.mMessage != null
&& MessageView.this.mMessage.isSet(Flag.X_DOWNLOADED_PARTIAL)
&& message.isSet(Flag.X_DOWNLOADED_FULL)) {
mMessageView.setHeaders(message, account);
}
MessageView.this.mMessage = message;
mMessageView.displayMessageBody(account, folder, uid, message, mPgpData);
mMessageView.renderAttachments(mMessage, 0, mMessage, mAccount, mController, mListener);
} catch (MessagingException e) {
if (Config.LOGV) {
Log.v(K9.LOG_TAG, "loadMessageForViewBodyAvailable", e);
}
}
}
});
}
class Listener extends MessagingListener {
@Override
public void loadMessageForViewHeadersAvailable(final Account account, String folder, String uid,
@ -1114,7 +958,6 @@ public class MessageView extends K9Activity implements OnClickListener {
|| !mMessageReference.accountUuid.equals(account.getUuid())) {
return;
}
MessageView.this.mMessage = message;
/*
* Clone the message object because the original could be modified by
@ -1148,17 +991,28 @@ public class MessageView extends K9Activity implements OnClickListener {
}
@Override
public void loadMessageForViewBodyAvailable(Account account, String folder, String uid,
Message message) {
if (!mMessageReference.uid.equals(uid) || !mMessageReference.folderName.equals(folder)
|| !mMessageReference.accountUuid.equals(account.getUuid())) {
public void loadMessageForViewBodyAvailable(final Account account, String folder,
String uid, final Message message) {
if (!mMessageReference.uid.equals(uid) ||
!mMessageReference.folderName.equals(folder) ||
!mMessageReference.accountUuid.equals(account.getUuid())) {
return;
}
displayMessageBody(account, folder, uid, message);
}//loadMessageForViewBodyAvailable
runOnUiThread(new Runnable() {
@Override
public void run() {
try {
mMessage = message;
mMessageView.setMessage(account, (LocalMessage) message, mPgpData,
mController, mListener);
} catch (MessagingException e) {
Log.v(K9.LOG_TAG, "loadMessageForViewBodyAvailable", e);
}
}
});
}
@Override
public void loadMessageForViewFailed(Account account, String folder, String uid, final Throwable t) {
@ -1263,8 +1117,14 @@ public class MessageView extends K9Activity implements OnClickListener {
// This REALLY should be in MessageCryptoView
public void onDecryptDone(PgpData pgpData) {
// TODO: this might not be enough if the orientation was changed while in APG,
// sometimes shows the original encrypted content
mMessageView.loadBodyFromText(mAccount.getCryptoProvider(), mPgpData, mMessage, mPgpData.getDecryptedData(), "text/plain");
Account account = mAccount;
LocalMessage message = (LocalMessage) mMessage;
MessagingController controller = mController;
Listener listener = mListener;
try {
mMessageView.setMessage(account, message, pgpData, controller, listener);
} catch (MessagingException e) {
Log.e(K9.LOG_TAG, "displayMessageBody failed", e);
}
}
}

View File

@ -0,0 +1,106 @@
package com.fsck.k9.activity;
import android.appwidget.AppWidgetManager;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.Bundle;
import com.fsck.k9.Account;
import com.fsck.k9.BaseAccount;
import com.fsck.k9.R;
import com.fsck.k9.provider.UnreadWidgetProvider;
/**
* Activity to select an account for the unread widget.
*/
public class UnreadWidgetConfiguration extends AccountList {
/**
* Name of the preference file to store the widget configuration.
*/
private static final String PREFS_NAME = "unread_widget_configuration.xml";
/**
* Prefix for the preference keys.
*/
private static final String PREF_PREFIX_KEY = "unread_widget.";
/**
* The ID of the widget we are configuring.
*/
private int mAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
// Find the widget ID from the intent.
Intent intent = getIntent();
Bundle extras = intent.getExtras();
if (extras != null) {
mAppWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID);
}
// If they gave us an intent without the widget ID, just bail.
if (mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
finish();
return;
}
setTitle(R.string.unread_widget_select_account);
}
@Override
protected boolean displaySpecialAccounts() {
return false;
}
@Override
protected void onAccountSelected(BaseAccount baseAccount) {
if (!(baseAccount instanceof Account)) {
finish();
return;
}
Account account = (Account) baseAccount;
// Save widget configuration
String accountUuid = account.getUuid();
saveAccountUuid(this, mAppWidgetId, accountUuid);
// Update widget
Context context = getApplicationContext();
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
UnreadWidgetProvider.updateWidget(context, appWidgetManager, mAppWidgetId, accountUuid);
// Let the caller know that the configuration was successful
Intent resultValue = new Intent();
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
setResult(RESULT_OK, resultValue);
finish();
}
private static void saveAccountUuid(Context context, int appWidgetId, String accountUuid) {
SharedPreferences.Editor editor =
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE).edit();
editor.putString(PREF_PREFIX_KEY + appWidgetId, accountUuid);
editor.commit();
}
public static String getAccountUuid(Context context, int appWidgetId) {
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
String accountUuid = prefs.getString(PREF_PREFIX_KEY + appWidgetId, null);
return accountUuid;
}
public static void deleteWidgetConfiguration(Context context, int appWidgetId) {
Editor editor = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE).edit();
editor.remove(PREF_PREFIX_KEY + appWidgetId);
editor.commit();
}
}

View File

@ -18,7 +18,6 @@ import java.util.List;
import com.fsck.k9.Account;
import com.fsck.k9.Account.FolderMode;
import com.fsck.k9.Account.QuoteStyle;
import com.fsck.k9.Account.ScrollButtons;
import com.fsck.k9.K9;
import com.fsck.k9.NotificationSetting;
import com.fsck.k9.Preferences;
@ -54,8 +53,6 @@ public class AccountSettings extends K9PreferenceActivity {
private static final String PREFERENCE_FREQUENCY = "account_check_frequency";
private static final String PREFERENCE_DISPLAY_COUNT = "account_display_count";
private static final String PREFERENCE_DEFAULT = "account_default";
private static final String PREFERENCE_HIDE_BUTTONS = "hide_buttons_enum";
private static final String PREFERENCE_HIDE_MOVE_BUTTONS = "hide_move_buttons_enum";
private static final String PREFERENCE_SHOW_PICTURES = "show_pictures_enum";
private static final String PREFERENCE_ENABLE_MOVE_BUTTONS = "enable_move_buttons";
private static final String PREFERENCE_NOTIFY = "account_notify";
@ -100,7 +97,7 @@ public class AccountSettings extends K9PreferenceActivity {
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";
private static final String PREFERENCE_SENT_FOLDER = "sent_folder";
@ -124,8 +121,6 @@ public class AccountSettings extends K9PreferenceActivity {
private CheckBoxPreference mAccountDefault;
private CheckBoxPreference mAccountNotify;
private CheckBoxPreference mAccountNotifySelf;
private ListPreference mAccountScrollButtons;
private ListPreference mAccountScrollMoveButtons;
private ListPreference mAccountShowPictures;
private CheckBoxPreference mAccountEnableMoveButtons;
private CheckBoxPreference mAccountNotifySync;
@ -428,37 +423,10 @@ public class AccountSettings extends K9PreferenceActivity {
mAccountDefault.setChecked(
mAccount.equals(Preferences.getPreferences(this).getDefaultAccount()));
mAccountScrollButtons = (ListPreference) findPreference(PREFERENCE_HIDE_BUTTONS);
mAccountScrollButtons.setValue("" + mAccount.getScrollMessageViewButtons());
mAccountScrollButtons.setSummary(mAccountScrollButtons.getEntry());
mAccountScrollButtons.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
public boolean onPreferenceChange(Preference preference, Object newValue) {
final String summary = newValue.toString();
int index = mAccountScrollButtons.findIndexOfValue(summary);
mAccountScrollButtons.setSummary(mAccountScrollButtons.getEntries()[index]);
mAccountScrollButtons.setValue(summary);
return false;
}
});
mAccountEnableMoveButtons = (CheckBoxPreference) findPreference(PREFERENCE_ENABLE_MOVE_BUTTONS);
mAccountEnableMoveButtons.setEnabled(mIsMoveCapable);
mAccountEnableMoveButtons.setChecked(mAccount.getEnableMoveButtons());
mAccountScrollMoveButtons = (ListPreference) findPreference(PREFERENCE_HIDE_MOVE_BUTTONS);
mAccountScrollMoveButtons.setEnabled(mIsMoveCapable);
mAccountScrollMoveButtons.setValue("" + mAccount.getScrollMessageViewMoveButtons());
mAccountScrollMoveButtons.setSummary(mAccountScrollMoveButtons.getEntry());
mAccountScrollMoveButtons.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
public boolean onPreferenceChange(Preference preference, Object newValue) {
final String summary = newValue.toString();
int index = mAccountScrollMoveButtons.findIndexOfValue(summary);
mAccountScrollMoveButtons.setSummary(mAccountScrollMoveButtons.getEntries()[index]);
mAccountScrollMoveButtons.setValue(summary);
return false;
}
});
mAccountShowPictures = (ListPreference) findPreference(PREFERENCE_SHOW_PICTURES);
mAccountShowPictures.setValue("" + mAccount.getShowPictures());
mAccountShowPictures.setSummary(mAccountShowPictures.getEntry());
@ -745,11 +713,13 @@ public class AccountSettings extends K9PreferenceActivity {
else
mAccount.setAutoExpandFolderName(reverseTranslateFolder(mAutoExpandFolder.getValue()));
mAccount.setArchiveFolderName(mArchiveFolder.getValue());
mAccount.setDraftsFolderName(mDraftsFolder.getValue());
mAccount.setSentFolderName(mSentFolder.getValue());
mAccount.setSpamFolderName(mSpamFolder.getValue());
mAccount.setTrashFolderName(mTrashFolder.getValue());
if (mIsMoveCapable) {
mAccount.setArchiveFolderName(mArchiveFolder.getValue());
mAccount.setDraftsFolderName(mDraftsFolder.getValue());
mAccount.setSentFolderName(mSentFolder.getValue());
mAccount.setSpamFolderName(mSpamFolder.getValue());
mAccount.setTrashFolderName(mTrashFolder.getValue());
}
if (mIsPushCapable) {
@ -760,10 +730,8 @@ public class AccountSettings extends K9PreferenceActivity {
if (!mIsMoveCapable) {
mAccount.setEnableMoveButtons(false);
mAccount.setScrollMessageViewMoveButtons(ScrollButtons.NEVER);
} else {
mAccount.setEnableMoveButtons(mAccountEnableMoveButtons.isChecked());
mAccount.setScrollMessageViewMoveButtons(Account.ScrollButtons.valueOf(mAccountScrollMoveButtons.getValue()));
}
boolean needsRefresh = mAccount.setAutomaticCheckIntervalMinutes(Integer.parseInt(mCheckFrequency.getValue()));
@ -782,24 +750,23 @@ public class AccountSettings extends K9PreferenceActivity {
}
}
mAccount.setScrollMessageViewButtons(Account.ScrollButtons.valueOf(mAccountScrollButtons.getValue()));
mAccount.setShowPictures(Account.ShowPictures.valueOf(mAccountShowPictures.getValue()));
if (mIsPushCapable) {
boolean needsPushRestart = mAccount.setFolderPushMode(Account.FolderMode.valueOf(mPushMode.getValue()));
if (mAccount.getFolderPushMode() != FolderMode.NONE) {
needsPushRestart |= displayModeChanged;
needsPushRestart |= mIncomingChanged;
}
if (needsRefresh && needsPushRestart) {
MailService.actionReset(this, null);
} else if (needsRefresh) {
MailService.actionReschedulePoll(this, null);
} else if (needsPushRestart) {
MailService.actionRestartPushers(this, null);
}
}
if (mIsPushCapable) {
boolean needsPushRestart = mAccount.setFolderPushMode(Account.FolderMode.valueOf(mPushMode.getValue()));
if (mAccount.getFolderPushMode() != FolderMode.NONE) {
needsPushRestart |= displayModeChanged;
needsPushRestart |= mIncomingChanged;
}
if (needsRefresh && needsPushRestart) {
MailService.actionReset(this, null);
} else if (needsRefresh) {
MailService.actionReschedulePoll(this, null);
} else if (needsPushRestart) {
MailService.actionRestartPushers(this, null);
}
}
// TODO: refresh folder list here
mAccount.save(Preferences.getPreferences(this));
}
@ -946,22 +913,33 @@ public class AccountSettings extends K9PreferenceActivity {
mTrashFolder = (ListPreference)findPreference(PREFERENCE_TRASH_FOLDER);
mTrashFolder.setEnabled(false);
if (!mIsMoveCapable) {
PreferenceScreen foldersCategory =
(PreferenceScreen) findPreference(PREFERENCE_CATEGORY_FOLDERS);
foldersCategory.removePreference(mArchiveFolder);
foldersCategory.removePreference(mSpamFolder);
foldersCategory.removePreference(mDraftsFolder);
foldersCategory.removePreference(mSentFolder);
foldersCategory.removePreference(mTrashFolder);
}
}
@Override
protected void onPostExecute(Void res) {
initListPreference(mAutoExpandFolder, mAccount.getAutoExpandFolderName(), allFolderLabels, allFolderValues);
initListPreference(mArchiveFolder, mAccount.getArchiveFolderName(), allFolderLabels, allFolderValues);
initListPreference(mDraftsFolder, mAccount.getDraftsFolderName(), allFolderLabels, allFolderValues);
initListPreference(mSentFolder, mAccount.getSentFolderName(), allFolderLabels, allFolderValues);
initListPreference(mSpamFolder, mAccount.getSpamFolderName(), allFolderLabels, allFolderValues);
initListPreference(mTrashFolder, mAccount.getTrashFolderName(), allFolderLabels, allFolderValues);
mAutoExpandFolder.setEnabled(true);
mArchiveFolder.setEnabled(true);
mDraftsFolder.setEnabled(true);
mSentFolder.setEnabled(true);
mSpamFolder.setEnabled(true);
mTrashFolder.setEnabled(true);
if (mIsMoveCapable) {
initListPreference(mArchiveFolder, mAccount.getArchiveFolderName(), allFolderLabels, allFolderValues);
initListPreference(mDraftsFolder, mAccount.getDraftsFolderName(), allFolderLabels, allFolderValues);
initListPreference(mSentFolder, mAccount.getSentFolderName(), allFolderLabels, allFolderValues);
initListPreference(mSpamFolder, mAccount.getSpamFolderName(), allFolderLabels, allFolderValues);
initListPreference(mTrashFolder, mAccount.getTrashFolderName(), allFolderLabels, allFolderValues);
mArchiveFolder.setEnabled(true);
mSpamFolder.setEnabled(true);
mDraftsFolder.setEnabled(true);
mSentFolder.setEnabled(true);
mTrashFolder.setEnabled(true);
}
}
}
}

View File

@ -232,6 +232,11 @@ public class AccountSetupBasics extends K9Activity
mAccount.setSpamFolderName(getString(R.string.special_mailbox_name_spam));
}
mAccount.setSentFolderName(getString(R.string.special_mailbox_name_sent));
if (incomingUri.toString().startsWith("imap")) {
mAccount.setDeletePolicy(Account.DELETE_POLICY_ON_DELETE);
} else if (incomingUri.toString().startsWith("pop3")) {
mAccount.setDeletePolicy(Account.DELETE_POLICY_NEVER);
}
AccountSetupCheckSettings.actionCheckSettings(this, mAccount, true, true);
} catch (UnsupportedEncodingException enc) {
// This really shouldn't happen since the encoding is hardcoded to UTF-8
@ -310,6 +315,13 @@ public class AccountSetupBasics extends K9Activity
mAccount.setDraftsFolderName(getString(R.string.special_mailbox_name_drafts));
mAccount.setTrashFolderName(getString(R.string.special_mailbox_name_trash));
mAccount.setSentFolderName(getString(R.string.special_mailbox_name_sent));
mAccount.setArchiveFolderName(getString(R.string.special_mailbox_name_archive));
// Yahoo! has a special folder for Spam, called "Bulk Mail".
if (domain.endsWith(".yahoo.com")) {
mAccount.setSpamFolderName("Bulk Mail");
} else {
mAccount.setSpamFolderName(getString(R.string.special_mailbox_name_spam));
}
AccountSetupAccountType.actionSelectAccountType(this, mAccount, mDefaultView.isChecked());
finish();

View File

@ -117,6 +117,7 @@ public class MessagingController implements Runnable {
private static final String PENDING_COMMAND_MOVE_OR_COPY = "com.fsck.k9.MessagingController.moveOrCopy";
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_SET_FLAG = "com.fsck.k9.MessagingController.setFlag";
@ -1845,9 +1846,7 @@ public class MessagingController implements Runnable {
* right now, attachments will be left for later.
*/
ArrayList<Part> viewables = new ArrayList<Part>();
ArrayList<Part> attachments = new ArrayList<Part>();
MimeUtility.collectParts(message, viewables, attachments);
Set<Part> viewables = MimeUtility.collectTextParts(message);
/*
* Now download the parts we're interested in storing.
@ -2056,6 +2055,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_MOVE_OR_COPY.equals(command.command)) {
processPendingMoveOrCopyOld(command, account);
@ -2235,16 +2236,72 @@ public class MessagingController implements Runnable {
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 + 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);
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 trash message command.
*
@ -2256,6 +2313,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)) {
@ -2263,14 +2321,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));
}
}
}
@ -2291,6 +2377,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");
@ -2304,9 +2392,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);
}
}
if (!isCopy && Account.EXPUNGE_IMMEDIATELY.equals(account.getExpungePolicy())) {
@ -2315,12 +2403,32 @@ 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);
localDestMessage.setUid(newUid);
localDestFolder.changeUid((LocalMessage)localDestMessage);
for (MessagingListener l : getListeners()) {
l.messageUidChanged(account, destFolder, localDestUid, newUid);
}
}
}
} finally {
closeFolder(remoteSrcFolder);
closeFolder(remoteDestFolder);
}
}
private void queueSetFlag(final Account account, final String folderName, final String newState, final String flag, final String[] uids) {
@ -2673,65 +2781,117 @@ public class MessagingController implements Runnable {
@Override
public void act(final Account account, final Folder folder,
final List<Message> messages) {
String[] uids = new String[messages.size()];
for (int i = 0; i < messages.size(); i++) {
uids[i] = messages.get(i).getUid();
}
setFlag(account, folder.getRemoteName(), uids, flag, newState);
setFlag(account, folder.getRemoteName(), messages.toArray(EMPTY_MESSAGE_ARRAY), flag,
newState);
}
});
}
public void setFlag(
final Account account,
final String folderName,
final String[] uids,
final Flag flag,
final boolean newState) {
// TODO: put this into the background, but right now that causes odd behavior
// because the FolderMessageList doesn't have its own cache of the flag states
/**
* Set or remove a flag for a set of messages in a specific folder.
*
* <p>
* The {@link Message} objects passed in are updated to reflect the new flag state.
* </p>
*
* @param account
* The account the folder containing the messages belongs to.
* @param folderName
* The name of the folder.
* @param messages
* The messages to change the flag for.
* @param flag
* The flag to change.
* @param newState
* {@code true}, if the flag should be set. {@code false} if it should be removed.
*/
public void setFlag(Account account, String folderName, Message[] messages, Flag flag,
boolean newState) {
// TODO: Put this into the background, but right now some callers depend on the message
// objects being modified right after this method returns.
Folder localFolder = null;
try {
Store localStore = account.getLocalStore();
localFolder = localStore.getFolder(folderName);
localFolder.open(OpenMode.READ_WRITE);
ArrayList<Message> messages = new ArrayList<Message>();
for (String uid : uids) {
// Allows for re-allowing sending of messages that could not be sent
if (flag == Flag.FLAGGED && !newState
&& uid != null
&& account.getOutboxFolderName().equals(folderName)) {
sendCount.remove(uid);
}
Message msg = localFolder.getMessage(uid);
if (msg != null) {
messages.add(msg);
// Allows for re-allowing sending of messages that could not be sent
if (flag == Flag.FLAGGED && !newState &&
account.getOutboxFolderName().equals(folderName)) {
for (Message message : messages) {
String uid = message.getUid();
if (uid != null) {
sendCount.remove(uid);
}
}
}
localFolder.setFlags(messages.toArray(EMPTY_MESSAGE_ARRAY), new Flag[] {flag}, newState);
// Update the messages in the local store
localFolder.setFlags(messages, new Flag[] {flag}, newState);
for (MessagingListener l : getListeners()) {
l.folderStatusChanged(account, folderName, localFolder.getUnreadMessageCount());
}
/*
* Handle the remote side
*/
// The error folder is always a local folder
// TODO: Skip the remote part for all local-only folders
if (account.getErrorFolderName().equals(folderName)) {
return;
}
String[] uids = new String[messages.length];
for (int i = 0, end = uids.length; i < end; i++) {
uids[i] = messages[i].getUid();
}
queueSetFlag(account, folderName, Boolean.toString(newState), flag.toString(), uids);
processPendingCommands(account);
} catch (MessagingException me) {
addErrorMessage(account, null, me);
throw new RuntimeException(me);
} finally {
closeFolder(localFolder);
}
}//setMesssageFlag
}
/**
* Set or remove a flag for a message referenced by message UID.
*
* @param account
* The account the folder containing the message belongs to.
* @param folderName
* The name of the folder.
* @param uid
* The UID of the message to change the flag for.
* @param flag
* The flag to change.
* @param newState
* {@code true}, if the flag should be set. {@code false} if it should be removed.
*/
public void setFlag(Account account, String folderName, String uid, Flag flag,
boolean newState) {
Folder localFolder = null;
try {
LocalStore localStore = account.getLocalStore();
localFolder = localStore.getFolder(folderName);
localFolder.open(OpenMode.READ_WRITE);
Message message = localFolder.getMessage(uid);
if (message != null) {
setFlag(account, folderName, new Message[] { message }, flag, newState);
}
} catch (MessagingException me) {
addErrorMessage(account, null, me);
throw new RuntimeException(me);
} finally {
closeFolder(localFolder);
}
}
public void clearAllPending(final Account account) {
try {
@ -2916,9 +3076,7 @@ public class MessagingController implements Runnable {
try {
LocalStore localStore = account.getLocalStore();
ArrayList<Part> viewables = new ArrayList<Part>();
ArrayList<Part> attachments = new ArrayList<Part>();
MimeUtility.collectParts(message, viewables, attachments);
List<Part> attachments = MimeUtility.collectAttachments(message);
for (Part attachment : attachments) {
attachment.setBody(null);
}
@ -3385,6 +3543,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>();
Store localStore = account.getLocalStore();
Store remoteStore = account.getRemoteStore();
if (!isCopy && (!remoteStore.isMoveCapable() || !localStore.isMoveCapable())) {
@ -3397,12 +3556,17 @@ public class MessagingController implements Runnable {
Folder localSrcFolder = localStore.getFolder(srcFolder);
Folder localDestFolder = localStore.getFolder(destFolder);
boolean unreadCountAffected = false;
List<String> uids = new LinkedList<String>();
for (Message message : inMessages) {
String uid = message.getUid();
if (!uid.startsWith(K9.LOCAL_UID_PREFIX)) {
uids.add(uid);
}
if (!unreadCountAffected && !message.isSet(Flag.SEEN)) {
unreadCountAffected = true;
}
}
Message[] messages = localSrcFolder.getMessages(uids.toArray(EMPTY_STRING_ARRAY), null);
@ -3422,9 +3586,18 @@ 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
// folder, notify the listeners.
int unreadMessageCount = localDestFolder.getUnreadMessageCount();
for (MessagingListener l : getListeners()) {
l.folderStatusChanged(account, destFolder, unreadMessageCount);
}
}
} else {
localSrcFolder.moveMessages(messages, localDestFolder);
uidMap = localSrcFolder.moveMessages(messages, localDestFolder);
for (Map.Entry<String, Message> entry : origUidMap.entrySet()) {
String origUid = entry.getKey();
Message message = entry.getValue();
@ -3433,9 +3606,20 @@ public class MessagingController implements Runnable {
}
unsuppressMessage(account, srcFolder, origUid);
}
if (unreadCountAffected) {
// If this move operation changes the unread count, notify the listeners
// that the unread count changed in both the source and destination folder.
int unreadMessageCountSrc = localSrcFolder.getUnreadMessageCount();
int unreadMessageCountDest = localDestFolder.getUnreadMessageCount();
for (MessagingListener l : getListeners()) {
l.folderStatusChanged(account, srcFolder, unreadMessageCountSrc);
l.folderStatusChanged(account, destFolder, unreadMessageCountDest);
}
}
}
queueMoveOrCopy(account, srcFolder, destFolder, isCopy, origUidMap.keySet().toArray(EMPTY_STRING_ARRAY));
queueMoveOrCopy(account, srcFolder, destFolder, isCopy, origUidMap.keySet().toArray(EMPTY_STRING_ARRAY), uidMap);
}
processPendingCommands(account);
@ -3465,9 +3649,11 @@ public class MessagingController implements Runnable {
localFolder = localStore.getFolder(account.getDraftsFolderName());
localFolder.open(OpenMode.READ_WRITE);
String uid = localFolder.getMessageUidById(id);
Message message = localFolder.getMessage(uid);
if (message != null) {
deleteMessages(new Message[] { message }, null);
if (uid != null) {
Message message = localFolder.getMessage(uid);
if (message != null) {
deleteMessages(new Message[] { message }, null);
}
}
} catch (MessagingException me) {
addErrorMessage(account, null, me);
@ -3513,6 +3699,7 @@ public class MessagingController implements Runnable {
}
Store 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");
@ -3527,7 +3714,7 @@ public class MessagingController implements Runnable {
if (K9.DEBUG)
Log.d(K9.LOG_TAG, "Deleting messages in normal folder, moving");
localFolder.moveMessages(messages, localTrashFolder);
uidMap = localFolder.moveMessages(messages, localTrashFolder);
}
}
@ -3560,7 +3747,7 @@ public class MessagingController implements Runnable {
if (folder.equals(account.getTrashFolderName())) {
queueSetFlag(account, folder, Boolean.toString(true), Flag.DELETED.toString(), uids);
} else {
queueMoveOrCopy(account, folder, account.getTrashFolderName(), false, uids);
queueMoveOrCopy(account, folder, account.getTrashFolderName(), false, uids, uidMap);
}
processPendingCommands(account);
} else if (account.getDeletePolicy() == Account.DELETE_POLICY_MARK_AS_READ) {
@ -3622,12 +3809,14 @@ public class MessagingController implements Runnable {
putBackground("emptyTrash", listener, new Runnable() {
@Override
public void run() {
Folder localFolder = null;
LocalFolder localFolder = null;
try {
Store localStore = account.getLocalStore();
localFolder = localStore.getFolder(account.getTrashFolderName());
localFolder = (LocalFolder) localStore.getFolder(account.getTrashFolderName());
localFolder.open(OpenMode.READ_WRITE);
localFolder.setFlags(new Flag[] { Flag.DELETED }, true);
localFolder.setUnreadMessageCount(0);
localFolder.setFlaggedMessageCount(0);
for (MessagingListener l : getListeners()) {
l.emptyTrashCompleted(account);

View File

@ -12,191 +12,154 @@ import com.fsck.k9.mail.Part;
import java.util.List;
/**
* Defines the interface that MessagingController will use to callback to requesters. This class
* is defined as non-abstract so that someone who wants to receive only a few messages can
* do so without implementing the entire interface. It is highly recommended that users of
* this interface use the @Override annotation in their implementations to avoid being caught by
* Defines the interface that {@link MessagingController} will use to callback to requesters.
*
* <p>
* This class is defined as non-abstract so that someone who wants to receive only a few messages
* can do so without implementing the entire interface. It is highly recommended that users of this
* interface use the {@code @Override} annotation in their implementations to avoid being caught by
* changes in this class.
* </p>
*/
public class MessagingListener {
public void searchStats(AccountStats stats) {}
public void accountStatusChanged(BaseAccount account, AccountStats stats) {
}
public void accountSizeChanged(Account account, long oldSize, long newSize) {
}
public void accountStatusChanged(BaseAccount account, AccountStats stats) {}
public void listFoldersStarted(Account account) {
}
public void accountSizeChanged(Account account, long oldSize, long newSize) {}
public void listFolders(Account account, Folder[] folders) {
}
public void listFoldersFailed(Account account, String message) {
}
public void listFoldersStarted(Account account) {}
public void listFoldersFinished(Account account) {
}
public void listFolders(Account account, Folder[] folders) {}
public void listLocalMessagesStarted(Account account, String folder) {
}
public void listFoldersFinished(Account account) {}
public void listLocalMessages(Account account, String folder, Message[] messages) {
}
public void listFoldersFailed(Account account, String message) {}
public void listLocalMessagesAddMessages(Account account, String folder, List<Message> messages) {
}
public void listLocalMessagesUpdateMessage(Account account, String folder, Message message) {
}
public void listLocalMessagesStarted(Account account, String folder) {}
public void listLocalMessagesRemoveMessage(Account account, String folder, Message message) {
}
public void listLocalMessages(Account account, String folder, Message[] messages) {}
public void listLocalMessagesFailed(Account account, String folder, String message) {
}
public void listLocalMessagesAddMessages(Account account, String folder,
List<Message> messages) {}
public void listLocalMessagesFinished(Account account, String folder) {
}
public void listLocalMessagesUpdateMessage(Account account, String folder, Message message) {}
public void synchronizeMailboxStarted(Account account, String folder) {
}
public void listLocalMessagesRemoveMessage(Account account, String folder, Message message) {}
public void synchronizeMailboxHeadersStarted(Account account, String folder) {
}
public void listLocalMessagesFinished(Account account, String folder) {}
public void synchronizeMailboxHeadersProgress(Account account, String folder, int completed, int total) {
}
public void listLocalMessagesFailed(Account account, String folder, String message) {}
public void synchronizeMailboxStarted(Account account, String folder) {}
public void synchronizeMailboxHeadersStarted(Account account, String folder) {}
public void synchronizeMailboxHeadersProgress(Account account, String folder,
int completed, int total) {}
public void synchronizeMailboxHeadersFinished(Account account, String folder,
int totalMessagesInMailbox, int numNewMessages) {
}
int totalMessagesInMailbox, int numNewMessages) {}
public void synchronizeMailboxProgress(Account account, String folder, int completed,
int total) {}
public void synchronizeMailboxProgress(Account account, String folder, int completed, int total)
{}
public void synchronizeMailboxNewMessage(Account account, String folder, Message message) {}
public void synchronizeMailboxNewMessage(Account account, String folder, Message message) {
}
public void synchronizeMailboxAddOrUpdateMessage(Account account, String folder,
Message message) {}
public void synchronizeMailboxAddOrUpdateMessage(Account account, String folder, Message message) {
}
public void synchronizeMailboxRemovedMessage(Account account, String folder, Message message) {
}
public void synchronizeMailboxRemovedMessage(Account account, String folder,
Message message) {}
public void synchronizeMailboxFinished(Account account, String folder,
int totalMessagesInMailbox, int numNewMessages) {
}
int totalMessagesInMailbox, int numNewMessages) {}
public void synchronizeMailboxFailed(Account account, String folder,
String message) {
}
public void synchronizeMailboxFailed(Account account, String folder, String message) {}
public void loadMessageForViewStarted(Account account, String folder, String uid) {
}
public void loadMessageForViewStarted(Account account, String folder, String uid) {}
public void loadMessageForViewHeadersAvailable(Account account, String folder, String uid,
Message message) {
}
Message message) {}
public void loadMessageForViewBodyAvailable(Account account, String folder, String uid,
Message message) {
}
Message message) {}
public void loadMessageForViewFinished(Account account, String folder, String uid,
Message message) {
}
Message message) {}
public void loadMessageForViewFailed(Account account, String folder, String uid, Throwable t) {
}
public void loadMessageForViewFailed(Account account, String folder, String uid,
Throwable t) {}
/**
* Called when a message for view has been fully displayed on the screen.
*/
public void messageViewFinished() {}
public void checkMailStarted(Context context, Account account) {
}
public void checkMailFinished(Context context, Account account) {
}
public void checkMailStarted(Context context, Account account) {}
public void checkMailFailed(Context context, Account account, String reason) {
}
public void checkMailFinished(Context context, Account account) {}
public void sendPendingMessagesStarted(Account account) {
}
public void checkMailFailed(Context context, Account account, String reason) {}
public void sendPendingMessagesCompleted(Account account) {
}
public void sendPendingMessagesFailed(Account account) {
}
public void sendPendingMessagesStarted(Account account) {}
public void messageDeleted(Account account, String folder, Message message) {
public void sendPendingMessagesCompleted(Account account) {}
}
public void emptyTrashCompleted(Account account) {
}
public void sendPendingMessagesFailed(Account account) {}
public void folderStatusChanged(Account account, String folderName, int unreadMessageCount) {
}
public void folderStatusChanged(Account account, String folderName) {
}
public void emptyTrashCompleted(Account account) {}
public void systemStatusChanged() {
}
public void messageUidChanged(Account account, String folder, String oldUid, String newUid) {
public void folderStatusChanged(Account account, String folderName, int unreadMessageCount) {}
}
public void setPushActive(Account account, String folderName, boolean enabled) {
public void systemStatusChanged() {}
}
public void loadAttachmentStarted(
Account account,
Message message,
Part part,
Object tag,
boolean requiresDownload) {
}
public void messageDeleted(Account account, String folder, Message message) {}
public void loadAttachmentFinished(
Account account,
Message message,
Part part,
Object tag) {
}
public void messageUidChanged(Account account, String folder, String oldUid, String newUid) {}
public void loadAttachmentFailed(
Account account,
Message message,
Part part,
Object tag,
String reason) {
}
public void setPushActive(Account account, String folderName, boolean enabled) {}
public void loadAttachmentStarted(Account account, Message message, Part part, Object tag,
boolean requiresDownload) {}
public void loadAttachmentFinished(Account account, Message message, Part part, Object tag) {}
public void loadAttachmentFailed(Account account, Message message, Part part, Object tag,
String reason) {}
public void pendingCommandStarted(Account account, String commandTitle) {}
public void pendingCommandsProcessing(Account account) {}
public void pendingCommandsFinished(Account account) {}
public void pendingCommandStarted(Account account, String commandTitle)
{}
public void pendingCommandCompleted(Account account, String commandTitle)
{}
public void pendingCommandCompleted(Account account, String commandTitle) {}
public void pendingCommandsFinished(Account account) {}
/**
* General notification messages subclasses can override to be notified that the controller
* has completed a command. This is useful for turning off progress indicators that may have
* been left over from previous commands.
* @param moreCommandsToRun True if the controller will continue on to another command
* immediately.
*
* @param moreCommandsToRun
* {@code true} if the controller will continue on to another command immediately.
* {@code false} otherwise.
*/
public void controllerCommandCompleted(boolean moreCommandsToRun) {
}
public void controllerCommandCompleted(boolean moreCommandsToRun) {}
}

View File

@ -125,19 +125,41 @@ public class HtmlConverter {
private static final int MAX_SMART_HTMLIFY_MESSAGE_LENGTH = 1024 * 256 ;
public static final String getHtmlHeader() {
return "<html><head/><body>";
}
public static final String getHtmlFooter() {
return "</body></html>";
}
/**
* Naively convert a text string into an HTML document. This method avoids using regular expressions on the entire
* message body to save memory.
* @param text Plain text string.
* Naively convert a text string into an HTML document.
*
* <p>
* This method avoids using regular expressions on the entire message body to save memory.
* </p>
*
* @param text
* Plain text string.
* @param useHtmlTag
* If {@code true} this method adds headers and footers to create a proper HTML
* document.
*
* @return HTML string.
*/
private static String simpleTextToHtml(String text) {
private static String simpleTextToHtml(String text, boolean useHtmlTag) {
// Encode HTML entities to make sure we don't display something evil.
text = TextUtils.htmlEncode(text);
StringReader reader = new StringReader(text);
StringBuilder buff = new StringBuilder(text.length() + TEXT_TO_HTML_EXTRA_BUFFER_LENGTH);
buff.append("<html><head/><body>");
if (useHtmlTag) {
buff.append(getHtmlHeader());
}
buff.append(htmlifyMessageHeader());
int c;
try {
@ -159,25 +181,39 @@ public class HtmlConverter {
Log.e(K9.LOG_TAG, "Could not read string to convert text to HTML:", e);
}
buff.append("</body></html>");
buff.append(htmlifyMessageFooter());
if (useHtmlTag) {
buff.append(getHtmlFooter());
}
return buff.toString();
}
/**
* Convert a text string into an HTML document. Attempts to do smart replacement for large
* documents to prevent OOM errors. This method adds headers and footers to create a proper HTML
* document. To convert to a fragment, use {@link #textToHtmlFragment(String)}.
* @param text Plain text string.
* Convert a text string into an HTML document.
*
* <p>
* Attempts to do smart replacement for large documents to prevent OOM errors. This method
* optionally adds headers and footers to create a proper HTML document. To convert to a
* fragment, use {@link #textToHtmlFragment(String)}.
* </p>
*
* @param text
* Plain text string.
* @param useHtmlTag
* If {@code true} this method adds headers and footers to create a proper HTML
* document.
*
* @return HTML string.
*/
public static String textToHtml(String text) {
public static String textToHtml(String text, boolean useHtmlTag) {
// Our HTMLification code is somewhat memory intensive
// and was causing lots of OOM errors on the market
// if the message is big and plain text, just do
// a trivial htmlification
if (text.length() > MAX_SMART_HTMLIFY_MESSAGE_LENGTH) {
return simpleTextToHtml(text);
return simpleTextToHtml(text, useHtmlTag);
}
StringReader reader = new StringReader(text);
StringBuilder buff = new StringBuilder(text.length() + TEXT_TO_HTML_EXTRA_BUFFER_LENGTH);
@ -221,11 +257,19 @@ public class HtmlConverter {
text = text.replaceAll("(?m)(\r\n|\n|\r){4,}", "\n\n");
StringBuffer sb = new StringBuffer(text.length() + TEXT_TO_HTML_EXTRA_BUFFER_LENGTH);
sb.append("<html><head></head><body>");
if (useHtmlTag) {
sb.append(getHtmlHeader());
}
sb.append(htmlifyMessageHeader());
linkifyText(text, sb);
sb.append(htmlifyMessageFooter());
sb.append("</body></html>");
if (useHtmlTag) {
sb.append(getHtmlFooter());
}
text = sb.toString();
return text;

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;
@ -102,11 +103,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 {
for (Message message : msgs) {

View File

@ -1,9 +1,14 @@
package com.fsck.k9.mail.internet;
import android.content.Context;
import android.util.Log;
import com.fsck.k9.K9;
import com.fsck.k9.R;
import com.fsck.k9.helper.HtmlConverter;
import com.fsck.k9.mail.*;
import com.fsck.k9.mail.Message.RecipientType;
import org.apache.commons.io.IOUtils;
import org.apache.james.mime4j.codec.Base64InputStream;
import org.apache.james.mime4j.codec.QuotedPrintableInputStream;
@ -12,7 +17,11 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.regex.Pattern;
import java.nio.charset.Charset;
import java.nio.charset.IllegalCharsetNameException;
@ -23,6 +32,9 @@ public class MimeUtility {
public static final String K9_SETTINGS_MIME_TYPE = "application/x-k9settings";
private static final String TEXT_DIVIDER =
"------------------------------------------------------------------------";
/*
* http://www.w3schools.com/media/media_mimeref.asp
* +
@ -1100,49 +1112,867 @@ public class MimeUtility {
return tempBody;
}
/**
* An unfortunately named method that makes decisions about a Part (usually a Message)
* as to which of it's children will be "viewable" and which will be attachments.
* The method recursively sorts the viewables and attachments into seperate
* lists for further processing.
* @param part
* @param viewables
* @param attachments
* @throws MessagingException
* Empty base class for the class hierarchy used by
* {@link MimeUtility#extractTextAndAttachments(Context, Message)}.
*
* @see Text
* @see Html
* @see MessageHeader
* @see Alternative
*/
public static void collectParts(Part part, ArrayList<Part> viewables,
ArrayList<Part> attachments) throws MessagingException {
/*
* If the part is Multipart but not alternative it's either mixed or
* something we don't know about, which means we treat it as mixed
* per the spec. We just process it's pieces recursively.
static abstract class Viewable { /* empty */ }
/**
* Class representing textual parts of a message that aren't marked as attachments.
*
* @see MimeUtility#isPartTextualBody(Part)
*/
static abstract class Textual extends Viewable {
private Part mPart;
public Textual(Part part) {
mPart = part;
}
public Part getPart() {
return mPart;
}
}
/**
* Class representing a {@code text/plain} part of a message.
*/
static class Text extends Textual {
public Text(Part part) {
super(part);
}
}
/**
* Class representing a {@code text/html} part of a message.
*/
static class Html extends Textual {
public Html(Part part) {
super(part);
}
}
/**
* Class representing a {@code message/rfc822} part of a message.
*
* <p>
* This is used to extract basic header information when the message contents are displayed
* inline.
* </p>
*/
static class MessageHeader extends Viewable {
private Part mContainerPart;
private Message mMessage;
public MessageHeader(Part containerPart, Message message) {
mContainerPart = containerPart;
mMessage = message;
}
public Part getContainerPart() {
return mContainerPart;
}
public Message getMessage() {
return mMessage;
}
}
/**
* Class representing a {@code multipart/alternative} part of a message.
*
* <p>
* Only relevant {@code text/plain} and {@code text/html} children are stored in this container
* class.
* </p>
*/
static class Alternative extends Viewable {
private List<Viewable> mText;
private List<Viewable> mHtml;
public Alternative(List<Viewable> text, List<Viewable> html) {
mText = text;
mHtml = html;
}
public List<Viewable> getText() {
return mText;
}
public List<Viewable> getHtml() {
return mHtml;
}
}
/**
* Store viewable text of a message as plain text and HTML, and the parts considered
* attachments.
*
* @see MimeUtility#extractTextAndAttachments(Context, Message)
*/
public static class ViewableContainer {
/**
* The viewable text of the message in plain text.
*/
if (part.getBody() instanceof Multipart) {
Multipart mp = (Multipart)part.getBody();
for (int i = 0; i < mp.getCount(); i++) {
collectParts(mp.getBodyPart(i), viewables, attachments);
public final String text;
/**
* The viewable text of the message in HTML.
*/
public final String html;
/**
* The parts of the message considered attachments (everything not viewable).
*/
public final List<Part> attachments;
ViewableContainer(String text, String html, List<Part> attachments) {
this.text = text;
this.html = html;
this.attachments = attachments;
}
}
/**
* Collect attachment parts of a message.
*
* @param message
* The message to collect the attachment parts from.
*
* @return A list of parts regarded as attachments.
*
* @throws MessagingException
* In case of an error.
*/
public static List<Part> collectAttachments(Message message)
throws MessagingException {
try {
List<Part> attachments = new ArrayList<Part>();
getViewables(message, attachments);
return attachments;
} catch (Exception e) {
throw new MessagingException("Couldn't collect attachment parts", e);
}
}
/**
* Collect the viewable textual parts of a message.
*
* @param message
* The message to extract the viewable parts from.
*
* @return A set of viewable parts of the message.
*
* @throws MessagingException
* In case of an error.
*/
public static Set<Part> collectTextParts(Message message)
throws MessagingException {
try {
List<Part> attachments = new ArrayList<Part>();
// Collect all viewable parts
List<Viewable> viewables = getViewables(message, attachments);
// Extract the Part references
return getParts(viewables);
} catch (Exception e) {
throw new MessagingException("Couldn't extract viewable parts", e);
}
}
/**
* Extract the viewable textual parts of a message and return the rest as attachments.
*
* @param context
* A {@link Context} instance that will be used to get localized strings.
* @param message
* The message to extract the text and attachments from.
*
* @return A {@link ViewableContainer} instance containing the textual parts of the message as
* plain text and HTML, and a list of message parts considered attachments.
*
* @throws MessagingException
* In case of an error.
*/
public static ViewableContainer extractTextAndAttachments(Context context, Message message)
throws MessagingException {
try {
List<Part> attachments = new ArrayList<Part>();
// Collect all viewable parts
List<Viewable> viewables = getViewables(message, attachments);
/*
* Convert the tree of viewable parts into text and HTML
*/
// Used to suppress the divider for the first viewable part
boolean hideDivider = true;
StringBuilder text = new StringBuilder();
StringBuilder html = new StringBuilder();
html.append(HtmlConverter.getHtmlHeader());
for (Viewable viewable : viewables) {
if (viewable instanceof Textual) {
// This is either a text/plain or text/html part. Fill the variables 'text' and
// 'html', converting between plain text and HTML as necessary.
text.append(buildText(viewable, !hideDivider));
html.append(buildHtml(viewable, !hideDivider));
hideDivider = false;
} else if (viewable instanceof MessageHeader) {
MessageHeader header = (MessageHeader) viewable;
Part containerPart = header.getContainerPart();
Message innerMessage = header.getMessage();
addTextDivider(text, containerPart, !hideDivider);
addMessageHeaderText(context, text, innerMessage);
addHtmlDivider(html, containerPart, !hideDivider);
addMessageHeaderHtml(context, html, innerMessage);
hideDivider = true;
} else if (viewable instanceof Alternative) {
// Handle multipart/alternative contents
Alternative alternative = (Alternative) viewable;
/*
* We made sure at least one of text/plain or text/html is present when
* creating the Alternative object. If one part is not present we convert the
* other one to make sure 'text' and 'html' always contain the same text.
*/
List<Viewable> textAlternative = alternative.getText().isEmpty() ?
alternative.getHtml() : alternative.getText();
List<Viewable> htmlAlternative = alternative.getHtml().isEmpty() ?
alternative.getText() : alternative.getHtml();
// Fill the 'text' variable
boolean divider = !hideDivider;
for (Viewable textViewable : textAlternative) {
text.append(buildText(textViewable, divider));
divider = true;
}
// Fill the 'html' variable
divider = !hideDivider;
for (Viewable htmlViewable : htmlAlternative) {
html.append(buildHtml(htmlViewable, divider));
divider = true;
}
hideDivider = false;
}
}
html.append(HtmlConverter.getHtmlFooter());
return new ViewableContainer(text.toString(), html.toString(), attachments);
} catch (Exception e) {
throw new MessagingException("Couldn't extract viewable parts", e);
}
/*
* If the part is an embedded message we just continue to process
* it, pulling any viewables or attachments into the running list.
*/
else if (part.getBody() instanceof Message) {
Message message = (Message)part.getBody();
collectParts(message, viewables, attachments);
}
/*
* If the part is HTML and it got this far it's part of a mixed (et
* al) and should be rendered inline.
*/
else if (isPartTextualBody(part)) {
viewables.add(part);
}
/**
* Traverse the MIME tree of a message an extract viewable parts.
*
* @param part
* The message part to start from.
* @param attachments
* A list that will receive the parts that are considered attachments.
*
* @return A list of {@link Viewable}s.
*
* @throws MessagingException
* In case of an error.
*/
public static List<Viewable> getViewables(Part part, List<Part> attachments) throws MessagingException {
List<Viewable> viewables = new ArrayList<Viewable>();
Body body = part.getBody();
if (body instanceof Multipart) {
Multipart multipart = (Multipart) body;
if (part.getMimeType().equalsIgnoreCase("multipart/alternative")) {
/*
* For multipart/alternative parts we try to find a text/plain and a text/html
* child. Everything else we find is put into 'attachments'.
*/
List<Viewable> text = findTextPart(multipart, true);
Set<Part> knownTextParts = getParts(text);
List<Viewable> html = findHtmlPart(multipart, knownTextParts, attachments, true);
if (!text.isEmpty() || !html.isEmpty()) {
Alternative alternative = new Alternative(text, html);
viewables.add(alternative);
}
} else {
// For all other multipart parts we recurse to grab all viewable children.
int childCount = multipart.getCount();
for (int i = 0; i < childCount; i++) {
Part bodyPart = multipart.getBodyPart(i);
viewables.addAll(getViewables(bodyPart, attachments));
}
}
} else if (body instanceof Message &&
!("attachment".equalsIgnoreCase(getContentDisposition(part)))) {
/*
* We only care about message/rfc822 parts whose Content-Disposition header has a value
* other than "attachment".
*/
Message message = (Message) body;
// We add the Message object so we can extract the filename later.
viewables.add(new MessageHeader(part, message));
// Recurse to grab all viewable parts and attachments from that message.
viewables.addAll(getViewables(message, attachments));
} else if (isPartTextualBody(part)) {
/*
* Save text/plain and text/html
*/
String mimeType = part.getMimeType();
if (mimeType.equalsIgnoreCase("text/plain")) {
Text text = new Text(part);
viewables.add(text);
} else {
Html html = new Html(part);
viewables.add(html);
}
} else {
// Everything else is treated as attachment.
attachments.add(part);
}
return viewables;
}
/**
* Search the children of a {@link Multipart} for {@code text/plain} parts.
*
* @param multipart
* The {@code Multipart} to search through.
* @param directChild
* If {@code true}, this method will return after the first {@code text/plain} was
* found.
*
* @return A list of {@link Text} viewables.
*
* @throws MessagingException
* In case of an error.
*/
private static List<Viewable> findTextPart(Multipart multipart, boolean directChild)
throws MessagingException {
List<Viewable> viewables = new ArrayList<Viewable>();
int childCount = multipart.getCount();
for (int i = 0; i < childCount; i++) {
Part part = multipart.getBodyPart(i);
Body body = part.getBody();
if (body instanceof Multipart) {
Multipart innerMultipart = (Multipart) body;
/*
* Recurse to find text parts. Since this is a multipart that is a child of a
* multipart/alternative we don't want to stop after the first text/plain part
* we find. This will allow to get all text parts for constructions like this:
*
* 1. multipart/alternative
* 1.1. multipart/mixed
* 1.1.1. text/plain
* 1.1.2. text/plain
* 1.2. text/html
*/
List<Viewable> textViewables = findTextPart(innerMultipart, false);
if (!textViewables.isEmpty()) {
viewables.addAll(textViewables);
if (directChild) {
break;
}
}
} else if (isPartTextualBody(part) && part.getMimeType().equalsIgnoreCase("text/plain")) {
Text text = new Text(part);
viewables.add(text);
if (directChild) {
break;
}
}
}
return viewables;
}
/**
* Search the children of a {@link Multipart} for {@code text/html} parts.
*
* <p>
* Every part that is not a {@code text/html} we want to display, we add to 'attachments'.
* </p>
*
* @param multipart
* The {@code Multipart} to search through.
* @param knownTextParts
* A set of {@code text/plain} parts that shouldn't be added to 'attachments'.
* @param attachments
* A list that will receive the parts that are considered attachments.
* @param directChild
* If {@code true}, this method will add all {@code text/html} parts except the first
* found to 'attachments'.
*
* @return A list of {@link Text} viewables.
*
* @throws MessagingException
* In case of an error.
*/
private static List<Viewable> findHtmlPart(Multipart multipart, Set<Part> knownTextParts,
List<Part> attachments, boolean directChild) throws MessagingException {
List<Viewable> viewables = new ArrayList<Viewable>();
boolean partFound = false;
int childCount = multipart.getCount();
for (int i = 0; i < childCount; i++) {
Part part = multipart.getBodyPart(i);
Body body = part.getBody();
if (body instanceof Multipart) {
Multipart innerMultipart = (Multipart) body;
if (directChild && partFound) {
// We already found our text/html part. Now we're only looking for attachments.
findAttachments(innerMultipart, knownTextParts, attachments);
} else {
/*
* Recurse to find HTML parts. Since this is a multipart that is a child of a
* multipart/alternative we don't want to stop after the first text/html part
* we find. This will allow to get all text parts for constructions like this:
*
* 1. multipart/alternative
* 1.1. text/plain
* 1.2. multipart/mixed
* 1.2.1. text/html
* 1.2.2. text/html
* 1.3. image/jpeg
*/
List<Viewable> htmlViewables = findHtmlPart(innerMultipart, knownTextParts,
attachments, false);
if (!htmlViewables.isEmpty()) {
partFound = true;
viewables.addAll(htmlViewables);
}
}
} else if (!(directChild && partFound) && isPartTextualBody(part) &&
part.getMimeType().equalsIgnoreCase("text/html")) {
Html html = new Html(part);
viewables.add(html);
partFound = true;
} else if (!knownTextParts.contains(part)) {
// Only add this part as attachment if it's not a viewable text/plain part found
// earlier.
attachments.add(part);
}
}
return viewables;
}
/**
* Build a set of message parts for fast lookups.
*
* @param viewables
* A list of {@link Viewable}s containing references to the message parts to include in
* the set.
*
* @return The set of viewable {@code Part}s.
*
* @see MimeUtility#findHtmlPart(Multipart, Set, List, boolean)
* @see MimeUtility#findAttachments(Multipart, Set, List)
*/
private static Set<Part> getParts(List<Viewable> viewables) {
Set<Part> parts = new HashSet<Part>();
for (Viewable viewable : viewables) {
if (viewable instanceof Textual) {
parts.add(((Textual) viewable).getPart());
} else if (viewable instanceof Alternative) {
Alternative alternative = (Alternative) viewable;
parts.addAll(getParts(alternative.getText()));
parts.addAll(getParts(alternative.getHtml()));
}
}
return parts;
}
/**
* Traverse the MIME tree and add everything that's not a known text part to 'attachments'.
*
* @param multipart
* The {@link Multipart} to start from.
* @param knownTextParts
* A set of known text parts we don't want to end up in 'attachments'.
* @param attachments
* A list that will receive the parts that are considered attachments.
*/
private static void findAttachments(Multipart multipart, Set<Part> knownTextParts,
List<Part> attachments) {
int childCount = multipart.getCount();
for (int i = 0; i < childCount; i++) {
Part part = multipart.getBodyPart(i);
Body body = part.getBody();
if (body instanceof Multipart) {
Multipart innerMultipart = (Multipart) body;
findAttachments(innerMultipart, knownTextParts, attachments);
} else if (!knownTextParts.contains(part)) {
attachments.add(part);
}
}
}
/**
* Extract important header values from a message to display inline (plain text version).
*
* @param context
* A {@link Context} instance that will be used to get localized strings.
* @param text
* The {@link StringBuilder} that will receive the (plain text) output.
* @param message
* The message to extract the header values from.
*
* @throws MessagingException
* In case of an error.
*/
private static void addMessageHeaderText(Context context, StringBuilder text, Message message)
throws MessagingException {
// From: <sender>
Address[] from = message.getFrom();
if (from != null && from.length > 0) {
text.append(context.getString(R.string.message_compose_quote_header_from));
text.append(' ');
text.append(Address.toString(from));
text.append("\n");
}
// To: <recipients>
Address[] to = message.getRecipients(RecipientType.TO);
if (to != null && to.length > 0) {
text.append(context.getString(R.string.message_compose_quote_header_to));
text.append(' ');
text.append(Address.toString(to));
text.append("\n");
}
// Cc: <recipients>
Address[] cc = message.getRecipients(RecipientType.CC);
if (cc != null && cc.length > 0) {
text.append(context.getString(R.string.message_compose_quote_header_cc));
text.append(' ');
text.append(Address.toString(cc));
text.append("\n");
}
// Date: <date>
Date date = message.getSentDate();
if (date != null) {
text.append(context.getString(R.string.message_compose_quote_header_send_date));
text.append(' ');
text.append(date.toString());
text.append("\n");
}
// Subject: <subject>
String subject = message.getSubject();
text.append(context.getString(R.string.message_compose_quote_header_subject));
text.append(' ');
if (subject == null) {
text.append(context.getString(R.string.general_no_subject));
} else {
text.append(subject);
}
text.append("\n\n");
}
/**
* Extract important header values from a message to display inline (HTML version).
*
* @param context
* A {@link Context} instance that will be used to get localized strings.
* @param html
* The {@link StringBuilder} that will receive the (HTML) output.
* @param message
* The message to extract the header values from.
*
* @throws MessagingException
* In case of an error.
*/
private static void addMessageHeaderHtml(Context context, StringBuilder html, Message message)
throws MessagingException {
html.append("<table style=\"border: 0\">");
// From: <sender>
Address[] from = message.getFrom();
if (from != null && from.length > 0) {
addTableRow(html, context.getString(R.string.message_compose_quote_header_from),
Address.toString(from));
}
// To: <recipients>
Address[] to = message.getRecipients(RecipientType.TO);
if (to != null && to.length > 0) {
addTableRow(html, context.getString(R.string.message_compose_quote_header_to),
Address.toString(to));
}
// Cc: <recipients>
Address[] cc = message.getRecipients(RecipientType.CC);
if (cc != null && cc.length > 0) {
addTableRow(html, context.getString(R.string.message_compose_quote_header_cc),
Address.toString(cc));
}
// Date: <date>
Date date = message.getSentDate();
if (date != null) {
addTableRow(html, context.getString(R.string.message_compose_quote_header_send_date),
date.toString());
}
// Subject: <subject>
String subject = message.getSubject();
addTableRow(html, context.getString(R.string.message_compose_quote_header_subject),
(subject == null) ? context.getString(R.string.general_no_subject) : subject);
html.append("</table>");
}
/**
* Output an HTML table two column row with some hardcoded style.
*
* @param html
* The {@link StringBuilder} that will receive the output.
* @param header
* The string to be put in the {@code TH} element.
* @param value
* The string to be put in the {@code TD} element.
*/
private static void addTableRow(StringBuilder html, String header, String value) {
html.append("<tr><th style=\"text-align: left; vertical-align: top;\">");
html.append(header);
html.append("</th>");
html.append("<td>");
html.append(value);
html.append("</td></tr>");
}
/**
* Use the contents of a {@link Viewable} to create the plain text to be displayed.
*
* <p>
* This will use {@link HtmlConverter#htmlToText(String)} to convert HTML parts to plain text
* if necessary.
* </p>
*
* @param viewable
* The viewable part to build the text from.
* @param prependDivider
* {@code true}, if the text divider should be inserted as first element.
* {@code false}, otherwise.
*
* @return The contents of the supplied viewable instance as plain text.
*/
private static StringBuilder buildText(Viewable viewable, boolean prependDivider)
{
StringBuilder text = new StringBuilder();
if (viewable instanceof Textual) {
Part part = ((Textual)viewable).getPart();
addTextDivider(text, part, prependDivider);
String t = getTextFromPart(part);
if (t == null) {
t = "";
} else if (viewable instanceof Html) {
t = HtmlConverter.htmlToText(t);
}
text.append(t);
} else if (viewable instanceof Alternative) {
// That's odd - an Alternative as child of an Alternative; go ahead and try to use the
// text/plain child; fall-back to the text/html part.
Alternative alternative = (Alternative) viewable;
List<Viewable> textAlternative = alternative.getText().isEmpty() ?
alternative.getHtml() : alternative.getText();
boolean divider = prependDivider;
for (Viewable textViewable : textAlternative) {
text.append(buildText(textViewable, divider));
divider = true;
}
}
return text;
}
/*
* Some constants that are used by addTextDivider() below.
*/
private static final int TEXT_DIVIDER_LENGTH = TEXT_DIVIDER.length();
private static final String FILENAME_PREFIX = "----- ";
private static final int FILENAME_PREFIX_LENGTH = FILENAME_PREFIX.length();
private static final String FILENAME_SUFFIX = " ";
private static final int FILENAME_SUFFIX_LENGTH = FILENAME_SUFFIX.length();
/**
* Add a plain text divider between two plain text message parts.
*
* @param text
* The {@link StringBuilder} to append the divider to.
* @param part
* The message part that will follow after the divider. This is used to extract the
* part's name.
* @param prependDivider
* {@code true}, if the divider should be appended. {@code false}, otherwise.
*/
private static void addTextDivider(StringBuilder text, Part part, boolean prependDivider) {
if (prependDivider) {
String filename = getPartName(part);
text.append("\n\n");
int len = filename.length();
if (len > 0) {
if (len > TEXT_DIVIDER_LENGTH - FILENAME_PREFIX_LENGTH - FILENAME_SUFFIX_LENGTH) {
filename = filename.substring(0, TEXT_DIVIDER_LENGTH - FILENAME_PREFIX_LENGTH -
FILENAME_SUFFIX_LENGTH - 3) + "...";
}
text.append(FILENAME_PREFIX);
text.append(filename);
text.append(FILENAME_SUFFIX);
text.append(TEXT_DIVIDER.substring(0, TEXT_DIVIDER_LENGTH -
FILENAME_PREFIX_LENGTH - filename.length() - FILENAME_SUFFIX_LENGTH));
} else {
text.append(TEXT_DIVIDER);
}
text.append("\n\n");
}
}
/**
* Use the contents of a {@link Viewable} to create the HTML to be displayed.
*
* <p>
* This will use {@link HtmlConverter#textToHtml(String, boolean)} to convert plain text parts
* to HTML if necessary.
* </p>
*
* @param viewable
* The viewable part to build the HTML from.
* @param prependDivider
* {@code true}, if the HTML divider should be inserted as first element.
* {@code false}, otherwise.
*
* @return The contents of the supplied viewable instance as HTML.
*/
private static StringBuilder buildHtml(Viewable viewable, boolean prependDivider)
{
StringBuilder html = new StringBuilder();
if (viewable instanceof Textual) {
Part part = ((Textual)viewable).getPart();
addHtmlDivider(html, part, prependDivider);
String t = getTextFromPart(part);
if (t == null) {
t = "";
} else if (viewable instanceof Text) {
t = HtmlConverter.textToHtml(t, false);
}
html.append(t);
} else if (viewable instanceof Alternative) {
// That's odd - an Alternative as child of an Alternative; go ahead and try to use the
// text/html child; fall-back to the text/plain part.
Alternative alternative = (Alternative) viewable;
List<Viewable> htmlAlternative = alternative.getHtml().isEmpty() ?
alternative.getText() : alternative.getHtml();
boolean divider = prependDivider;
for (Viewable htmlViewable : htmlAlternative) {
html.append(buildHtml(htmlViewable, divider));
divider = true;
}
}
return html;
}
/**
* Add an HTML divider between two HTML message parts.
*
* @param html
* The {@link StringBuilder} to append the divider to.
* @param part
* The message part that will follow after the divider. This is used to extract the
* part's name.
* @param prependDivider
* {@code true}, if the divider should be appended. {@code false}, otherwise.
*/
private static void addHtmlDivider(StringBuilder html, Part part, boolean prependDivider) {
if (prependDivider) {
String filename = getPartName(part);
html.append("<p style=\"margin-top: 2.5em; margin-bottom: 1em; border-bottom: 1px solid #000\">");
html.append(filename);
html.append("</p>");
}
}
/**
* Get the name of the message part.
*
* @param part
* The part to get the name for.
*
* @return The (file)name of the part if available. An empty string, otherwise.
*/
private static String getPartName(Part part) {
try {
String disposition = part.getDisposition();
if (disposition != null) {
String name = MimeUtility.getHeaderParameter(disposition, "filename");
return (name == null) ? "" : name;
}
}
catch (MessagingException e) { /* ignore */ }
return "";
}
/**
* Get the value of the {@code Content-Disposition} header.
*
* @param part
* The message part to read the header from.
*
* @return The value of the {@code Content-Disposition} header if available. {@code null},
* otherwise.
*/
private static String getContentDisposition(Part part) {
try {
String disposition = part.getDisposition();
if (disposition != null) {
return MimeUtility.getHeaderParameter(disposition, null);
}
}
catch (MessagingException e) { /* ignore */ }
return null;
}
public static Boolean isPartTextualBody(Part part) throws MessagingException {
String disposition = part.getDisposition();
@ -1210,6 +2040,17 @@ public class MimeUtility {
return DEFAULT_ATTACHMENT_MIME_TYPE;
}
public static String getExtensionByMimeType(String mimeType) {
String lowerCaseMimeType = mimeType.toLowerCase(Locale.US);
for (String[] contentTypeMapEntry : MIME_TYPE_BY_EXTENSION_MAP) {
if (contentTypeMapEntry[1].equals(lowerCaseMimeType)) {
return contentTypeMapEntry[0];
}
}
return null;
}
/**
* Convert some wrong MIME types encountered in the wild to canonical MIME types.
*

View File

@ -17,6 +17,7 @@ import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
@ -1088,13 +1089,15 @@ public class EasStore 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.getRemoteName(), 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.getRemoteName(), true);
return null;
}
@Override
@ -1464,8 +1467,9 @@ public class EasStore extends Store {
}
@Override
public void appendMessages(Message[] messages) throws MessagingException {
public Map<String, String> appendMessages(Message[] messages) throws MessagingException {
// EASTODO
return null;
}
@Override

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;
@ -1064,53 +1066,127 @@ 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()));
if (!exists(remoteDestName)) {
/*
* If the remote trash folder doesn't exist we try to create it.
*/
if (K9.DEBUG)
Log.i(K9.LOG_TAG, "IMAPMessage.copyMessages: attempting to create remote '" + remoteDestName + "' folder for " + getLogId());
// If the remote trash folder doesn't exist we try to create it.
if (K9.DEBUG) {
Log.i(K9.LOG_TAG, "Attempting to create remote folder '" + remoteDestName +
"' for " + getLogId());
}
iFolder.create(FolderType.HOLDS_MESSAGES);
}
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);
//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;
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
@ -1854,20 +1930,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();
@ -1881,16 +1967,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);
}
@ -1916,7 +2040,7 @@ public class ImapStore extends Store {
List<ImapResponse> responses =
executeSimpleCommand(
String.format("UID SEARCH HEADER MESSAGE-ID %s", messageId));
String.format("UID SEARCH HEADER MESSAGE-ID %s", encodeString(messageId)));
for (ImapResponse response1 : responses) {
if (response1.mTag == null && ImapResponseParser.equalsIgnoreCase(response1.get(0), "SEARCH")
&& response1.size() > 1) {

View File

@ -55,6 +55,7 @@ import com.fsck.k9.mail.internet.MimeHeader;
import com.fsck.k9.mail.internet.MimeMessage;
import com.fsck.k9.mail.internet.MimeMultipart;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.internet.MimeUtility.ViewableContainer;
import com.fsck.k9.mail.internet.TextBody;
import com.fsck.k9.mail.store.LockableDatabase.DbCallback;
import com.fsck.k9.mail.store.LockableDatabase.WrappedException;
@ -1720,6 +1721,12 @@ public class LocalStore extends Store implements Serializable {
contentDisposition,
name, // TODO: Should use encoded word defined in RFC 2231.
size));
} else {
bp.setHeader(MimeHeader.HEADER_CONTENT_TYPE, type);
bp.setHeader(MimeHeader.HEADER_CONTENT_DISPOSITION,
String.format("%s;\n size=%d",
contentDisposition,
size));
}
bp.setHeader(MimeHeader.HEADER_CONTENT_ID, contentId);
@ -1950,21 +1957,23 @@ public class LocalStore extends Store implements Serializable {
}
@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
@ -1990,7 +1999,10 @@ public class LocalStore extends Store implements Serializable {
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(),
@ -1998,6 +2010,11 @@ public class LocalStore extends Store implements Serializable {
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);
@ -2009,6 +2026,7 @@ public class LocalStore extends Store implements Serializable {
return null;
}
});
return uidMap;
} catch (WrappedException e) {
throw(MessagingException) e.getCause();
}
@ -2054,8 +2072,8 @@ public class LocalStore extends Store implements Serializable {
* 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 destroyMessages(final Message[] messages) throws MessagingException {
@ -2091,10 +2109,12 @@ public class LocalStore extends Store implements Serializable {
* 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 {
@ -2107,11 +2127,26 @@ public class LocalStore extends Store implements Serializable {
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) {
@ -2128,45 +2163,14 @@ public class LocalStore extends Store implements Serializable {
deleteAttachments(message.getUid());
}
ArrayList<Part> viewables = new ArrayList<Part>();
ArrayList<Part> attachments = new ArrayList<Part>();
MimeUtility.collectParts(message, viewables, attachments);
ViewableContainer container =
MimeUtility.extractTextAndAttachments(mApplication, message);
StringBuilder sbHtml = new StringBuilder();
StringBuilder sbText = new StringBuilder();
for (Part viewable : viewables) {
try {
String text = MimeUtility.getTextFromPart(viewable);
List<Part> attachments = container.attachments;
String text = container.text;
String html = HtmlConverter.convertEmoji2Img(container.html);
/*
* Small hack to make sure the string "null" doesn't end up
* in one of the StringBuilders.
*/
if (text == null) {
text = "";
}
/*
* Anything with MIME type text/html will be stored as such. Anything
* else will be stored as text/plain.
*/
if (viewable.getMimeType().equalsIgnoreCase("text/html")) {
sbHtml.append(text);
} else {
sbText.append(text);
}
} catch (Exception e) {
throw new MessagingException("Unable to get text for message part", e);
}
}
String text = sbText.toString();
String html = markupContent(text, sbHtml.toString());
String preview = calculateContentPreview(text);
// If we couldn't generate a reasonable preview from the text part, try doing it with the HTML part.
if (preview == null || preview.length() == 0) {
preview = calculateContentPreview(HtmlConverter.htmlToText(html));
}
try {
ContentValues cv = new ContentValues();
@ -2222,6 +2226,7 @@ public class LocalStore extends Store implements Serializable {
return null;
}
});
return uidMap;
} catch (WrappedException e) {
throw(MessagingException) e.getCause();
}
@ -2244,49 +2249,17 @@ public class LocalStore extends Store implements Serializable {
@Override
public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
try {
ArrayList<Part> viewables = new ArrayList<Part>();
ArrayList<Part> attachments = new ArrayList<Part>();
message.buildMimeRepresentation();
MimeUtility.collectParts(message, viewables, attachments);
ViewableContainer container =
MimeUtility.extractTextAndAttachments(mApplication, message);
StringBuilder sbHtml = new StringBuilder();
StringBuilder sbText = new StringBuilder();
for (int i = 0, count = viewables.size(); i < count; i++) {
Part viewable = viewables.get(i);
try {
String text = MimeUtility.getTextFromPart(viewable);
List<Part> attachments = container.attachments;
String text = container.text;
String html = HtmlConverter.convertEmoji2Img(container.html);
/*
* Small hack to make sure the string "null" doesn't end up
* in one of the StringBuilders.
*/
if (text == null) {
text = "";
}
/*
* Anything with MIME type text/html will be stored as such. Anything
* else will be stored as text/plain.
*/
if (viewable.getMimeType().equalsIgnoreCase("text/html")) {
sbHtml.append(text);
} else {
sbText.append(text);
}
} catch (Exception e) {
throw new MessagingException("Unable to get text for message part", e);
}
}
String text = sbText.toString();
String html = markupContent(text, sbHtml.toString());
String preview = calculateContentPreview(text);
// If we couldn't generate a reasonable preview from the text part, try doing it with the HTML part.
if (preview == null || preview.length() == 0) {
preview = calculateContentPreview(HtmlConverter.htmlToText(html));
}
try {
db.execSQL("UPDATE messages SET "
+ "uid = ?, subject = ?, sender_list = ?, date = ?, flags = ?, "
@ -2420,6 +2393,18 @@ public class LocalStore extends Store implements Serializable {
Body body = attachment.getBody();
if (body instanceof LocalAttachmentBody) {
contentUri = ((LocalAttachmentBody) body).getContentUri();
} else if (body instanceof Message) {
// It's a message, so use Message.writeTo() to output the
// message including all children.
Message message = (Message) body;
tempAttachmentFile = File.createTempFile("att", null, attachmentDirectory);
FileOutputStream out = new FileOutputStream(tempAttachmentFile);
try {
message.writeTo(out);
} finally {
out.close();
}
size = (int) (tempAttachmentFile.length() & 0x7FFFFFFFL);
} else {
/*
* If the attachment has a body we're expected to save it into the local store
@ -2647,7 +2632,7 @@ public class LocalStore extends Store implements Serializable {
setVisibleLimit(mAccount.getDisplayCount());
}
private void resetUnreadAndFlaggedCounts() {
public void resetUnreadAndFlaggedCounts() {
try {
int newUnread = 0;
int newFlagged = 0;
@ -2718,22 +2703,34 @@ public class LocalStore extends Store implements Serializable {
public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
Cursor attachmentsCursor = null;
try {
attachmentsCursor = db.query("attachments", new String[]
{ "id" }, "message_id = ?", new String[]
{ Long.toString(messageId) }, null, null, null);
String accountUuid = mAccount.getUuid();
Context context = mApplication;
// Get attachment IDs
String[] whereArgs = new String[] { Long.toString(messageId) };
attachmentsCursor = db.query("attachments", new String[] { "id" },
"message_id = ?", whereArgs, null, null, null);
final File attachmentDirectory = StorageManager.getInstance(mApplication)
.getAttachmentDirectory(uUid, database.getStorageProviderId());
.getAttachmentDirectory(uUid, database.getStorageProviderId());
while (attachmentsCursor.moveToNext()) {
long attachmentId = attachmentsCursor.getLong(0);
String attachmentId = Long.toString(attachmentsCursor.getLong(0));
try {
File file = new File(attachmentDirectory, Long.toString(attachmentId));
// Delete stored attachment
File file = new File(attachmentDirectory, attachmentId);
if (file.exists()) {
file.delete();
}
} catch (Exception e) {
}
// Delete thumbnail file
AttachmentProvider.deleteThumbnail(context, accountUuid,
attachmentId);
} catch (Exception e) { /* ignore */ }
}
// Delete attachment metadata from the database
db.delete("attachments", "message_id = ?", whereArgs);
} finally {
Utility.closeQuietly(attachmentsCursor);
}
@ -2817,17 +2814,7 @@ public class LocalStore extends Store implements Serializable {
}
}
public String markupContent(String text, String html) {
if (text.length() > 0 && html.length() == 0) {
html = HtmlConverter.textToHtml(text);
}
html = HtmlConverter.convertEmoji2Img(html);
return html;
}
@Override
public boolean isInTopGroup() {
return mInTopGroup;

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";
@ -893,7 +894,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

@ -1333,13 +1333,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.getRemoteName(), 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.getRemoteName(), true);
return null;
}
@Override
@ -1919,8 +1921,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

@ -12,7 +12,6 @@ import com.fsck.k9.Account;
import com.fsck.k9.K9;
import com.fsck.k9.R;
import com.fsck.k9.Account.FolderMode;
import com.fsck.k9.Account.ScrollButtons;
import com.fsck.k9.crypto.Apg;
import com.fsck.k9.mail.store.StorageManager;
import com.fsck.k9.preferences.Settings.*;
@ -82,12 +81,6 @@ public class AccountSettings {
s.put("goToUnreadMessageSearch", Settings.versions(
new V(1, new BooleanSetting(false))
));
s.put("hideButtonsEnum", Settings.versions(
new V(1, new EnumSetting(ScrollButtons.class, ScrollButtons.NEVER))
));
s.put("hideMoveButtonsEnum", Settings.versions(
new V(1, new EnumSetting(ScrollButtons.class, ScrollButtons.NEVER))
));
s.put("idleRefreshMinutes", Settings.versions(
new V(1, new IntegerResourceSetting(24, R.array.idle_refresh_period_values))
));

View File

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

View File

@ -23,8 +23,12 @@ import java.io.*;
import java.util.List;
/**
* A simple ContentProvider that allows file access to Email's attachments.<br/>
* Warning! We make heavy assumptions about the Uris used by the {@link LocalStore} for an {@link Account} here.
* A simple ContentProvider that allows file access to attachments.
*
* <p>
* Warning! We make heavy assumptions about the Uris used by the {@link LocalStore} for an
* {@link Account} here.
* </p>
*/
public class AttachmentProvider extends ContentProvider {
public static final Uri CONTENT_URI = Uri.parse("content://com.fsck.k9.attachmentprovider");
@ -33,6 +37,11 @@ public class AttachmentProvider extends ContentProvider {
private static final String FORMAT_VIEW = "VIEW";
private static final String FORMAT_THUMBNAIL = "THUMBNAIL";
private static final String[] DEFAULT_PROJECTION = new String[] {
AttachmentProviderColumns._ID,
AttachmentProviderColumns.DATA,
};
public static class AttachmentProviderColumns {
public static final String _ID = "_id";
public static final String DATA = "_data";
@ -40,6 +49,7 @@ public class AttachmentProvider extends ContentProvider {
public static final String SIZE = "_size";
}
public static Uri getAttachmentUri(Account account, long id) {
return getAttachmentUri(account.getUuid(), id, true);
}
@ -66,6 +76,47 @@ public class AttachmentProvider extends ContentProvider {
.build();
}
public static void clear(Context context) {
/*
* We use the cache dir as a temporary directory (since Android doesn't give us one) so
* on startup we'll clean up any .tmp files from the last run.
*/
File[] files = context.getCacheDir().listFiles();
for (File file : files) {
try {
if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "Deleting file " + file.getCanonicalPath());
}
} catch (IOException ioe) { /* No need to log failure to log */ }
file.delete();
}
}
/**
* Delete the thumbnail of an attachment.
*
* @param context
* The application context.
* @param accountUuid
* The UUID of the account the attachment belongs to.
* @param attachmentId
* The ID of the attachment the thumbnail was created for.
*/
public static void deleteThumbnail(Context context, String accountUuid, String attachmentId) {
File file = getThumbnailFile(context, accountUuid, attachmentId);
if (file.exists()) {
file.delete();
}
}
private static File getThumbnailFile(Context context, String accountUuid,
String attachmentId) {
String filename = "thmb_" + accountUuid + "_" + attachmentId + ".tmp";
File dir = context.getCacheDir();
return new File(dir, filename);
}
@Override
public boolean onCreate() {
/*
@ -89,21 +140,6 @@ public class AttachmentProvider extends ContentProvider {
return true;
}
public static void clear(Context lContext) {
/*
* We use the cache dir as a temporary directory (since Android doesn't give us one) so
* on startup we'll clean up any .tmp files from the last run.
*/
File[] files = lContext.getCacheDir().listFiles();
for (File file : files) {
try {
if (K9.DEBUG)
Log.d(K9.LOG_TAG, "Deleting file " + file.getCanonicalPath());
} catch (IOException ioe) {} // No need to log failure to log
file.delete();
}
}
@Override
public String getType(Uri uri) {
List<String> segments = uri.getPathSegments();
@ -114,67 +150,24 @@ public class AttachmentProvider extends ContentProvider {
return getType(dbName, id, format);
}
private String getType(String dbName, String id, String format) {
if (FORMAT_THUMBNAIL.equals(format)) {
return "image/png";
} else {
final Account account = Preferences.getPreferences(getContext()).getAccount(dbName);
try {
final LocalStore localStore = LocalStore.getLocalInstance(account, K9.app);
AttachmentInfo attachmentInfo = localStore.getAttachmentInfo(id);
if (FORMAT_VIEW.equals(format)) {
return MimeUtility.getMimeTypeForViewing(attachmentInfo.type, attachmentInfo.name);
} else {
// When accessing the "raw" message we deliver the original MIME type.
return attachmentInfo.type;
}
} catch (MessagingException e) {
Log.e(K9.LOG_TAG, "Unable to retrieve LocalStore for " + account, e);
return null;
}
}
}
private File getFile(String dbName, String id)
throws FileNotFoundException {
try {
final Account account = Preferences.getPreferences(getContext()).getAccount(dbName);
final File attachmentsDir;
attachmentsDir = StorageManager.getInstance(K9.app).getAttachmentDirectory(dbName,
account.getLocalStorageProviderId());
final File file = new File(attachmentsDir, id);
if (!file.exists()) {
throw new FileNotFoundException(file.getAbsolutePath());
}
return file;
} catch (IOException e) {
Log.w(K9.LOG_TAG, null, e);
throw new FileNotFoundException(e.getMessage());
}
}
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
File file;
List<String> segments = uri.getPathSegments();
String dbName = segments.get(0); // "/sdcard/..." is URL-encoded and makes up only 1 segment
String id = segments.get(1);
String accountUuid = segments.get(0);
String attachmentId = segments.get(1);
String format = segments.get(2);
if (FORMAT_THUMBNAIL.equals(format)) {
int width = Integer.parseInt(segments.get(3));
int height = Integer.parseInt(segments.get(4));
String filename = "thmb_" + dbName + "_" + id + ".tmp";
int index = dbName.lastIndexOf('/');
if (index >= 0) {
filename = /*dbName.substring(0, index + 1) + */"thmb_" + dbName.substring(index + 1) + "_" + id + ".tmp";
}
File dir = getContext().getCacheDir();
File file = new File(dir, filename);
file = getThumbnailFile(getContext(), accountUuid, attachmentId);
if (!file.exists()) {
String type = getType(dbName, id, FORMAT_VIEW);
String type = getType(accountUuid, attachmentId, FORMAT_VIEW);
try {
FileInputStream in = new FileInputStream(getFile(dbName, id));
FileInputStream in = new FileInputStream(getFile(accountUuid, attachmentId));
try {
Bitmap thumbnail = createThumbnail(type, in);
if (thumbnail != null) {
@ -187,40 +180,24 @@ public class AttachmentProvider extends ContentProvider {
}
}
} finally {
try { in.close(); } catch (Throwable ignore) {}
try { in.close(); } catch (Throwable ignore) { /* ignore */ }
}
} catch (IOException ioe) {
return null;
}
}
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
} else {
return ParcelFileDescriptor.open(
getFile(dbName, id),
ParcelFileDescriptor.MODE_READ_ONLY);
file = getFile(accountUuid, attachmentId);
}
}
@Override
public int delete(Uri uri, String arg1, String[] arg2) {
return 0;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
if (projection == null) {
projection =
new String[] {
AttachmentProviderColumns._ID,
AttachmentProviderColumns.DATA,
};
}
String[] columnNames = (projection == null) ? DEFAULT_PROJECTION : projection;
List<String> segments = uri.getPathSegments();
String dbName = segments.get(0);
@ -232,7 +209,6 @@ public class AttachmentProvider extends ContentProvider {
dbName = dbName.substring(0, dbName.length() - 3);
}
//String format = segments.get(2);
final AttachmentInfo attachmentInfo;
try {
final Account account = Preferences.getPreferences(getContext()).getAccount(dbName);
@ -242,10 +218,10 @@ public class AttachmentProvider extends ContentProvider {
return null;
}
MatrixCursor ret = new MatrixCursor(projection);
Object[] values = new Object[projection.length];
for (int i = 0, count = projection.length; i < count; i++) {
String column = projection[i];
MatrixCursor ret = new MatrixCursor(columnNames);
Object[] values = new Object[columnNames.length];
for (int i = 0, count = columnNames.length; i < count; i++) {
String column = columnNames[i];
if (AttachmentProviderColumns._ID.equals(column)) {
values[i] = id;
} else if (AttachmentProviderColumns.DATA.equals(column)) {
@ -265,6 +241,56 @@ public class AttachmentProvider extends ContentProvider {
return 0;
}
@Override
public int delete(Uri uri, String arg1, String[] arg2) {
return 0;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
private String getType(String dbName, String id, String format) {
String type;
if (FORMAT_THUMBNAIL.equals(format)) {
type = "image/png";
} else {
final Account account = Preferences.getPreferences(getContext()).getAccount(dbName);
try {
final LocalStore localStore = LocalStore.getLocalInstance(account, K9.app);
AttachmentInfo attachmentInfo = localStore.getAttachmentInfo(id);
if (FORMAT_VIEW.equals(format)) {
type = MimeUtility.getMimeTypeForViewing(attachmentInfo.type, attachmentInfo.name);
} else {
// When accessing the "raw" message we deliver the original MIME type.
type = attachmentInfo.type;
}
} catch (MessagingException e) {
Log.e(K9.LOG_TAG, "Unable to retrieve LocalStore for " + account, e);
type = null;
}
}
return type;
}
private File getFile(String dbName, String id) throws FileNotFoundException {
Account account = Preferences.getPreferences(getContext()).getAccount(dbName);
File attachmentsDir = StorageManager.getInstance(K9.app).getAttachmentDirectory(dbName,
account.getLocalStorageProviderId());
File file = new File(attachmentsDir, id);
if (!file.exists()) {
throw new FileNotFoundException(file.getAbsolutePath());
}
return file;
}
private Bitmap createThumbnail(String type, InputStream data) {
if (MimeUtility.mimeTypeMatches(type, "image/*")) {
return createImageThumbnail(data);

View File

@ -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,

View File

@ -0,0 +1,127 @@
package com.fsck.k9.provider;
import com.fsck.k9.Account;
import com.fsck.k9.AccountStats;
import com.fsck.k9.K9;
import com.fsck.k9.Preferences;
import com.fsck.k9.R;
import com.fsck.k9.activity.UnreadWidgetConfiguration;
import com.fsck.k9.activity.FolderList;
import com.fsck.k9.activity.MessageList;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.view.View;
import android.widget.RemoteViews;
public class UnreadWidgetProvider extends AppWidgetProvider {
private static final int MAX_COUNT = 9999;
/**
* Trigger update for all of our unread widgets.
*
* @param context
* The {@code Context} object to use for the broadcast intent.
*/
public static void updateUnreadCount(Context context) {
Context appContext = context.getApplicationContext();
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(appContext);
ComponentName thisWidget = new ComponentName(appContext, UnreadWidgetProvider.class);
int[] widgetIds = appWidgetManager.getAppWidgetIds(thisWidget);
Intent intent = new Intent(context, UnreadWidgetProvider.class);
intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, widgetIds);
context.sendBroadcast(intent);
}
public static void updateWidget(Context context, AppWidgetManager appWidgetManager,
int appWidgetId, String accountUuid) {
RemoteViews remoteViews = new RemoteViews(context.getPackageName(),
R.layout.unread_widget_layout);
int unreadCount = 0;
String accountName = context.getString(R.string.app_name);
Intent clickIntent = null;
try {
Account account = Preferences.getPreferences(context).getAccount(accountUuid);
if (account != null) {
AccountStats stats = new AccountStats();
account.getLocalStore().getMessageCounts(stats);
unreadCount = stats.unreadMessageCount;
accountName = account.getDescription();
if (K9.FOLDER_NONE.equals(account.getAutoExpandFolderName())) {
clickIntent = FolderList.actionHandleAccountIntent(context, account, null);
} else {
clickIntent = MessageList.actionHandleFolderIntent(context, account,
account.getAutoExpandFolderName());
}
clickIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
}
} catch (Exception e) {
if (K9.DEBUG) {
Log.e(K9.LOG_TAG, "Error getting widget configuration", e);
}
}
if (unreadCount <= 0) {
// Hide TextView for unread count if there are no unread messages.
remoteViews.setViewVisibility(R.id.unread_count, View.GONE);
} else {
remoteViews.setViewVisibility(R.id.unread_count, View.VISIBLE);
String displayCount = (unreadCount <= MAX_COUNT) ?
String.valueOf(unreadCount) : String.valueOf(MAX_COUNT) + "+";
remoteViews.setTextViewText(R.id.unread_count, displayCount);
}
remoteViews.setTextViewText(R.id.account_name, accountName);
if (clickIntent == null) {
// If the widget configuration couldn't be loaded we open the configuration
// activity when the user clicks the widget.
clickIntent = new Intent(context, UnreadWidgetConfiguration.class);
clickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
}
clickIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent pendingIntent = PendingIntent.getActivity(context, appWidgetId,
clickIntent, 0);
remoteViews.setOnClickPendingIntent(R.id.unread_widget_layout, pendingIntent);
appWidgetManager.updateAppWidget(appWidgetId, remoteViews);
}
/**
* Called when one or more widgets need to be updated.
*/
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
for (int widgetId : appWidgetIds) {
String accountUuid = UnreadWidgetConfiguration.getAccountUuid(context, widgetId);
updateWidget(context, appWidgetManager, widgetId, accountUuid);
}
}
/**
* Called when a widget instance is deleted.
*/
@Override
public void onDeleted(Context context, int[] appWidgetIds) {
for (int appWidgetId : appWidgetIds) {
UnreadWidgetConfiguration.deleteWidgetConfiguration(context, appWidgetId);
}
}
}

View File

@ -18,6 +18,8 @@ import android.os.Environment;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageView;
@ -33,12 +35,34 @@ import com.fsck.k9.helper.MediaScannerNotifier;
import com.fsck.k9.helper.SizeFormatter;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Part;
import com.fsck.k9.mail.internet.MimeHeader;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.store.LocalStore.LocalAttachmentBodyPart;
import com.fsck.k9.provider.AttachmentProvider;
public class AttachmentView extends FrameLayout {
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;
@ -81,86 +105,125 @@ public class AttachmentView extends FrameLayout {
*/
public void showFileBrowser(AttachmentView caller);
}
public boolean populateFromPart(Part inputPart, Message message, Account account, MessagingController controller, MessagingListener listener) {
try {
part = (LocalAttachmentBodyPart) inputPart;
contentType = MimeUtility.unfoldAndDecode(part.getContentType());
String contentDisposition = MimeUtility.unfoldAndDecode(part.getDisposition());
/**
* Populates this view with information about the attachment.
*
* <p>
* This method also decides which attachments are displayed when the "show attachments" button
* is pressed, and which attachments are only displayed after the "show more attachments"
* button was pressed.<br>
* Inline attachments with content ID and unnamed attachments fall into the second category.
* </p>
*
* @param inputPart
* @param message
* @param account
* @param controller
* @param listener
*
* @return {@code true} for a regular attachment. {@code false}, otherwise.
*
* @throws MessagingException
* In case of an error
*/
public boolean populateFromPart(Part inputPart, Message message, Account account,
MessagingController controller, MessagingListener listener) throws MessagingException {
boolean firstClassAttachment = true;
part = (LocalAttachmentBodyPart) inputPart;
name = MimeUtility.getHeaderParameter(contentType, "name");
if (name == null) {
name = MimeUtility.getHeaderParameter(contentDisposition, "filename");
}
if (name == null) {
return false;
}
contentType = MimeUtility.unfoldAndDecode(part.getContentType());
String contentDisposition = MimeUtility.unfoldAndDecode(part.getDisposition());
mAccount = account;
mMessage = message;
mController = controller;
mListener = listener;
size = Integer.parseInt(MimeUtility.getHeaderParameter(contentDisposition, "size"));
contentType = MimeUtility.getMimeTypeForViewing(part.getMimeType(), name);
TextView attachmentName = (TextView) findViewById(R.id.attachment_name);
TextView attachmentInfo = (TextView) findViewById(R.id.attachment_info);
ImageView attachmentIcon = (ImageView) findViewById(R.id.attachment_icon);
viewButton = (Button) findViewById(R.id.view);
downloadButton = (Button) findViewById(R.id.download);
if ((!MimeUtility.mimeTypeMatches(contentType, K9.ACCEPTABLE_ATTACHMENT_VIEW_TYPES))
|| (MimeUtility.mimeTypeMatches(contentType, K9.UNACCEPTABLE_ATTACHMENT_VIEW_TYPES))) {
viewButton.setVisibility(View.GONE);
}
if ((!MimeUtility.mimeTypeMatches(contentType, K9.ACCEPTABLE_ATTACHMENT_DOWNLOAD_TYPES))
|| (MimeUtility.mimeTypeMatches(contentType, K9.UNACCEPTABLE_ATTACHMENT_DOWNLOAD_TYPES))) {
downloadButton.setVisibility(View.GONE);
}
if (size > K9.MAX_ATTACHMENT_DOWNLOAD_SIZE) {
viewButton.setVisibility(View.GONE);
downloadButton.setVisibility(View.GONE);
}
viewButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
onViewButtonClicked();
return;
}
});
downloadButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
onSaveButtonClicked();
return;
}
});
downloadButton.setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
callback.showFileBrowser(AttachmentView.this);
return true;
}
});
attachmentName.setText(name);
attachmentInfo.setText(SizeFormatter.formatSize(mContext, size));
Bitmap previewIcon = getPreviewIcon();
if (previewIcon != null) {
attachmentIcon.setImageBitmap(previewIcon);
} else {
attachmentIcon.setImageResource(R.drawable.attached_image_placeholder);
}
name = MimeUtility.getHeaderParameter(contentType, "name");
if (name == null) {
name = MimeUtility.getHeaderParameter(contentDisposition, "filename");
}
catch (Exception e) {
Log.e(K9.LOG_TAG, "error ", e);
if (name == null) {
firstClassAttachment = false;
String extension = MimeUtility.getExtensionByMimeType(contentType);
name = "noname" + ((extension != null) ? "." + extension : "");
}
return true;
// Inline parts with a content-id are almost certainly components of an HTML message
// not attachments. Only show them if the user pressed the button to show more
// attachments.
if (contentDisposition != null &&
MimeUtility.getHeaderParameter(contentDisposition, null).matches("^(?i:inline)")
&& part.getHeader(MimeHeader.HEADER_CONTENT_ID) != null) {
firstClassAttachment = false;
}
mAccount = account;
mMessage = message;
mController = controller;
mListener = listener;
String sizeParam = MimeUtility.getHeaderParameter(contentDisposition, "size");
if (sizeParam != null) {
try {
size = Integer.parseInt(sizeParam);
} catch (NumberFormatException e) { /* ignore */ }
}
contentType = MimeUtility.getMimeTypeForViewing(part.getMimeType(), name);
TextView attachmentName = (TextView) findViewById(R.id.attachment_name);
TextView attachmentInfo = (TextView) findViewById(R.id.attachment_info);
ImageView attachmentIcon = (ImageView) findViewById(R.id.attachment_icon);
viewButton = (Button) findViewById(R.id.view);
downloadButton = (Button) findViewById(R.id.download);
if ((!MimeUtility.mimeTypeMatches(contentType, K9.ACCEPTABLE_ATTACHMENT_VIEW_TYPES))
|| (MimeUtility.mimeTypeMatches(contentType, K9.UNACCEPTABLE_ATTACHMENT_VIEW_TYPES))) {
viewButton.setVisibility(View.GONE);
}
if ((!MimeUtility.mimeTypeMatches(contentType, K9.ACCEPTABLE_ATTACHMENT_DOWNLOAD_TYPES))
|| (MimeUtility.mimeTypeMatches(contentType, K9.UNACCEPTABLE_ATTACHMENT_DOWNLOAD_TYPES))) {
downloadButton.setVisibility(View.GONE);
}
if (size > K9.MAX_ATTACHMENT_DOWNLOAD_SIZE) {
viewButton.setVisibility(View.GONE);
downloadButton.setVisibility(View.GONE);
}
viewButton.setOnClickListener(this);
downloadButton.setOnClickListener(this);
downloadButton.setOnLongClickListener(this);
attachmentName.setText(name);
attachmentInfo.setText(SizeFormatter.formatSize(mContext, size));
Bitmap previewIcon = getPreviewIcon();
if (previewIcon != null) {
attachmentIcon.setImageBitmap(previewIcon);
} else {
attachmentIcon.setImageResource(R.drawable.attached_image_placeholder);
}
return firstClassAttachment;
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.view: {
onViewButtonClicked();
break;
}
case R.id.download: {
onSaveButtonClicked();
break;
}
}
}
@Override
public boolean onLongClick(View view) {
if (view.getId() == R.id.download) {
callback.showFileBrowser(this);
return true;
}
return false;
}
private Bitmap getPreviewIcon() {
@ -196,7 +259,8 @@ public class AttachmentView extends FrameLayout {
*/
public void writeFile(File directory) {
try {
File file = Utility.createUniqueFile(directory, name);
String filename = sanitizeFilename(name);
File file = Utility.createUniqueFile(directory, filename);
Uri uri = AttachmentProvider.getAttachmentUri(mAccount, part.getAttachmentId());
InputStream in = mContext.getContentResolver().openInputStream(uri);
OutputStream out = new FileOutputStream(file);
@ -207,9 +271,25 @@ public class AttachmentView extends FrameLayout {
attachmentSaved(file.toString());
new MediaScannerNotifier(mContext, file);
} catch (IOException ioe) {
if (K9.DEBUG) {
Log.e(K9.LOG_TAG, "Error saving attachment", ioe);
}
attachmentNotSaved();
}
}
/**
* 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

@ -2,6 +2,8 @@ package com.fsck.k9.view;
import android.content.Context;
import android.graphics.Typeface;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.style.StyleSpan;
@ -10,7 +12,10 @@ import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.ScrollView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
@ -25,14 +30,14 @@ import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.store.LocalStore;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.text.DateFormat;
public class MessageHeader extends LinearLayout {
public class MessageHeader extends ScrollView implements OnClickListener {
private Context mContext;
private TextView mFromView;
private TextView mDateView;
@ -44,17 +49,19 @@ public class MessageHeader extends LinearLayout {
private DateFormat mTimeFormat;
private View mChip;
private View mChip2;
private CheckBox mFlagged;
private int defaultSubjectColor;
private LinearLayout mToContainerView;
private LinearLayout mCcContainerView;
private TextView mAdditionalHeadersView;
private View mAttachmentIcon;
private View mAnsweredIcon;
private Message mMessage;
private Account mAccount;
private FontSizes mFontSizes = K9.getFontSizes();
private Contacts mContacts;
private ImageView mShowAdditionalHeadersIcon;
private SavedState mSavedState;
/**
* Pair class is only available since API Level 5, so we need
@ -79,7 +86,6 @@ public class MessageHeader extends LinearLayout {
}
private void initializeLayout() {
mAttachmentIcon = findViewById(R.id.attachment);
mAnsweredIcon = findViewById(R.id.answered);
mFromView = (TextView) findViewById(R.id.from);
mToView = (TextView) findViewById(R.id.to);
@ -89,17 +95,20 @@ public class MessageHeader extends LinearLayout {
mSubjectView = (TextView) findViewById(R.id.subject);
mAdditionalHeadersView = (TextView) findViewById(R.id.additional_headers_view);
mChip = findViewById(R.id.chip);
mChip2 = findViewById(R.id.chip2);
mDateView = (TextView) findViewById(R.id.date);
mTimeView = (TextView) findViewById(R.id.time);
mFlagged = (CheckBox) findViewById(R.id.flagged);
mShowAdditionalHeadersIcon = (ImageView) findViewById(R.id.show_additional_headers_icon);
defaultSubjectColor = mSubjectView.getCurrentTextColor();
mSubjectView.setTextSize(TypedValue.COMPLEX_UNIT_SP, mFontSizes.getMessageViewSubject());
mTimeView.setTextSize(TypedValue.COMPLEX_UNIT_SP, mFontSizes.getMessageViewTime());
mDateView.setTextSize(TypedValue.COMPLEX_UNIT_SP, mFontSizes.getMessageViewDate());
mAdditionalHeadersView.setTextSize(TypedValue.COMPLEX_UNIT_SP, mFontSizes.getMessageViewAdditionalHeaders());
mAdditionalHeadersView.setVisibility(View.GONE);
mAttachmentIcon.setVisibility(View.GONE);
hideAdditionalHeaders();
mAnsweredIcon.setVisibility(View.GONE);
mFromView.setTextSize(TypedValue.COMPLEX_UNIT_SP, mFontSizes.getMessageViewSender());
mToView.setTextSize(TypedValue.COMPLEX_UNIT_SP, mFontSizes.getMessageViewTo());
@ -107,39 +116,45 @@ public class MessageHeader extends LinearLayout {
((TextView) findViewById(R.id.to_label)).setTextSize(TypedValue.COMPLEX_UNIT_SP, mFontSizes.getMessageViewTo());
((TextView) findViewById(R.id.cc_label)).setTextSize(TypedValue.COMPLEX_UNIT_SP, mFontSizes.getMessageViewCC());
setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
onShowAdditionalHeaders();
}
});
findViewById(R.id.show_additional_headers_area).setOnClickListener(this);
mFromView.setOnClickListener(this);
}
mFromView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (mMessage != null) {
try {
final Address senderEmail = mMessage.getFrom()[0];
mContacts.createContact(senderEmail);
} catch (Exception e) {
Log.e(K9.LOG_TAG, "Couldn't create contact", e);
}
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.show_additional_headers_area: {
onShowAdditionalHeaders();
break;
}
});
case R.id.from: {
onAddSenderToContacts();
break;
}
}
}
private void onAddSenderToContacts() {
if (mMessage != null) {
try {
final Address senderEmail = mMessage.getFrom()[0];
mContacts.createContact(senderEmail);
} catch (Exception e) {
Log.e(K9.LOG_TAG, "Couldn't create contact", e);
}
}
}
public void setOnFlagListener(OnClickListener listener) {
if (mFlagged == null)
return;
mFlagged.setOnClickListener(listener);
}
public boolean additionalHeadersVisible() {
if (mAdditionalHeadersView != null && mAdditionalHeadersView.getVisibility() == View.VISIBLE) {
return true;
} else {
return false;
}
return (mAdditionalHeadersView != null &&
mAdditionalHeadersView.getVisibility() == View.VISIBLE);
}
/**
@ -149,7 +164,7 @@ public class MessageHeader extends LinearLayout {
private void hideAdditionalHeaders() {
mAdditionalHeadersView.setVisibility(View.GONE);
mAdditionalHeadersView.setText("");
mShowAdditionalHeadersIcon.setImageResource(R.drawable.show_more);
}
@ -168,6 +183,7 @@ public class MessageHeader extends LinearLayout {
// Show the additional headers that we have got.
populateAdditionalHeadersView(additionalHeaders);
mAdditionalHeadersView.setVisibility(View.VISIBLE);
mShowAdditionalHeadersIcon.setImageResource(R.drawable.show_less);
}
if (!allHeadersDownloaded) {
/*
@ -227,14 +243,25 @@ public class MessageHeader extends LinearLayout {
mToView.setText(to);
mCcContainerView.setVisibility((cc != null && cc.length() > 0) ? View.VISIBLE : View.GONE);
mCcView.setText(cc);
mAttachmentIcon.setVisibility(((LocalStore.LocalMessage) message).hasAttachments() ? View.VISIBLE : View.GONE);
mAnsweredIcon.setVisibility(message.isSet(Flag.ANSWERED) ? View.VISIBLE : View.GONE);
mFlagged.setChecked(message.isSet(Flag.FLAGGED));
mChip.setBackgroundDrawable(mAccount.generateColorChip().drawable());
mChip.getBackground().setAlpha(!message.isSet(Flag.SEEN) ? 255 : 127);
int chipColor = mAccount.getChipColor();
int chipColorAlpha = (!message.isSet(Flag.SEEN)) ? 255 : 127;
mChip.setBackgroundColor(chipColor);
mChip.getBackground().setAlpha(chipColorAlpha);
mChip2.setBackgroundColor(chipColor);
mChip2.getBackground().setAlpha(chipColorAlpha);
setVisibility(View.VISIBLE);
if (mAdditionalHeadersView.getVisibility() == View.VISIBLE) {
showAdditionalHeaders();
if (mSavedState != null) {
if (mSavedState.additionalHeadersVisible) {
showAdditionalHeaders();
}
mSavedState = null;
} else {
hideAdditionalHeaders();
}
}
@ -294,4 +321,61 @@ public class MessageHeader extends LinearLayout {
mAdditionalHeadersView.setText(sb);
}
@Override
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState savedState = new SavedState(superState);
savedState.additionalHeadersVisible = additionalHeadersVisible();
return savedState;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
if(!(state instanceof SavedState)) {
super.onRestoreInstanceState(state);
return;
}
SavedState savedState = (SavedState)state;
super.onRestoreInstanceState(savedState.getSuperState());
mSavedState = savedState;
}
static class SavedState extends BaseSavedState {
boolean additionalHeadersVisible;
@SuppressWarnings("hiding")
public static final Parcelable.Creator<SavedState> CREATOR =
new Parcelable.Creator<SavedState>() {
@Override
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
@Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
SavedState(Parcelable superState) {
super(superState);
}
private SavedState(Parcel in) {
super(in);
this.additionalHeadersVisible = (in.readInt() != 0);
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeInt((this.additionalHeadersVisible) ? 1 : 0);
}
}
}

View File

@ -1,25 +1,20 @@
package com.fsck.k9.view;
import android.content.Context;
import android.graphics.Picture;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.widget.Toast;
import com.fsck.k9.K9;
import com.fsck.k9.R;
import com.fsck.k9.controller.MessagingListener;
import java.lang.reflect.Method;
import java.util.Set;
public class MessageWebView extends WebView {
// Store a reference to the listeners in MessagingController. We can't fetch it directly since
// we don't know the application name.
private Set<MessagingListener> mListeners = null;
/**
* We use WebSettings.getBlockNetworkLoads() to prevent the WebView that displays email
@ -75,7 +70,7 @@ public class MessageWebView extends WebView {
this.setVerticalScrollBarEnabled(true);
this.setVerticalScrollbarOverlay(true);
this.setScrollBarStyle(SCROLLBARS_INSIDE_OVERLAY);
this.setLongClickable(true);
final WebSettings webSettings = this.getSettings();
@ -97,22 +92,16 @@ public class MessageWebView extends WebView {
webSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NARROW_COLUMNS);
}
if (Integer.parseInt(Build.VERSION.SDK) >= 9 ) {
setOverScrollMode(OVER_SCROLL_NEVER);
}
webSettings.setTextSize(K9.getFontSizes().getMessageViewContent());
// Disable network images by default. This is overridden by preferences.
blockNetworkData(true);
// Listen for when the screen has finished drawing.
setPictureListener(new PictureListener() {
@Override
public void onNewPicture(WebView webView, Picture picture) {
if (mListeners != null) {
for (MessagingListener l : mListeners) {
l.messageViewFinished();
}
}
}
});
}
/*
@ -132,7 +121,15 @@ public class MessageWebView extends WebView {
}
}
public void setListeners(final Set<MessagingListener> listeners) {
this.mListeners = listeners;
public void wrapSetTitleBar(final View title) {
try {
Class<?> webViewClass = Class.forName("android.webkit.WebView");
Method setEmbeddedTitleBar = webViewClass.getMethod("setEmbeddedTitleBar", View.class);
setEmbeddedTitleBar.invoke(this, title);
}
catch (Exception e) {
Log.v(K9.LOG_TAG, "failed to find the setEmbeddedTitleBar method",e);
}
}
}

View File

@ -7,16 +7,20 @@ import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.database.Cursor;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.LinearLayout;
import com.fsck.k9.Account;
import com.fsck.k9.K9;
import com.fsck.k9.R;
import com.fsck.k9.activity.K9Activity;
import com.fsck.k9.controller.MessagingController;
import com.fsck.k9.controller.MessagingListener;
import com.fsck.k9.crypto.CryptoProvider;
@ -24,49 +28,67 @@ import com.fsck.k9.crypto.PgpData;
import com.fsck.k9.helper.Contacts;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.*;
import com.fsck.k9.mail.internet.MimeHeader;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.store.LocalStore;
import com.fsck.k9.mail.store.LocalStore.LocalMessage;
import java.util.List;
import java.util.Set;
/**
*/
public class SingleMessageView extends LinearLayout {
public class SingleMessageView extends LinearLayout implements OnClickListener {
private boolean mScreenReaderEnabled;
private MessageCryptoView mCryptoView;
private MessageWebView mMessageContentView;
private AccessibleWebView mAccessibleMessageContentView;
private MessageHeader mHeaderContainer;
private LinearLayout mAttachments;
private View mShowPicturesSection;
private Button mShowHiddenAttachments;
private LinearLayout mHiddenAttachments;
private View mShowPicturesAction;
private View mShowMessageAction;
private View mShowAttachmentsAction;
private boolean mShowPictures;
private boolean mHasAttachments;
private Button mDownloadRemainder;
private LayoutInflater mInflater;
private Contacts mContacts;
private AttachmentView.AttachmentFileDownloadCallback attachmentCallback;
private LinearLayout mHeaderPlaceHolder;
private LinearLayout mTitleBarHeaderContainer;
private View mAttachmentsContainer;
private LinearLayout mInsideAttachmentsContainer;
private SavedState mSavedState;
public void initialize(Activity activity) {
mMessageContentView = (MessageWebView) findViewById(R.id.message_content);
mAccessibleMessageContentView = (AccessibleWebView) findViewById(R.id.accessible_message_content);
mAttachments = (LinearLayout) findViewById(R.id.attachments);
mMessageContentView.configure();
mHeaderPlaceHolder = (LinearLayout) findViewById(R.id.message_view_header_container);
mHeaderContainer = (MessageHeader) findViewById(R.id.header_container);
mAttachmentsContainer = findViewById(R.id.attachments_container);
mInsideAttachmentsContainer = (LinearLayout) findViewById(R.id.inside_attachments_container);
mAttachments = (LinearLayout) findViewById(R.id.attachments);
mHiddenAttachments = (LinearLayout) findViewById(R.id.hidden_attachments);
mHiddenAttachments.setVisibility(View.GONE);
mShowHiddenAttachments = (Button) findViewById(R.id.show_hidden_attachments);
mShowHiddenAttachments.setVisibility(View.GONE);
mCryptoView = (MessageCryptoView) findViewById(R.id.layout_decrypt);
mCryptoView.setActivity(activity);
mCryptoView.setupChildViews();
mShowPicturesSection = findViewById(R.id.show_pictures_section);
mShowPicturesAction = findViewById(R.id.show_pictures);
mShowMessageAction = findViewById(R.id.show_message);
mShowAttachmentsAction = findViewById(R.id.show_attachments);
mShowPictures = false;
mContacts = Contacts.getInstance(activity);
mInflater = activity.getLayoutInflater();
mDownloadRemainder = (Button) findViewById(R.id.download_remainder);
mMessageContentView.configure();
mAttachments.setVisibility(View.GONE);
mDownloadRemainder.setVisibility(View.GONE);
mAttachmentsContainer.setVisibility(View.GONE);
if (isScreenReaderActive(activity)) {
mAccessibleMessageContentView.setVisibility(View.VISIBLE);
mMessageContentView.setVisibility(View.GONE);
@ -75,8 +97,62 @@ public class SingleMessageView extends LinearLayout {
mAccessibleMessageContentView.setVisibility(View.GONE);
mMessageContentView.setVisibility(View.VISIBLE);
mScreenReaderEnabled = false;
mHeaderPlaceHolder.removeView(mHeaderContainer);
// the HTC version of WebView tries to force the background of the
// titlebar, which is really unfair.
mHeaderContainer.setBackgroundColor(((K9Activity)activity).getThemeBackgroundColor());
mTitleBarHeaderContainer = new LinearLayout(activity);
mTitleBarHeaderContainer.addView(mHeaderContainer);
mMessageContentView.wrapSetTitleBar(mTitleBarHeaderContainer);
}
mShowHiddenAttachments.setOnClickListener(this);
mShowMessageAction.setOnClickListener(this);
mShowAttachmentsAction.setOnClickListener(this);
mShowPicturesAction.setOnClickListener(this);
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.show_hidden_attachments: {
onShowHiddenAttachments();
break;
}
case R.id.show_message: {
onShowMessage();
break;
}
case R.id.show_attachments: {
onShowAttachments();
break;
}
case R.id.show_pictures: {
setLoadPictures(true);
break;
}
}
}
private void onShowHiddenAttachments() {
mShowHiddenAttachments.setVisibility(View.GONE);
mHiddenAttachments.setVisibility(View.VISIBLE);
}
public void onShowMessage() {
showShowMessageAction(false);
showAttachments(false);
showShowAttachmentsAction(mHasAttachments);
showMessageWebView(true);
}
public void onShowAttachments() {
showMessageWebView(false);
showShowAttachmentsAction(false);
showShowMessageAction(true);
showAttachments(true);
}
public SingleMessageView(Context context, AttributeSet attrs) {
@ -102,15 +178,19 @@ public class SingleMessageView extends LinearLayout {
// content://<nameofpackage>.providers.StatusProvider
cursor = cr.query(Uri.parse("content://" + screenReader.serviceInfo.packageName
+ ".providers.StatusProvider"), null, null, null, null);
if (cursor != null) {
cursor.moveToFirst();
// These content providers use a special cursor that only has
// one element,
// an integer that is 1 if the screen reader is running.
status = cursor.getInt(0);
cursor.close();
if (status == 1) {
return true;
try {
if (cursor != null && cursor.moveToFirst()) {
// These content providers use a special cursor that only has
// one element,
// an integer that is 1 if the screen reader is running.
status = cursor.getInt(0);
if (status == 1) {
return true;
}
}
} finally {
if (cursor != null) {
cursor.close();
}
}
}
@ -135,20 +215,27 @@ public class SingleMessageView extends LinearLayout {
public void setLoadPictures(boolean enable) {
mMessageContentView.blockNetworkData(!enable);
setShowPictures(enable);
showShowPicturesSection(false);
showShowPicturesAction(false);
}
public Button downloadRemainderButton() {
return mDownloadRemainder;
}
public void showShowPicturesSection(boolean show) {
mShowPicturesSection.setVisibility(show ? View.VISIBLE : View.GONE);
public void showShowPicturesAction(boolean show) {
mShowPicturesAction.setVisibility(show ? View.VISIBLE : View.GONE);
}
public void showShowMessageAction(boolean show) {
mShowMessageAction.setVisibility(show ? View.VISIBLE : View.GONE);
}
public void showShowAttachmentsAction(boolean show) {
mShowAttachmentsAction.setVisibility(show ? View.VISIBLE : View.GONE);
}
public void setHeaders(final Message message, Account account) {
try {
mHeaderContainer.populate(message, account);
mHeaderContainer.setVisibility(View.VISIBLE);
} catch (Exception me) {
@ -177,9 +264,9 @@ public class SingleMessageView extends LinearLayout {
return mHeaderContainer.additionalHeadersVisible();
}
public void displayMessageBody(Account account, String folder, String uid, Message message, PgpData pgpData) throws MessagingException {
// TODO - really this code path? this is an odd place to put it
removeAllAttachments();
public void setMessage(Account account, LocalMessage message, PgpData pgpData,
MessagingController controller, MessagingListener listener) throws MessagingException {
resetView();
String type;
String text = pgpData.getDecryptedData();
@ -187,13 +274,49 @@ public class SingleMessageView extends LinearLayout {
type = "text/plain";
} else {
// getTextForDisplay() always returns HTML-ified content.
text = ((LocalStore.LocalMessage) message).getTextForDisplay();
text = message.getTextForDisplay();
type = "text/html";
}
if (text != null) {
final String emailText = text;
final String contentType = type;
loadBodyFromText(account.getCryptoProvider(), pgpData, message, emailText, contentType);
loadBodyFromText(emailText, contentType);
updateCryptoLayout(account.getCryptoProvider(), pgpData, message);
} else {
loadBodyFromUrl("file:///android_asset/empty.html");
}
mHasAttachments = message.hasAttachments();
if (mHasAttachments) {
renderAttachments(message, 0, message, account, controller, listener);
}
mHiddenAttachments.setVisibility(View.GONE);
boolean lookForImages = true;
if (mSavedState != null) {
if (mSavedState.showPictures) {
setLoadPictures(true);
lookForImages = false;
}
if (mSavedState.attachmentViewVisible) {
onShowAttachments();
} else {
onShowMessage();
}
if (mSavedState.hiddenAttachmentsVisible) {
onShowHiddenAttachments();
}
mSavedState = null;
} else {
onShowMessage();
}
if (text != null && lookForImages) {
// If the message contains external pictures and the "Show pictures"
// button wasn't already pressed, see if the user's preferences has us
// showing them anyway.
@ -206,11 +329,9 @@ public class SingleMessageView extends LinearLayout {
mContacts.isInContacts(from[0].getAddress()))) {
setLoadPictures(true);
} else {
showShowPicturesSection(true);
showShowPicturesAction(true);
}
}
} else {
loadBodyFromUrl("file:///android_asset/empty.html");
}
}
@ -220,14 +341,13 @@ public class SingleMessageView extends LinearLayout {
}
public void loadBodyFromText(CryptoProvider cryptoProvider, PgpData pgpData, Message message, String emailText, String contentType) {
private void loadBodyFromText(String emailText, String contentType) {
if (mScreenReaderEnabled) {
mAccessibleMessageContentView.loadDataWithBaseURL("http://", emailText, contentType, "utf-8", null);
} else {
mMessageContentView.loadDataWithBaseURL("http://", emailText, contentType, "utf-8", null);
mMessageContentView.scrollTo(0, 0);
}
updateCryptoLayout(cryptoProvider, pgpData, message);
}
@ -235,6 +355,23 @@ public class SingleMessageView extends LinearLayout {
mCryptoView.updateLayout(cp, pgpData, message);
}
public void showAttachments(boolean show) {
mAttachmentsContainer.setVisibility(show ? View.VISIBLE : View.GONE);
boolean showHidden = (show && mHiddenAttachments.getVisibility() == View.GONE &&
mHiddenAttachments.getChildCount() > 0);
mShowHiddenAttachments.setVisibility(showHidden ? View.VISIBLE : View.GONE);
if (show) {
moveHeaderToLayout();
} else {
moveHeaderToWebViewTitleBar();
}
}
public void showMessageWebView(boolean show) {
mMessageContentView.setVisibility(show ? View.VISIBLE : View.GONE);
}
public void setAttachmentsEnabled(boolean enabled) {
for (int i = 0, count = mAttachments.getChildCount(); i < count; i++) {
AttachmentView attachment = (AttachmentView) mAttachments.getChildAt(i);
@ -243,7 +380,6 @@ public class SingleMessageView extends LinearLayout {
}
}
public void removeAllAttachments() {
for (int i = 0, count = mAttachments.getChildCount(); i < count; i++) {
mAttachments.removeView(mAttachments.getChildAt(i));
@ -259,25 +395,27 @@ public class SingleMessageView extends LinearLayout {
renderAttachments(mp.getBodyPart(i), depth + 1, message, account, controller, listener);
}
} else if (part instanceof LocalStore.LocalAttachmentBodyPart) {
String contentDisposition = MimeUtility.unfoldAndDecode(part.getDisposition());
// Inline parts with a content-id are almost certainly components of an HTML message
// not attachments. Don't show attachment download buttons for them.
if (contentDisposition != null &&
MimeUtility.getHeaderParameter(contentDisposition, null).matches("^(?i:inline)")
&& part.getHeader(MimeHeader.HEADER_CONTENT_ID) != null) {
return;
}
AttachmentView view = (AttachmentView)mInflater.inflate(R.layout.message_view_attachment, null);
view.setCallback(attachmentCallback);
if (view.populateFromPart(part, message, account, controller, listener)) {
addAttachment(view);
try {
if (view.populateFromPart(part, message, account, controller, listener)) {
addAttachment(view);
} else {
addHiddenAttachment(view);
}
} catch (Exception e) {
Log.e(K9.LOG_TAG, "Error adding attachment view", e);
}
}
}
public void addAttachment(View attachmentView) {
mAttachments.addView(attachmentView);
mAttachments.setVisibility(View.VISIBLE);
}
public void addHiddenAttachment(View attachmentView) {
mHiddenAttachments.addView(attachmentView);
}
public void zoom(KeyEvent event) {
@ -297,11 +435,26 @@ public class SingleMessageView extends LinearLayout {
}
public void resetView() {
mDownloadRemainder.setVisibility(View.GONE);
setLoadPictures(false);
mMessageContentView.scrollTo(0, 0);
mHeaderContainer.setVisibility(View.GONE);
mMessageContentView.clearView();
showShowAttachmentsAction(false);
showShowMessageAction(false);
showShowPicturesAction(false);
mAttachments.removeAllViews();
mHiddenAttachments.removeAllViews();
/*
* Clear the WebView content
*
* For some reason WebView.clearView() doesn't clear the contents when the WebView changes
* its size because the button to download the complete message was previously shown and
* is now hidden.
*/
loadBodyFromText("", "text/plain");
}
public void resetHeaderView() {
mHeaderContainer.setVisibility(View.GONE);
}
public AttachmentView.AttachmentFileDownloadCallback getAttachmentCallback() {
@ -313,20 +466,85 @@ public class SingleMessageView extends LinearLayout {
this.attachmentCallback = attachmentCallback;
}
/**
* Save a copy of the {@link com.fsck.k9.controller.MessagingController#getListeners()}. This method will also
* pass along these listeners to the underlying views.
* @param listeners Set of listeners.
*/
public void setListeners(final Set<MessagingListener> listeners) {
if (!mScreenReaderEnabled) {
if (mMessageContentView != null) {
mMessageContentView.setListeners(listeners);
private void moveHeaderToLayout() {
if (mTitleBarHeaderContainer != null && mTitleBarHeaderContainer.getChildCount() != 0) {
mTitleBarHeaderContainer.removeView(mHeaderContainer);
mInsideAttachmentsContainer.addView(mHeaderContainer, 0);
}
}
private void moveHeaderToWebViewTitleBar() {
if (mTitleBarHeaderContainer != null && mTitleBarHeaderContainer.getChildCount() == 0) {
mInsideAttachmentsContainer.removeView(mHeaderContainer);
mTitleBarHeaderContainer.addView(mHeaderContainer);
}
}
@Override
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState savedState = new SavedState(superState);
savedState.attachmentViewVisible = (mAttachmentsContainer != null &&
mAttachmentsContainer.getVisibility() == View.VISIBLE);
savedState.hiddenAttachmentsVisible = (mHiddenAttachments != null &&
mHiddenAttachments.getVisibility() == View.VISIBLE);
savedState.showPictures = mShowPictures;
return savedState;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
if(!(state instanceof SavedState)) {
super.onRestoreInstanceState(state);
return;
}
SavedState savedState = (SavedState)state;
super.onRestoreInstanceState(savedState.getSuperState());
mSavedState = savedState;
}
static class SavedState extends BaseSavedState {
boolean attachmentViewVisible;
boolean hiddenAttachmentsVisible;
boolean showPictures;
@SuppressWarnings("hiding")
public static final Parcelable.Creator<SavedState> CREATOR =
new Parcelable.Creator<SavedState>() {
@Override
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
} else {
if (mAccessibleMessageContentView != null) {
mAccessibleMessageContentView.setListeners(listeners);
@Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
SavedState(Parcelable superState) {
super(superState);
}
private SavedState(Parcel in) {
super(in);
this.attachmentViewVisible = (in.readInt() != 0);
this.hiddenAttachmentsVisible = (in.readInt() != 0);
this.showPictures = (in.readInt() != 0);
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeInt((this.attachmentViewVisible) ? 1 : 0);
out.writeInt((this.hiddenAttachmentsVisible) ? 1 : 0);
out.writeInt((this.showPictures) ? 1 : 0);
}
}
}

View File

@ -0,0 +1,188 @@
package com.fsck.k9.mail.internet;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import android.test.AndroidTestCase;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Message.RecipientType;
import com.fsck.k9.mail.internet.MimeUtility.ViewableContainer;
public class ViewablesTest extends AndroidTestCase {
public void testSimplePlainTextMessage() throws MessagingException {
String bodyText = "K-9 Mail rocks :>";
// Create text/plain body
TextBody body = new TextBody(bodyText);
// Create message
MimeMessage message = new MimeMessage();
message.setBody(body);
// Extract text
ViewableContainer container = MimeUtility.extractTextAndAttachments(getContext(), message);
String expectedText = bodyText;
String expectedHtml =
"<html><head/><body>" +
"<pre style=\"white-space: pre-wrap; word-wrap:break-word; " +
"font-family: sans-serif\">" +
"K-9 Mail rocks :&gt;" +
"</pre>" +
"</body></html>";
assertEquals(expectedText, container.text);
assertEquals(expectedHtml, container.html);
}
public void testSimpleHtmlMessage() throws MessagingException {
String bodyText = "<strong>K-9 Mail</strong> rocks :&gt;";
// Create text/plain body
TextBody body = new TextBody(bodyText);
// Create message
MimeMessage message = new MimeMessage();
message.setHeader("Content-Type", "text/html");
message.setBody(body);
// Extract text
ViewableContainer container = MimeUtility.extractTextAndAttachments(getContext(), message);
String expectedText = "K-9 Mail rocks :>";
String expectedHtml =
"<html><head/><body>" +
bodyText +
"</body></html>";
assertEquals(expectedText, container.text);
assertEquals(expectedHtml, container.html);
}
public void testMultipartPlainTextMessage() throws MessagingException {
String bodyText1 = "text body 1";
String bodyText2 = "text body 2";
// Create text/plain bodies
TextBody body1 = new TextBody(bodyText1);
TextBody body2 = new TextBody(bodyText2);
// Create multipart/mixed part
MimeMultipart multipart = new MimeMultipart();
MimeBodyPart bodyPart1 = new MimeBodyPart(body1, "text/plain");
MimeBodyPart bodyPart2 = new MimeBodyPart(body2, "text/plain");
multipart.addBodyPart(bodyPart1);
multipart.addBodyPart(bodyPart2);
// Create message
MimeMessage message = new MimeMessage();
message.setBody(multipart);
// Extract text
ViewableContainer container = MimeUtility.extractTextAndAttachments(getContext(), message);
String expectedText =
bodyText1 + "\n\n" +
"------------------------------------------------------------------------\n\n" +
bodyText2;
String expectedHtml =
"<html><head/><body>" +
"<pre style=\"white-space: pre-wrap; word-wrap:break-word; " +
"font-family: sans-serif\">" +
bodyText1 +
"</pre>" +
"<p style=\"margin-top: 2.5em; margin-bottom: 1em; " +
"border-bottom: 1px solid #000\"></p>" +
"<pre style=\"white-space: pre-wrap; word-wrap:break-word; " +
"font-family: sans-serif\">" +
bodyText2 +
"</pre>" +
"</body></html>";
assertEquals(expectedText, container.text);
assertEquals(expectedHtml, container.html);
}
public void testTextPlusRfc822Message() throws MessagingException {
Locale.setDefault(Locale.US);
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
String bodyText = "Some text here";
String innerBodyText = "Hey there. I'm inside a message/rfc822 (inline) attachment.";
// Create text/plain body
TextBody textBody = new TextBody(bodyText);
// Create inner text/plain body
TextBody innerBody = new TextBody(innerBodyText);
// Create message/rfc822 body
MimeMessage innerMessage = new MimeMessage();
innerMessage.addSentDate(new Date(112, 02, 17));
innerMessage.setRecipients(RecipientType.TO, new Address[] { new Address("to@example.com") });
innerMessage.setSubject("Subject");
innerMessage.setFrom(new Address("from@example.com"));
innerMessage.setBody(innerBody);
// Create multipart/mixed part
MimeMultipart multipart = new MimeMultipart();
MimeBodyPart bodyPart1 = new MimeBodyPart(textBody, "text/plain");
MimeBodyPart bodyPart2 = new MimeBodyPart(innerMessage, "message/rfc822");
bodyPart2.setHeader("Content-Disposition", "inline; filename=\"message.eml\"");
multipart.addBodyPart(bodyPart1);
multipart.addBodyPart(bodyPart2);
// Create message
MimeMessage message = new MimeMessage();
message.setBody(multipart);
// Extract text
ViewableContainer container = MimeUtility.extractTextAndAttachments(getContext(), message);
String expectedText =
bodyText +
"\n\n" +
"----- message.eml ------------------------------------------------------" +
"\n\n" +
"From: from@example.com" + "\n" +
"To: to@example.com" + "\n" +
"Sent: Sat Mar 17 00:00:00 GMT+00:00 2012" + "\n" +
"Subject: Subject" + "\n" +
"\n" +
innerBodyText;
String expectedHtml =
"<html><head/><body>" +
"<pre style=\"white-space: pre-wrap; word-wrap:break-word; " +
"font-family: sans-serif\">" +
bodyText +
"</pre>" +
"<p style=\"margin-top: 2.5em; margin-bottom: 1em; border-bottom: " +
"1px solid #000\">message.eml</p>" +
"<table style=\"border: 0\">" +
"<tr>" +
"<th style=\"text-align: left; vertical-align: top;\">From:</th>" +
"<td>from@example.com</td>" +
"</tr><tr>" +
"<th style=\"text-align: left; vertical-align: top;\">To:</th>" +
"<td>to@example.com</td>" +
"</tr><tr>" +
"<th style=\"text-align: left; vertical-align: top;\">Sent:</th>" +
"<td>Sat Mar 17 00:00:00 GMT+00:00 2012</td>" +
"</tr><tr>" +
"<th style=\"text-align: left; vertical-align: top;\">Subject:</th>" +
"<td>Subject</td>" +
"</tr>" +
"</table>" +
"<pre style=\"white-space: pre-wrap; word-wrap:break-word; " +
"font-family: sans-serif\">" +
innerBodyText +
"</pre>" +
"</body></html>";
assertEquals(expectedText, container.text);
assertEquals(expectedHtml, container.html);
}
}

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());
}
}