diff --git a/res/layout/account_setup_incoming.xml b/res/layout/account_setup_incoming.xml
index 30a1fbca6..a362694e3 100644
--- a/res/layout/account_setup_incoming.xml
+++ b/res/layout/account_setup_incoming.xml
@@ -212,6 +212,17 @@
android:text="@string/account_setup_incoming_other_label"
/>
+
+
-
-
+
+
+
-
+ - Add star
Remove star
Copy
-
+ Show full header
+ Hide full header
+
Mark as unread
Move to
Folders
@@ -260,7 +262,12 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin
Show pictures
Fetching attachment.
Unable to find viewer for %s.
-
+
+
+ Not all headers have been downloaded or saved. Select \"Save all headers locally\" in the account\'s incoming server settings to enable this for the future.
+ All headers have been downloaded, but there are no additional headers to show.
+ The retrieval of additional headers from the database or mail server failed.
+
Folders
New folder
@@ -341,6 +348,9 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin
Wi-Fi
Other
+ Downloading of mail headers
+ Save all headers locally
+
Expunge messages
Immediately after delete or move
During each poll
@@ -764,6 +774,7 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin
Message sender
Message receiver (To)
Message receiver (CC)
+ Additional headers
Message subject
Message time
Message date
diff --git a/res/xml/font_preferences.xml b/res/xml/font_preferences.xml
index 1a9385745..b8c7624e1 100644
--- a/res/xml/font_preferences.xml
+++ b/res/xml/font_preferences.xml
@@ -86,6 +86,13 @@
android:entryValues="@array/font_values"
android:dialogTitle="@string/font_size_message_view_cc" />
+
+
additionalHeaders = getAdditionalHeaders(mMessage);
+
+ if (!additionalHeaders.isEmpty())
+ {
+ // Show the additional headers that we have got.
+ setupAdditionalHeadersView(additionalHeaders);
+ mAdditionalHeadersView.setVisibility(View.VISIBLE);
+ }
+
+ if (!allHeadersDownloaded)
+ {
+ /*
+ * Tell the user about the "save all headers" setting
+ *
+ * NOTE: This is only a temporary solution... in fact,
+ * the system should download headers on-demand when they
+ * have not been saved in their entirety initially.
+ */
+ messageToShow = R.string.message_additional_headers_not_downloaded;
+ }
+ else if (additionalHeaders.isEmpty())
+ {
+ // All headers have been downloaded, but there are no additional headers.
+ messageToShow = R.string.message_no_additional_headers_available;
+ }
+ }
+ catch(MessagingException e)
+ {
+ messageToShow = R.string.message_additional_headers_retrieval_failed;
+ }
+
+ // Show a message to the user, if any
+ if (messageToShow != null)
+ {
+ Toast toast = Toast.makeText(MessageView.this, messageToShow, Toast.LENGTH_LONG);
+ toast.setGravity(Gravity.CENTER_VERTICAL | Gravity.CENTER_HORIZONTAL, 0, 0);
+ toast.show();
+ }
+ }
+ });
+ }
+
+ /**
+ * Set up the additional headers text view with the supplied header data.
+ *
+ * @param additionalHeaders
+ * List of header entries. Each entry consists of a header
+ * name and a header value. Header names may appear multiple
+ * times.
+ *
+ * This method is always called from within the UI thread by
+ * {@link #showAdditionalHeaders()}.
+ */
+ private void setupAdditionalHeadersView(final List additionalHeaders)
+ {
+ SpannableStringBuilder sb = new SpannableStringBuilder();
+ boolean first = true;
+ for (HeaderEntry additionalHeader : additionalHeaders)
+ {
+ if (!first)
+ {
+ sb.append("\n");
+ }
+ else
+ {
+ first = false;
+ }
+
+ StyleSpan boldSpan = new StyleSpan(Typeface.BOLD);
+ SpannableString label = new SpannableString(additionalHeader.label + ": ");
+ label.setSpan(boldSpan, 0, label.length(), 0);
+
+ sb.append(label);
+ sb.append(additionalHeader.value.replaceAll("\\r\\n", ""));
+ }
+
+ mAdditionalHeadersView.setText(sb);
+ }
}
class Attachment
@@ -460,6 +593,8 @@ public class MessageView extends K9Activity implements OnClickListener
mSubjectView = (TextView)findViewById(R.id.subject);
defaultSubjectColor = mSubjectView.getCurrentTextColor();
+ mAdditionalHeadersView = (TextView)findViewById(R.id.additional_headers_view);
+
chip = findViewById(R.id.chip);
mDateView = (TextView)findViewById(R.id.date);
@@ -503,7 +638,8 @@ public class MessageView extends K9Activity implements OnClickListener
mSubjectView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, mFontSizes.getMessageViewSubject());
mTimeView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, mFontSizes.getMessageViewTime());
mDateView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, mFontSizes.getMessageViewDate());
-
+ mAdditionalHeadersView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, mFontSizes.getMessageViewAdditionalHeaders());
+ mAdditionalHeadersView.setVisibility(View.GONE);
mAttachments.setVisibility(View.GONE);
mAttachmentIcon.setVisibility(View.GONE);
@@ -515,6 +651,9 @@ public class MessageView extends K9Activity implements OnClickListener
setOnClickListener(R.id.next);
setOnClickListener(R.id.previous);
+ // 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);
@@ -830,7 +969,7 @@ public class MessageView extends K9Activity implements OnClickListener
{
mMessage.setFlag(Flag.FLAGGED, !mMessage.isSet(Flag.FLAGGED));
setHeaders(mAccount, mMessage.getFolder().getName(), mMessage.getUid(), mMessage);
- setMenuFlag();
+ prepareMenuItems();
}
catch (MessagingException me)
{
@@ -877,6 +1016,43 @@ public class MessageView extends K9Activity implements OnClickListener
startActivityForResult(intent, ACTIVITY_CHOOSE_FOLDER_COPY);
}
+ private void onShowAdditionalHeaders()
+ {
+ int currentVisibility = mAdditionalHeadersView.getVisibility();
+
+ if (currentVisibility == View.VISIBLE)
+ {
+ mHandler.hideAdditionalHeaders();
+ }
+ else
+ {
+ mHandler.showAdditionalHeaders();
+ }
+ }
+
+ private List getAdditionalHeaders(final Message message)
+ throws MessagingException
+ {
+ List additionalHeaders = new LinkedList();
+
+ // Do not include the following headers, since they are always visible anyway
+ Set headerNames = new HashSet(message.getHeaderNames());
+ headerNames.remove("To");
+ headerNames.remove("From");
+ headerNames.remove("Cc");
+ headerNames.remove("Subject");
+
+ for (String headerName : headerNames)
+ {
+ String[] headerValues = message.getHeader(headerName);
+ for (String headerValue : headerValues)
+ {
+ additionalHeaders.add(new HeaderEntry(headerName, headerValue));
+ }
+ }
+ return additionalHeaders;
+ }
+
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
@@ -1100,6 +1276,9 @@ public class MessageView extends K9Activity implements OnClickListener
case R.id.show_pictures:
onShowPictures();
break;
+ case R.id.header_container:
+ onShowAdditionalHeaders();
+ break;
}
}
@@ -1135,6 +1314,9 @@ public class MessageView extends K9Activity implements OnClickListener
case R.id.copy:
onCopy();
break;
+ case R.id.show_full_header:
+ onShowAdditionalHeaders();
+ break;
default:
return super.onOptionsItemSelected(item);
}
@@ -1147,7 +1329,7 @@ public class MessageView extends K9Activity implements OnClickListener
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.message_view_option, menu);
optionsMenu = menu;
- setMenuFlag();
+ prepareMenuItems();
if (MessagingController.getInstance(getApplication()).isCopyCapable(mAccount) == false)
{
menu.findItem(R.id.copy).setVisible(false);
@@ -1162,11 +1344,11 @@ public class MessageView extends K9Activity implements OnClickListener
@Override
public boolean onPrepareOptionsMenu(Menu menu)
{
- setMenuFlag();
+ prepareMenuItems();
return super.onPrepareOptionsMenu(menu);
}
- private void setMenuFlag()
+ private void prepareMenuItems()
{
Menu menu = optionsMenu;
if (menu != null)
@@ -1176,6 +1358,13 @@ public class MessageView extends K9Activity implements OnClickListener
{
flagItem.setTitle((mMessage.isSet(Flag.FLAGGED) ? R.string.unflag_action : R.string.flag_action));
}
+
+ MenuItem additionalHeadersItem = menu.findItem(R.id.show_full_header);
+ if (additionalHeadersItem != null)
+ {
+ additionalHeadersItem.setTitle((mAdditionalHeadersView.getVisibility() == View.VISIBLE) ?
+ R.string.hide_full_header_action : R.string.show_full_header_action);
+ }
}
}
@@ -1328,10 +1517,12 @@ public class MessageView extends K9Activity implements OnClickListener
String timeText = getTimeFormat().format(message.getSentDate());
String toText = Address.toFriendly(message.getRecipients(RecipientType.TO));
String ccText = Address.toFriendly(message.getRecipients(RecipientType.CC));
+
int color = mAccount.getChipColor();
boolean hasAttachments = ((LocalMessage) message).getAttachmentCount() > 0;
boolean isDownloading = !message.isSet(Flag.X_DOWNLOADED_FULL);
boolean unread = !message.isSet(Flag.SEEN);
+
mHandler.setHeaders(subjectText,
fromText,
dateText,
@@ -1344,6 +1535,12 @@ public class MessageView extends K9Activity implements OnClickListener
isDownloading,
message.isSet(Flag.FLAGGED),
message.isSet(Flag.ANSWERED));
+
+ // Update additional headers display, if visible
+ if (mAdditionalHeadersView.getVisibility() == View.VISIBLE)
+ {
+ mHandler.showAdditionalHeaders();
+ }
}
class Listener extends MessagingListener
diff --git a/src/com/fsck/k9/activity/setup/AccountSetupIncoming.java b/src/com/fsck/k9/activity/setup/AccountSetupIncoming.java
index 4c8afbd8f..072cc36ac 100644
--- a/src/com/fsck/k9/activity/setup/AccountSetupIncoming.java
+++ b/src/com/fsck/k9/activity/setup/AccountSetupIncoming.java
@@ -85,6 +85,7 @@ public class AccountSetupIncoming extends K9Activity implements OnClickListener
private CheckBox compressionMobile;
private CheckBox compressionWifi;
private CheckBox compressionOther;
+ private CheckBox saveAllHeaders;
private CheckBox pushPollOnConnect;
private Spinner idleRefreshPeriod;
private Spinner folderPushLimit;
@@ -130,6 +131,7 @@ public class AccountSetupIncoming extends K9Activity implements OnClickListener
compressionMobile = (CheckBox)findViewById(R.id.compression_mobile);
compressionWifi = (CheckBox)findViewById(R.id.compression_wifi);
compressionOther = (CheckBox)findViewById(R.id.compression_other);
+ saveAllHeaders = (CheckBox)findViewById(R.id.save_all_headers);
pushPollOnConnect = (CheckBox)findViewById(R.id.push_poll_on_connect);
idleRefreshPeriod = (Spinner)findViewById(R.id.idle_refresh_period);
@@ -389,6 +391,7 @@ public class AccountSetupIncoming extends K9Activity implements OnClickListener
compressionMobile.setChecked(mAccount.useCompression(Account.TYPE_MOBILE));
compressionWifi.setChecked(mAccount.useCompression(Account.TYPE_WIFI));
compressionOther.setChecked(mAccount.useCompression(Account.TYPE_OTHER));
+
if (uri.getHost() != null)
{
mServerView.setText(uri.getHost());
@@ -402,6 +405,8 @@ public class AccountSetupIncoming extends K9Activity implements OnClickListener
{
updatePortFromSecurityType();
}
+
+ saveAllHeaders.setChecked(mAccount.isSaveAllHeaders());
pushPollOnConnect.setChecked(mAccount.isPushPollOnConnect());
SpinnerHelper.initSpinner(this, idleRefreshPeriod, R.array.idle_refresh_period_entries,
R.array.idle_refresh_period_values, String.valueOf(mAccount.getIdleRefreshMinutes()));
@@ -557,6 +562,7 @@ public class AccountSetupIncoming extends K9Activity implements OnClickListener
mAccount.setCompression(Account.TYPE_MOBILE, compressionMobile.isChecked());
mAccount.setCompression(Account.TYPE_WIFI, compressionWifi.isChecked());
mAccount.setCompression(Account.TYPE_OTHER, compressionOther.isChecked());
+ mAccount.setSaveAllHeaders(saveAllHeaders.isChecked());
mAccount.setPushPollOnConnect(pushPollOnConnect.isChecked());
String idleRefreshPeriodValue = SpinnerHelper.getSpinnerValue(idleRefreshPeriod);
try
diff --git a/src/com/fsck/k9/activity/setup/FontSizeSettings.java b/src/com/fsck/k9/activity/setup/FontSizeSettings.java
index 12b9060da..b962be05e 100644
--- a/src/com/fsck/k9/activity/setup/FontSizeSettings.java
+++ b/src/com/fsck/k9/activity/setup/FontSizeSettings.java
@@ -32,6 +32,7 @@ public class FontSizeSettings extends K9PreferenceActivity
private static final String PREFERENCE_MESSAGE_VIEW_SENDER_FONT = "message_view_sender_font";
private static final String PREFERENCE_MESSAGE_VIEW_TO_FONT = "message_view_to_font";
private static final String PREFERENCE_MESSAGE_VIEW_CC_FONT = "message_view_cc_font";
+ private static final String PREFERENCE_MESSAGE_VIEW_ADDITIONAL_HEADERS_FONT = "message_view_additional_headers_font";
private static final String PREFERENCE_MESSAGE_VIEW_SUBJECT_FONT = "message_view_subject_font";
private static final String PREFERENCE_MESSAGE_VIEW_TIME_FONT = "message_view_time_font";
private static final String PREFERENCE_MESSAGE_VIEW_DATE_FONT = "message_view_date_font";
@@ -47,6 +48,7 @@ public class FontSizeSettings extends K9PreferenceActivity
private ListPreference mMessageViewSender;
private ListPreference mMessageViewTo;
private ListPreference mMessageViewCC;
+ private ListPreference mMessageViewAdditionalHeaders;
private ListPreference mMessageViewSubject;
private ListPreference mMessageViewTime;
private ListPreference mMessageViewDate;
@@ -85,6 +87,7 @@ public class FontSizeSettings extends K9PreferenceActivity
mMessageViewSender = initializeListPreference(PREFERENCE_MESSAGE_VIEW_SENDER_FONT, fontSizes.getMessageViewSender());
mMessageViewTo = initializeListPreference(PREFERENCE_MESSAGE_VIEW_TO_FONT, fontSizes.getMessageViewTo());
mMessageViewCC = initializeListPreference(PREFERENCE_MESSAGE_VIEW_CC_FONT, fontSizes.getMessageViewCC());
+ mMessageViewAdditionalHeaders = initializeListPreference(PREFERENCE_MESSAGE_VIEW_ADDITIONAL_HEADERS_FONT, fontSizes.getMessageViewAdditionalHeaders());
mMessageViewSubject = initializeListPreference(PREFERENCE_MESSAGE_VIEW_SUBJECT_FONT, fontSizes.getMessageViewSubject());
mMessageViewTime = initializeListPreference(PREFERENCE_MESSAGE_VIEW_TIME_FONT, fontSizes.getMessageViewTime());
mMessageViewDate = initializeListPreference(PREFERENCE_MESSAGE_VIEW_DATE_FONT, fontSizes.getMessageViewDate());
@@ -112,6 +115,7 @@ public class FontSizeSettings extends K9PreferenceActivity
fontSizes.setMessageViewSender(Integer.parseInt(mMessageViewSender.getValue()));
fontSizes.setMessageViewTo(Integer.parseInt(mMessageViewTo.getValue()));
fontSizes.setMessageViewCC(Integer.parseInt(mMessageViewCC.getValue()));
+ fontSizes.setMessageViewAdditionalHeaders(Integer.parseInt(mMessageViewAdditionalHeaders.getValue()));
fontSizes.setMessageViewSubject(Integer.parseInt(mMessageViewSubject.getValue()));
fontSizes.setMessageViewTime(Integer.parseInt(mMessageViewTime.getValue()));
fontSizes.setMessageViewDate(Integer.parseInt(mMessageViewDate.getValue()));
diff --git a/src/com/fsck/k9/mail/Flag.java b/src/com/fsck/k9/mail/Flag.java
index 706abf5d2..9291fcecf 100644
--- a/src/com/fsck/k9/mail/Flag.java
+++ b/src/com/fsck/k9/mail/Flag.java
@@ -15,9 +15,6 @@ public enum Flag
/*
* The following flags are for internal library use only.
- * TODO Eventually we should creates a Flags class that extends ArrayList that allows
- * these flags and Strings to represent user defined flags. At that point the below
- * flags should become user defined flags.
*/
/**
* Delete and remove from the LocalStore immediately.
@@ -51,4 +48,11 @@ public enum Flag
* Indicates that the copy of a message to the Sent folder has started.
*/
X_REMOTE_COPY_STARTED,
+
+ /**
+ * Indicates that all headers of the message have been stored in the
+ * database. If this is false, additional headers might be retrieved from
+ * the server (if the message is still there).
+ */
+ X_GOT_ALL_HEADERS,
}
diff --git a/src/com/fsck/k9/mail/Message.java b/src/com/fsck/k9/mail/Message.java
index 06bc8fc18..fa9ea2981 100644
--- a/src/com/fsck/k9/mail/Message.java
+++ b/src/com/fsck/k9/mail/Message.java
@@ -3,6 +3,7 @@ package com.fsck.k9.mail;
import java.util.Date;
import java.util.HashSet;
+import java.util.Set;
import com.fsck.k9.activity.MessageReference;
@@ -123,6 +124,8 @@ public abstract class Message implements Part, Body
public abstract String[] getHeader(String name) throws MessagingException;
+ public abstract Set getHeaderNames();
+
public abstract void removeHeader(String name) throws MessagingException;
public abstract void setBody(Body body) throws MessagingException;
diff --git a/src/com/fsck/k9/mail/internet/MimeHeader.java b/src/com/fsck/k9/mail/internet/MimeHeader.java
index 0fdbff0f1..4c66f642f 100644
--- a/src/com/fsck/k9/mail/internet/MimeHeader.java
+++ b/src/com/fsck/k9/mail/internet/MimeHeader.java
@@ -9,8 +9,7 @@ import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.*;
public class MimeHeader
{
@@ -69,9 +68,9 @@ public class MimeHeader
addHeader(name, value);
}
- public List getHeaderNames()
+ public Set getHeaderNames()
{
- ArrayList names = new ArrayList();
+ Set names = new HashSet();
for (Field field : mFields)
{
names.add(field.name);
diff --git a/src/com/fsck/k9/mail/internet/MimeMessage.java b/src/com/fsck/k9/mail/internet/MimeMessage.java
index 4b32a6b2d..74062980a 100644
--- a/src/com/fsck/k9/mail/internet/MimeMessage.java
+++ b/src/com/fsck/k9/mail/internet/MimeMessage.java
@@ -459,7 +459,8 @@ public class MimeMessage extends Message
mHeader.removeHeader(name);
}
- public List getHeaderNames()
+ @Override
+ public Set getHeaderNames()
{
return mHeader.getHeaderNames();
}
diff --git a/src/com/fsck/k9/mail/store/LocalStore.java b/src/com/fsck/k9/mail/store/LocalStore.java
index 65430dccd..5b9b96ffe 100644
--- a/src/com/fsck/k9/mail/store/LocalStore.java
+++ b/src/com/fsck/k9/mail/store/LocalStore.java
@@ -1441,6 +1441,13 @@ public class LocalStore extends Store implements Serializable
"LocalStore.getMessages(int, int, MessageRetrievalListener) not yet implemented");
}
+ /**
+ * Populate the header fields of the given list of messages by reading
+ * the saved header data from the database.
+ *
+ * @param messages
+ * The messages whose headers should be loaded.
+ */
private void populateHeaders(List messages)
{
Cursor cursor = null;
@@ -1635,6 +1642,11 @@ public class LocalStore extends Store implements Serializable
* assigned and it matches the uid of an existing message then this message will replace the
* old message. It is implemented as a delete/insert. This functionality is used in saving
* of drafts and re-synchronization of updated server messages.
+ *
+ * NOTE that although this method is located in the LocalStore class, it is not guaranteed
+ * that the messages supplied as parameters are actually {@link LocalMessage} instances (in
+ * fact, in most cases, they are not). Therefore, if you want to make local changes only to a
+ * message, retrieve the appropriate local message instance first (if it already exists).
*/
@Override
public void appendMessages(Message[] messages) throws MessagingException
@@ -1647,6 +1659,11 @@ public class LocalStore extends Store implements Serializable
* assigned and it matches the uid of an existing message then this message will replace the
* old message. It is implemented as a delete/insert. This functionality is used in saving
* of drafts and re-synchronization of updated server messages.
+ *
+ * NOTE that although this method is located in the LocalStore class, it is not guaranteed
+ * that the messages supplied as parameters are actually {@link LocalMessage} instances (in
+ * fact, in most cases, they are not). Therefore, if you want to make local changes only to a
+ * message, retrieve the appropriate local message instance first (if it already exists).
*/
private void appendMessages(Message[] messages, boolean copy) throws MessagingException
{
@@ -1863,12 +1880,19 @@ public class LocalStore extends Store implements Serializable
}
}
- private void saveHeaders(long id, MimeMessage message)
+ /**
+ * Save the headers of the given message. Note that the message is not
+ * necessarily a {@link LocalMessage} instance.
+ */
+ private void saveHeaders(long id, MimeMessage message) throws MessagingException
{
+ boolean saveAllHeaders = mAccount.isSaveAllHeaders();
+ boolean gotAdditionalHeaders = false;
+
deleteHeaders(id);
for (String name : message.getHeaderNames())
{
- if (HEADERS_TO_SAVE.contains(name))
+ if (saveAllHeaders || HEADERS_TO_SAVE.contains(name))
{
String[] values = message.getHeader(name);
for (String value : values)
@@ -1880,6 +1904,22 @@ public class LocalStore extends Store implements Serializable
mDb.insert("headers", "name", cv);
}
}
+ else
+ {
+ gotAdditionalHeaders = true;
+ }
+ }
+
+ if (!gotAdditionalHeaders)
+ {
+ // Remember that all headers for this message have been saved, so it is
+ // not necessary to download them again in case the user wants to see all headers.
+ List appendedFlags = new ArrayList();
+ appendedFlags.addAll(Arrays.asList(message.getFlags()));
+ appendedFlags.add(Flag.X_GOT_ALL_HEADERS);
+
+ mDb.execSQL("UPDATE messages " + "SET flags = ? " + " WHERE id = ?", new Object[]
+ { Utility.combine(appendedFlags.toArray(), ',').toUpperCase(), id } );
}
}
@@ -2698,9 +2738,7 @@ public class LocalStore extends Store implements Serializable
public void addHeader(String name, String value)
{
if (!mHeadersLoaded)
- {
loadHeaders();
- }
super.addHeader(name, value);
}
@@ -2717,7 +2755,6 @@ public class LocalStore extends Store implements Serializable
{
if (!mHeadersLoaded)
loadHeaders();
-
return super.getHeader(name);
}
@@ -2729,8 +2766,12 @@ public class LocalStore extends Store implements Serializable
super.removeHeader(name);
}
-
-
+ @Override
+ public Set getHeaderNames() {
+ if (!mHeadersLoaded)
+ loadHeaders();
+ return super.getHeaderNames();
+ }
}
public class LocalAttachmentBodyPart extends MimeBodyPart