1
0
mirror of https://github.com/moparisthebest/k-9 synced 2025-01-10 05:08:18 -05:00

Merge branch 'master' into ms-eas

This commit is contained in:
Kris Wong 2011-11-19 10:10:49 -05:00
commit 825c11f688
52 changed files with 1938 additions and 1166 deletions

View File

@ -8,5 +8,6 @@
<classpathentry kind="lib" path="libs/commons-io-2.0.1.jar"/>
<classpathentry kind="lib" path="libs/jzlib-1.0.7.jar"/>
<classpathentry kind="lib" path="libs/jutf7-1.0.1-SNAPSHOT.jar"/>
<classpathentry kind="lib" path="libs/htmlcleaner-2.2.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>

View File

@ -6,6 +6,7 @@ LOCAL_STATIC_JAVA_LIBRARIES += libdom
LOCAL_STATIC_JAVA_LIBRARIES += libio
LOCAL_STATIC_JAVA_LIBRARIES += libjutf
LOCAL_STATIC_JAVA_LIBRARIES += libjzlib
LOCAL_STATIC_JAVA_LIBRARIES += libhtmlcleaner
LOCAL_MODULE_TAGS := eng
@ -24,6 +25,7 @@ LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES += libdom:libs/apache-mime4j-dom-0.7-SNAPSH
LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES += libio:libs/commons-io-2.0.1.jar
LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES += libjutf:libs/jutf7-1.0.1-SNAPSHOT.jar
LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES += libjzlib:libs/jzlib-1.0.7.jar
LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES += libhtmlcleaner:libs/htmlcleaner-2.2.jar
include $(BUILD_MULTI_PREBUILT)

35
HTMLCLEANER_LICENSE Normal file
View File

@ -0,0 +1,35 @@
HtmlCleaner is distributed under BSD License. It gives the freedom for
anyone to use, explore, modify, and distribute HtmlCleaner, but without any
warranty.
Copyright (c) 2006-2011, HtmlCleaner team.
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* The name of HtmlCleaner may not be used to endorse or promote
products derived from this software without specific prior
written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -77,6 +77,18 @@
<SOURCES />
</library>
</orderEntry>
<orderEntry type="module-library">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/libs/htmlcleaner-2.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$MODULE_DIR$/libs/htmlcleaner-2.2-all.zip!/HtmlCleaner/src/main/java" />
<root url="jar://$MODULE_DIR$/libs/htmlcleaner-2.2-all.zip!/HtmlCleaner/src/test/java" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library" scope="TEST">
<library>
<CLASSES>

Binary file not shown.

BIN
libs/htmlcleaner-2.2.jar Normal file

Binary file not shown.

View File

@ -340,6 +340,7 @@ K-9 Mail セットアップにようこそ。\nK-9 は標準のAndroidメール
<string name="global_settings_confirm_actions_summary">選択した動作を実行するときに常にダイアログを表示する</string>
<string name="global_settings_confirm_action_archive">アーカイブ</string>
<string name="global_settings_confirm_action_delete">削除(メッセージ表示画面のみ)</string>
<string name="global_settings_confirm_action_delete_starred">スター付きメッセージの削除 (メッセージ表示画面のみ)</string>
<string name="global_settings_confirm_action_spam">迷惑メール</string>
<string name="global_settings_confirm_action_mark_all_as_read">すべて既読にする</string>
<string name="global_settings_confirm_action_send">送信</string>
@ -564,9 +565,13 @@ K-9 Mail セットアップにようこそ。\nK-9 は標準のAndroidメール
<string name="account_settings_reply_after_quote_label">引用テキストの後に返信を書く</string>
<string name="account_settings_reply_after_quote_summary">返信するときに元のメッセージを返信の上に表示</string>
<string name="account_settings_strip_signature_label">引用から署名を除く</string>
<string name="account_settings_strip_signature_summary">メッセージの返信時に引用文にある署名を取り除く</string>
<string name="account_settings_message_format_label">メッセージの形式</string>
<string name="account_settings_message_format_text">テキスト(画像や書式は取り除く)</string>
<string name="account_settings_message_format_html">HTML (画像や書式を保持)</string>
<string name="account_settings_message_format_auto">自動 (HTMLメールに対する返信でなければテキスト)</string>
<string name="account_settings_message_read_receipt_label">開封確認</string>
<string name="account_settings_message_read_receipt_summary">常に開封確認を要求する</string>

File diff suppressed because it is too large Load Diff

View File

@ -677,11 +677,13 @@
<string-array name="account_settings_message_format_entries">
<item>@string/account_settings_message_format_text</item>
<item>@string/account_settings_message_format_html</item>
<item>@string/account_settings_message_format_auto</item>
</string-array>
<string-array name="account_settings_message_format_values">
<item>TEXT</item>
<item>HTML</item>
<item>AUTO</item>
</string-array>
</resources>

View File

@ -5,6 +5,7 @@
<string name="app_authors">Google, The K-9 Dog Walkers.</string>
<string name="app_copyright_fmt">Copyright 2008-<xliff:g>%s</xliff:g> The K-9 Dog Walkers. Portions Copyright 2006-<xliff:g>%s</xliff:g> the Android Open Source Project.</string>
<string name="app_license">Licensed under the Apache License, Version 2.0.</string>
<string name="app_htmlcleaner_license">&lt;p>HtmlCleaner is distributed under BSD License. It gives the freedom for anyone to use, explore, modify, and distribute HtmlCleaner, but without any warranty.&lt;/p>&lt;p>Copyright (c) 2006-2011, HtmlCleaner team.&lt;br>All rights reserved.&lt;/p>&lt;p>Redistribution and use of this software in source and binary forms, with or without modification, are permitted provided that the following conditions are met:&lt;/p>&lt;p>&lt;ul>&lt;li>Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.&lt;/li>&lt;li>Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.&lt;/li>&lt;li>The name of HtmlCleaner may not be used to endorse or promote products derived from this software without specific prior written permission.&lt;/li>&lt;/ul>&lt;/p>&lt;p>THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.&lt;/p></string>
<string name="app_authors_fmt">Authors: <xliff:g id="app_authors">%s</xliff:g></string>
<string name="app_revision_fmt">Revision Information: <xliff:g id="app_revision_url">%s</xliff:g></string>
<string name="app_libraries">We\'re using the following third-party libraries: <xliff:g id="app_libraries_list">%s</xliff:g></string>
@ -341,6 +342,7 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin
<string name="global_settings_confirm_actions_summary">Show a dialog whenever you perform selected actions</string>
<string name="global_settings_confirm_action_archive">Archive</string>
<string name="global_settings_confirm_action_delete">Delete (message view only)</string>
<string name="global_settings_confirm_action_delete_starred">Delete Starred (message view only)</string>
<string name="global_settings_confirm_action_spam">Spam</string>
<string name="global_settings_confirm_action_mark_all_as_read">Mark all as read</string>
<string name="global_settings_confirm_action_send">Send</string>
@ -567,9 +569,13 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin
<string name="account_settings_reply_after_quote_label">Reply after quoted text</string>
<string name="account_settings_reply_after_quote_summary">When replying to messages, the original message will appear above your reply.</string>
<string name="account_settings_strip_signature_label">Strip signature from quoted reply</string>
<string name="account_settings_strip_signature_summary">When replying to messages, the signature of the quoted text will be stripped</string>
<string name="account_settings_message_format_label">Message Format</string>
<string name="account_settings_message_format_text">Plain Text (images and formatting will be removed)</string>
<string name="account_settings_message_format_html">HTML (images and formatting are preserved)</string>
<string name="account_settings_message_format_auto">Automatic (plain text unless replying to an HTML message)</string>
<string name="account_settings_message_read_receipt_label">Read receipt</string>
<string name="account_settings_message_read_receipt_summary">Always request a read receipt</string>

View File

@ -259,6 +259,13 @@
android:defaultValue="false"
android:summary="@string/account_settings_reply_after_quote_summary" />
<CheckBoxPreference
android:persistent="false"
android:key="strip_signature"
android:title="@string/account_settings_strip_signature_label"
android:defaultValue="true"
android:summary="@string/account_settings_strip_signature_summary" />
<EditTextPreference
android:persistent="false"
android:key="account_quote_prefix"

View File

@ -60,11 +60,13 @@ public class Account implements BaseAccount {
private static final String[] networkTypes = { TYPE_WIFI, TYPE_MOBILE, TYPE_OTHER };
public static final MessageFormat DEFAULT_MESSAGE_FORMAT = MessageFormat.HTML;
public static final boolean DEFAULT_MESSAGE_FORMAT_AUTO = false;
public static final boolean DEFAULT_MESSAGE_READ_RECEIPT = false;
public static final QuoteStyle DEFAULT_QUOTE_STYLE = QuoteStyle.PREFIX;
public static final String DEFAULT_QUOTE_PREFIX = ">";
public static final boolean DEFAULT_QUOTED_TEXT_SHOWN = true;
public static final boolean DEFAULT_REPLY_AFTER_QUOTE = false;
public static final boolean DEFAULT_STRIP_SIGNATURE = true;
public static final String ACCOUNT_DESCRIPTION_KEY = "description";
public static final String STORE_URI_KEY = "storeUri";
@ -137,11 +139,13 @@ public class Account implements BaseAccount {
// current set of fetched messages
private boolean mRingNotified;
private MessageFormat mMessageFormat;
private boolean mMessageFormatAuto;
private boolean mMessageReadReceipt;
private QuoteStyle mQuoteStyle;
private String mQuotePrefix;
private boolean mDefaultQuotedTextShown;
private boolean mReplyAfterQuote;
private boolean mStripSignature;
private boolean mSyncRemoteDeletions;
private String mCryptoApp;
private boolean mCryptoAutoSignature;
@ -191,7 +195,7 @@ public class Account implements BaseAccount {
}
public enum MessageFormat {
TEXT, HTML
TEXT, HTML, AUTO
}
protected Account(Context context) {
@ -226,11 +230,13 @@ public class Account implements BaseAccount {
maximumPolledMessageAge = -1;
maximumAutoDownloadMessageSize = 32768;
mMessageFormat = DEFAULT_MESSAGE_FORMAT;
mMessageFormatAuto = DEFAULT_MESSAGE_FORMAT_AUTO;
mMessageReadReceipt = DEFAULT_MESSAGE_READ_RECEIPT;
mQuoteStyle = DEFAULT_QUOTE_STYLE;
mQuotePrefix = DEFAULT_QUOTE_PREFIX;
mDefaultQuotedTextShown = DEFAULT_QUOTED_TEXT_SHOWN;
mReplyAfterQuote = DEFAULT_REPLY_AFTER_QUOTE;
mStripSignature = DEFAULT_STRIP_SIGNATURE;
mSyncRemoteDeletions = true;
mCryptoApp = Apg.NAME;
mCryptoAutoSignature = false;
@ -302,11 +308,16 @@ public class Account implements BaseAccount {
maximumPolledMessageAge = prefs.getInt(mUuid + ".maximumPolledMessageAge", -1);
maximumAutoDownloadMessageSize = prefs.getInt(mUuid + ".maximumAutoDownloadMessageSize", 32768);
mMessageFormat = MessageFormat.valueOf(prefs.getString(mUuid + ".messageFormat", DEFAULT_MESSAGE_FORMAT.name()));
mMessageFormatAuto = prefs.getBoolean(mUuid + ".messageFormatAuto", DEFAULT_MESSAGE_FORMAT_AUTO);
if (mMessageFormatAuto && mMessageFormat == MessageFormat.TEXT) {
mMessageFormat = MessageFormat.AUTO;
}
mMessageReadReceipt = prefs.getBoolean(mUuid + ".messageReadReceipt", DEFAULT_MESSAGE_READ_RECEIPT);
mQuoteStyle = QuoteStyle.valueOf(prefs.getString(mUuid + ".quoteStyle", DEFAULT_QUOTE_STYLE.name()));
mQuotePrefix = prefs.getString(mUuid + ".quotePrefix", DEFAULT_QUOTE_PREFIX);
mDefaultQuotedTextShown = prefs.getBoolean(mUuid + ".defaultQuotedTextShown", DEFAULT_QUOTED_TEXT_SHOWN);
mReplyAfterQuote = prefs.getBoolean(mUuid + ".replyAfterQuote", DEFAULT_REPLY_AFTER_QUOTE);
mStripSignature = prefs.getBoolean(mUuid + ".stripSignature", DEFAULT_STRIP_SIGNATURE);
for (String type : networkTypes) {
Boolean useCompression = prefs.getBoolean(mUuid + ".useCompression." + type,
true);
@ -461,10 +472,12 @@ public class Account implements BaseAccount {
editor.remove(mUuid + ".subscribedFoldersOnly");
editor.remove(mUuid + ".maximumPolledMessageAge");
editor.remove(mUuid + ".maximumAutoDownloadMessageSize");
editor.remove(mUuid + ".messageFormatAuto");
editor.remove(mUuid + ".quoteStyle");
editor.remove(mUuid + ".quotePrefix");
editor.remove(mUuid + ".showPicturesEnum");
editor.remove(mUuid + ".replyAfterQuote");
editor.remove(mUuid + ".stripSignature");
editor.remove(mUuid + ".cryptoApp");
editor.remove(mUuid + ".cryptoAutoSignature");
editor.remove(mUuid + ".enabled");
@ -612,12 +625,22 @@ public class Account implements BaseAccount {
editor.putBoolean(mUuid + ".subscribedFoldersOnly", subscribedFoldersOnly);
editor.putInt(mUuid + ".maximumPolledMessageAge", maximumPolledMessageAge);
editor.putInt(mUuid + ".maximumAutoDownloadMessageSize", maximumAutoDownloadMessageSize);
editor.putString(mUuid + ".messageFormat", mMessageFormat.name());
if (MessageFormat.AUTO.equals(mMessageFormat)) {
// saving MessageFormat.AUTO as is to the database will cause downgrades to crash on
// startup, so we save as MessageFormat.TEXT instead with a separate flag for auto.
editor.putString(mUuid + ".messageFormat", Account.MessageFormat.TEXT.name());
mMessageFormatAuto = true;
} else {
editor.putString(mUuid + ".messageFormat", mMessageFormat.name());
mMessageFormatAuto = false;
}
editor.putBoolean(mUuid + ".messageFormatAuto", mMessageFormatAuto);
editor.putBoolean(mUuid + ".messageReadReceipt", mMessageReadReceipt);
editor.putString(mUuid + ".quoteStyle", mQuoteStyle.name());
editor.putString(mUuid + ".quotePrefix", mQuotePrefix);
editor.putBoolean(mUuid + ".defaultQuotedTextShown", mDefaultQuotedTextShown);
editor.putBoolean(mUuid + ".replyAfterQuote", mReplyAfterQuote);
editor.putBoolean(mUuid + ".stripSignature", mStripSignature);
editor.putString(mUuid + ".cryptoApp", mCryptoApp);
editor.putBoolean(mUuid + ".cryptoAutoSignature", mCryptoAutoSignature);
editor.putBoolean(mUuid + ".enabled", mEnabled);
@ -1409,6 +1432,14 @@ public class Account implements BaseAccount {
mReplyAfterQuote = replyAfterQuote;
}
public synchronized boolean isStripSignature() {
return mStripSignature;
}
public synchronized void setStripSignature(boolean stripSignature) {
mStripSignature = stripSignature;
}
public boolean getEnableMoveButtons() {
return mEnableMoveButtons;
}

View File

@ -2,7 +2,6 @@
package com.fsck.k9;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
@ -23,7 +22,6 @@ import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.text.format.Time;
import android.text.style.AbsoluteSizeSpan;
import android.util.Log;
import com.fsck.k9.activity.MessageCompose;
@ -68,11 +66,6 @@ public class K9 extends Application {
*/
private static List<ApplicationAware> observers = new ArrayList<ApplicationAware>();
/**
* @see K9#createAbsoluteSizeSpan(int)
*/
private static Constructor<AbsoluteSizeSpan> sAbsoluteSizeSpanConstructor;
public enum BACKGROUND_OPS {
WHEN_CHECKED, ALWAYS, NEVER, WHEN_CHECKED_AUTO_SYNC
}
@ -153,6 +146,7 @@ public class K9 extends Application {
private static boolean mAnimations = true;
private static boolean mConfirmDelete = false;
private static boolean mConfirmDeleteStarred = false;
private static boolean mConfirmSpam = false;
private static boolean mConfirmMarkAllAsRead = true;
private static boolean mKeyguardPrivacy = false;
@ -443,6 +437,7 @@ public class K9 extends Application {
editor.putBoolean("useGalleryBugWorkaround", useGalleryBugWorkaround);
editor.putBoolean("confirmDelete", mConfirmDelete);
editor.putBoolean("confirmDeleteStarred", mConfirmDeleteStarred);
editor.putBoolean("confirmSpam", mConfirmSpam);
editor.putBoolean("confirmMarkAllAsRead", mConfirmMarkAllAsRead);
@ -571,6 +566,7 @@ public class K9 extends Application {
useGalleryBugWorkaround = sprefs.getBoolean("useGalleryBugWorkaround", K9.isGalleryBuggy());
mConfirmDelete = sprefs.getBoolean("confirmDelete", false);
mConfirmDeleteStarred = sprefs.getBoolean("confirmDeleteStarred", false);
mConfirmSpam = sprefs.getBoolean("confirmSpam", false);
mConfirmMarkAllAsRead = sprefs.getBoolean("confirmMarkAllAsRead", true);
@ -951,6 +947,14 @@ public class K9 extends Application {
mConfirmDelete = confirm;
}
public static boolean confirmDeleteStarred() {
return mConfirmDeleteStarred;
}
public static void setConfirmDeleteStarred(final boolean confirm) {
mConfirmDeleteStarred = confirm;
}
public static boolean confirmSpam() {
return mConfirmSpam;
}
@ -1012,48 +1016,4 @@ public class K9 extends Application {
public static void setAttachmentDefaultPath(String attachmentDefaultPath) {
K9.mAttachmentDefaultPath = attachmentDefaultPath;
}
/**
* Creates an {@link AbsoluteSizeSpan} object.
*
* <p>
* Android versions prior to 2.0 don't support the constructor with two parameters
* ({@link AbsoluteSizeSpan#AbsoluteSizeSpan(int, boolean)}). So we have to perform some
* reflection magic to dynamically load the new constructor on devices that support it.
* For devices with old Android versions we just use the size as pixels (instead of dip).
* </p>
*
* @param size This is used as the {@code size} parameter for the AbsoluteSizeSpan constructor.
* @return a AbsoluteSizeSpan object with the specified text size.
*/
public static AbsoluteSizeSpan createAbsoluteSizeSpan(int size) {
if (Integer.parseInt(android.os.Build.VERSION.SDK) < 5) {
// For Android 1.5/1.6 simply use the constructor with only the size parameter.
// Yes, that will most likely look wrong!
return new AbsoluteSizeSpan(size);
}
if (sAbsoluteSizeSpanConstructor == null) {
try {
sAbsoluteSizeSpanConstructor = AbsoluteSizeSpan.class.getConstructor(int.class, boolean.class);
} catch (Exception e) {
Log.e(K9.LOG_TAG, "Couldn't get the AbsoluteSizeSpan(int, boolean) constructor", e);
// Fallback
return new AbsoluteSizeSpan(size);
}
}
AbsoluteSizeSpan result;
try {
result = sAbsoluteSizeSpanConstructor.newInstance(size, true);
} catch (Exception e) {
Log.e(K9.LOG_TAG, "Couldn't call the AbsoluteSizeSpan(int, boolean) constructor", e);
// Fallback
result = new AbsoluteSizeSpan(size);
}
return result;
}
}

View File

@ -1109,6 +1109,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC
new String[] {"JZlib", "http://www.jcraft.com/jzlib/"},
new String[] {"Commons IO", "http://commons.apache.org/io/"},
new String[] {"Mime4j", "http://james.apache.org/mime4j/"},
new String[] {"HtmlCleaner", "http://htmlcleaner.sourceforge.net/"},
};
private void onAbout() {
@ -1153,7 +1154,9 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC
"<div>TypePad \u7d75\u6587\u5b57\u30a2\u30a4\u30b3\u30f3\u753b\u50cf " +
"(<a href=\"http://typepad.jp/\">Six Apart Ltd</a>) / " +
"<a href=\"http://creativecommons.org/licenses/by/2.1/jp/\">CC BY 2.1</a></div>"))
.append("</p>");
.append("</p><hr/><p>")
.append(getString(R.string.app_htmlcleaner_license));
wv.loadDataWithBaseURL("file:///android_res/drawable/", html.toString(), "text/html", "utf-8", null);
new AlertDialog.Builder(this)

View File

@ -1,7 +1,6 @@
package com.fsck.k9.activity;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
import android.widget.CheckBox;
import android.widget.CompoundButton;
@ -125,12 +124,9 @@ public class EditIdentity extends K9Activity {
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
saveIdentity();
return true;
}
return super.onKeyDown(keyCode, event);
public void onBackPressed() {
saveIdentity();
super.onBackPressed();
}
@Override

View File

@ -356,38 +356,18 @@ public class FolderList extends K9ListActivity {
@Override
public void onBackPressed() {
// This will be called either automatically for you on 2.0
// or later, or by the code above on earlier versions of the
// platform.
if (K9.manageBack()) {
onAccounts();
} else {
// TODO - when we move to android 2.0, uncomment this instead.
// super.onBackPressed()
finish();
super.onBackPressed();
}
}
@Override public boolean onKeyDown(int keyCode, KeyEvent event) {
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
//Shortcuts that work no matter what is selected
if (
// TODO - when we move to android 2.0, uncomment this.
// android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.ECLAIR &&
keyCode == KeyEvent.KEYCODE_BACK
&& event.getRepeatCount() == 0
&& K9.manageBack()) {
// Take care of calling this method on earlier versions of
// the platform where it doesn't exist.
onBackPressed();
return true;
}
switch (keyCode) {
case KeyEvent.KEYCODE_Q:
//case KeyEvent.KEYCODE_BACK:
{
onAccounts();
return true;

View File

@ -1,153 +1,161 @@
package com.fsck.k9.activity;
import java.io.Serializable;
/**
* <p>Represents an HTML document with an insertion point for placing a reply. The quoted
* document may have been modified to make it suitable for insertion. The modified quoted
* document should be used in place of the original document.</p>
*
* <p>Changes to the user-generated inserted content should be done with {@link
* #setUserContent(String)}.</p>
*
* TODO: This container should also have a text part, along with its insertion point. Or maybe a generic InsertableContent and maintain one each for Html and Text?
*/
class InsertableHtmlContent implements Serializable {
private static final long serialVersionUID = 2397327034L;
// Default to a headerInsertionPoint at the beginning of the message.
private int headerInsertionPoint = 0;
private int footerInsertionPoint = 0;
// Quoted message, if any. headerInsertionPoint refers to a position in this string.
private StringBuilder quotedContent = new StringBuilder();
// User content (typically their reply or comments on a forward)
private StringBuilder userContent = new StringBuilder();
// Where to insert the content. Default to top posting.
private InsertionLocation insertionLocation = InsertionLocation.BEFORE_QUOTE;
/**
* Defines where user content should be inserted, either before or after quoted content.
*/
public enum InsertionLocation {
BEFORE_QUOTE, AFTER_QUOTE
}
public void setHeaderInsertionPoint(int headerInsertionPoint) {
this.headerInsertionPoint = headerInsertionPoint;
}
public void setFooterInsertionPoint(int footerInsertionPoint) {
this.footerInsertionPoint = footerInsertionPoint;
}
/**
* Get the quoted content.
* @return Quoted content.
*/
public String getQuotedContent() {
return quotedContent.toString();
}
/**
* Set the quoted content. The insertion point should be set against this content.
* @param content
*/
public void setQuotedContent(StringBuilder content) {
this.quotedContent = content;
}
/**
* <p>Insert something into the quoted content header. This is typically used for inserting
* reply/forward headers into the quoted content rather than inserting the user-generated reply
* content.</p>
*
* <p>Subsequent calls to {@link #insertIntoQuotedHeader(String)} will <b>prepend</b> text onto any
* existing header and quoted content.</p>
* @param content Content to add.
*/
public void insertIntoQuotedHeader(final String content) {
quotedContent.insert(headerInsertionPoint, content);
// Update the location of the footer insertion point.
footerInsertionPoint += content.length();
}
/**
* <p>Insert something into the quoted content footer. This is typically used for inserting closing
* tags of reply/forward headers rather than inserting the user-generated reply content.</p>
*
* <p>Subsequent calls to {@link #insertIntoQuotedFooter(String)} will <b>append</b> text onto any
* existing footer and quoted content.</p>
* @param content Content to add.
*/
public void insertIntoQuotedFooter(final String content) {
quotedContent.insert(footerInsertionPoint, content);
// Update the location of the footer insertion point to the end of the inserted content.
footerInsertionPoint += content.length();
}
/**
* Remove all quoted content.
*/
public void clearQuotedContent() {
quotedContent.setLength(0);
footerInsertionPoint = 0;
headerInsertionPoint = 0;
}
/**
* Set the inserted content to the specified content. Replaces anything currently in the
* inserted content buffer.
* @param content
*/
public void setUserContent(final String content) {
userContent = new StringBuilder(content);
}
/**
* Configure where user content should be inserted, either before or after the quoted content.
* @param insertionLocation Where to insert user content.
*/
public void setInsertionLocation(final InsertionLocation insertionLocation) {
this.insertionLocation = insertionLocation;
}
/**
* Fetch the insertion point based upon the quote style.
* @return Insertion point
*/
public int getInsertionPoint() {
if (insertionLocation == InsertionLocation.BEFORE_QUOTE) {
return headerInsertionPoint;
} else {
return footerInsertionPoint;
}
}
/**
* Build the composed string with the inserted and original content.
* @return Composed string.
*/
@Override
public String toString() {
final int insertionPoint = getInsertionPoint();
// Inserting and deleting was twice as fast as instantiating a new StringBuilder and
// using substring() to build the new pieces.
String result = quotedContent.insert(insertionPoint, userContent.toString()).toString();
quotedContent.delete(insertionPoint, insertionPoint + userContent.length());
return result;
}
/**
* Return debugging information for this container.
* @return Debug string.
*/
public String toDebugString() {
return "InsertableHtmlContent{" +
"headerInsertionPoint=" + headerInsertionPoint +
", footerInsertionPoint=" + footerInsertionPoint +
", insertionLocation=" + insertionLocation +
", quotedContent=" + quotedContent +
", userContent=" + userContent +
", compiledResult=" + toString() +
'}';
}
}
package com.fsck.k9.activity;
import java.io.Serializable;
/**
* <p>Represents an HTML document with an insertion point for placing a reply. The quoted
* document may have been modified to make it suitable for insertion. The modified quoted
* document should be used in place of the original document.</p>
*
* <p>Changes to the user-generated inserted content should be done with {@link
* #setUserContent(String)}.</p>
*
* TODO: This container should also have a text part, along with its insertion point. Or maybe a generic InsertableContent and maintain one each for Html and Text?
*/
class InsertableHtmlContent implements Serializable {
private static final long serialVersionUID = 2397327034L;
// Default to a headerInsertionPoint at the beginning of the message.
private int headerInsertionPoint = 0;
private int footerInsertionPoint = 0;
// Quoted message, if any. headerInsertionPoint refers to a position in this string.
private StringBuilder quotedContent = new StringBuilder();
// User content (typically their reply or comments on a forward)
private StringBuilder userContent = new StringBuilder();
// Where to insert the content. Default to top posting.
private InsertionLocation insertionLocation = InsertionLocation.BEFORE_QUOTE;
/**
* Defines where user content should be inserted, either before or after quoted content.
*/
public enum InsertionLocation {
BEFORE_QUOTE, AFTER_QUOTE
}
public void setHeaderInsertionPoint(int headerInsertionPoint) {
this.headerInsertionPoint = headerInsertionPoint;
}
public void setFooterInsertionPoint(int footerInsertionPoint) {
this.footerInsertionPoint = footerInsertionPoint;
}
/**
* Get the quoted content.
* @return Quoted content.
*/
public String getQuotedContent() {
return quotedContent.toString();
}
/**
* Set the quoted content. The insertion point should be set against this content.
* @param content
*/
public void setQuotedContent(StringBuilder content) {
this.quotedContent = content;
}
/**
* <p>Insert something into the quoted content header. This is typically used for inserting
* reply/forward headers into the quoted content rather than inserting the user-generated reply
* content.</p>
*
* <p>Subsequent calls to {@link #insertIntoQuotedHeader(String)} will <b>prepend</b> text onto any
* existing header and quoted content.</p>
* @param content Content to add.
*/
public void insertIntoQuotedHeader(final String content) {
quotedContent.insert(headerInsertionPoint, content);
// Update the location of the footer insertion point.
footerInsertionPoint += content.length();
}
/**
* <p>Insert something into the quoted content footer. This is typically used for inserting closing
* tags of reply/forward headers rather than inserting the user-generated reply content.</p>
*
* <p>Subsequent calls to {@link #insertIntoQuotedFooter(String)} will <b>append</b> text onto any
* existing footer and quoted content.</p>
* @param content Content to add.
*/
public void insertIntoQuotedFooter(final String content) {
quotedContent.insert(footerInsertionPoint, content);
// Update the location of the footer insertion point to the end of the inserted content.
footerInsertionPoint += content.length();
}
/**
* Remove all quoted content.
*/
public void clearQuotedContent() {
quotedContent.setLength(0);
footerInsertionPoint = 0;
headerInsertionPoint = 0;
}
/**
* Set the inserted content to the specified content. Replaces anything currently in the
* inserted content buffer.
* @param content
*/
public void setUserContent(final String content) {
userContent = new StringBuilder(content);
}
/**
* Configure where user content should be inserted, either before or after the quoted content.
* @param insertionLocation Where to insert user content.
*/
public void setInsertionLocation(final InsertionLocation insertionLocation) {
this.insertionLocation = insertionLocation;
}
/**
* Fetch the insertion point based upon the quote style.
* @return Insertion point
*/
public int getInsertionPoint() {
if (insertionLocation == InsertionLocation.BEFORE_QUOTE) {
return headerInsertionPoint;
} else {
return footerInsertionPoint;
}
}
/**
* Get the footer insertion point.
* @return Footer insertion point
*/
public int getFooterInsertionPoint() {
return footerInsertionPoint;
}
/**
* Build the composed string with the inserted and original content.
* @return Composed string.
*/
@Override
public String toString() {
final int insertionPoint = getInsertionPoint();
// Inserting and deleting was twice as fast as instantiating a new StringBuilder and
// using substring() to build the new pieces.
String result = quotedContent.insert(insertionPoint, userContent.toString()).toString();
quotedContent.delete(insertionPoint, insertionPoint + userContent.length());
return result;
}
/**
* Return debugging information for this container.
* @return Debug string.
*/
public String toDebugString() {
return "InsertableHtmlContent{" +
"headerInsertionPoint=" + headerInsertionPoint +
", footerInsertionPoint=" + footerInsertionPoint +
", insertionLocation=" + insertionLocation +
", quotedContent=" + quotedContent +
", userContent=" + userContent +
", compiledResult=" + toString() +
'}';
}
}

View File

@ -119,11 +119,9 @@ public class ManageIdentities extends ChooseIdentity {
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
saveIdentities();
}
return super.onKeyDown(keyCode, event);
public void onBackPressed() {
saveIdentities();
super.onBackPressed();
}
private void saveIdentities() {

View File

@ -10,6 +10,7 @@ import java.util.regex.Pattern;
import android.text.*;
import android.webkit.WebViewClient;
import com.fsck.k9.helper.HtmlConverter;
import com.fsck.k9.helper.StringUtils;
import com.fsck.k9.mail.*;
import com.fsck.k9.view.MessageWebView;
import org.apache.james.mime4j.codec.EncoderUtil;
@ -29,7 +30,6 @@ import android.os.Parcelable;
import android.provider.OpenableColumns;
import android.text.util.Rfc822Tokenizer;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
@ -47,6 +47,11 @@ import android.widget.MultiAutoCompleteTextView;
import android.widget.TextView;
import android.widget.Toast;
import org.htmlcleaner.CleanerProperties;
import org.htmlcleaner.HtmlCleaner;
import org.htmlcleaner.SimpleHtmlSerializer;
import org.htmlcleaner.TagNode;
import com.fsck.k9.Account;
import com.fsck.k9.Account.QuoteStyle;
import com.fsck.k9.Account.MessageFormat;
@ -75,6 +80,7 @@ import com.fsck.k9.mail.store.LocalStore.LocalAttachmentBody;
public class MessageCompose extends K9Activity implements OnClickListener, OnFocusChangeListener {
private static final int DIALOG_SAVE_OR_DISCARD_DRAFT_MESSAGE = 1;
private static final String ACTION_COMPOSE = "com.fsck.k9.intent.action.COMPOSE";
private static final String ACTION_REPLY = "com.fsck.k9.intent.action.REPLY";
private static final String ACTION_REPLY_ALL = "com.fsck.k9.intent.action.REPLY_ALL";
private static final String ACTION_FORWARD = "com.fsck.k9.intent.action.FORWARD";
@ -207,6 +213,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
private boolean mSourceProcessed = false;
private MessageFormat mMessageFormat;
private QuoteStyle mQuoteStyle;
private boolean mDraftNeedsSaving = false;
private boolean mPreventDraftSaving = false;
@ -280,6 +287,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
}
Intent i = new Intent(context, MessageCompose.class);
i.putExtra(EXTRA_ACCOUNT, account.getUuid());
i.setAction(ACTION_COMPOSE);
context.startActivity(i);
}
@ -533,7 +541,18 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
}
mMessageFormat = mAccount.getMessageFormat();
if (mMessageFormat == MessageFormat.AUTO) {
if (ACTION_COMPOSE.equals(action)) {
mMessageFormat = MessageFormat.TEXT;
} else if (mSourceMessageBody != null) {
// mSourceMessageBody is set to something when replying to and forwarding decrypted
// messages, so we set the format to plain text.
mMessageFormat = MessageFormat.TEXT;
}
}
mReadReceipt = mAccount.isMessageReadReceiptAlways();
mQuoteStyle = mAccount.getQuoteStyle();
if (!mSourceMessageProcessed) {
updateFrom();
@ -663,9 +682,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
* it wrong! So go fix your program or get AOSP to change the documentation.
*/
}
//TODO: Use constant Intent.ACTION_SEND_MULTIPLE once we drop Android 1.5 support
else if (Intent.ACTION_SEND.equals(action) ||
"android.intent.action.SEND_MULTIPLE".equals(action)) {
else if (Intent.ACTION_SEND.equals(action) || Intent.ACTION_SEND_MULTIPLE.equals(action)) {
/*
* Note: Here we allow a slight deviation from the documentated behavior.
* EXTRA_TEXT is used as message body (if available) regardless of the MIME
@ -840,13 +857,12 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
mMessageFormat = (MessageFormat) savedInstanceState
.getSerializable(STATE_KEY_MESSAGE_FORMAT);
mReadReceipt = savedInstanceState
.getBoolean(STATE_KEY_READ_RECEIPT);
.getBoolean(STATE_KEY_READ_RECEIPT);
mCcWrapper.setVisibility(savedInstanceState.getBoolean(STATE_KEY_CC_SHOWN) ? View.VISIBLE
: View.GONE);
mBccWrapper.setVisibility(savedInstanceState
.getBoolean(STATE_KEY_BCC_SHOWN) ? View.VISIBLE : View.GONE);
showOrHideQuotedText((QuotedTextMode)savedInstanceState.getSerializable(STATE_KEY_QUOTED_TEXT_MODE));
if (mQuotedTextMode != QuotedTextMode.NONE && mMessageFormat == MessageFormat.HTML) {
mQuotedHtmlContent = (InsertableHtmlContent) savedInstanceState.getSerializable(STATE_KEY_HTML_QUOTE);
if (mQuotedHtmlContent != null && mQuotedHtmlContent.getQuotedContent() != null) {
@ -907,6 +923,17 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
* @param isDraft If we should build a message that will be saved as a draft (as opposed to sent).
*/
private TextBody buildText(boolean isDraft) {
return buildText(isDraft, mMessageFormat);
}
/*
* Build the Body that will contain the text of the message. We'll decide where to
* include it later. Draft messages are treated somewhat differently in that signatures are not
* appended and HTML separators between composed text and quoted text are not added.
* @param isDraft If we should build a message that will be saved as a draft (as opposed to sent).
* @param messageFormat Set MessageFormat to build.
*/
private TextBody buildText(boolean isDraft, MessageFormat messageFormat) {
boolean replyAfterQuote = false;
String action = getIntent().getAction();
if (mAccount.isReplyAfterQuote() &&
@ -926,10 +953,13 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
// Handle HTML separate from the rest of the text content. HTML mode doesn't allow signature after the quoted
// text, nor does it allow reply after quote. Users who want that functionality will need to stick with text
// mode.
if (mMessageFormat == MessageFormat.HTML) {
// Add the signature.
if (messageFormat == MessageFormat.HTML) {
// Place the signature immediately after the reply.
if (!isDraft) {
text = appendSignature(text);
if (mQuoteStyle == QuoteStyle.HEADER || replyAfterQuote || mAccount.isSignatureBeforeQuotedText()) {
Log.d("ASH", "appending signature after new content");
text = appendSignature(text);
}
}
text = HtmlConverter.textToHtmlFragment(text);
// Insert it into the existing content object.
@ -943,7 +973,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
// location. We only add the extra separators when we're sending, that way when we
// load a draft, we don't have to know the length of the separators to remove them
// before editing.
if (mAccount.getQuoteStyle() == QuoteStyle.PREFIX && replyAfterQuote) {
if (mQuoteStyle == QuoteStyle.PREFIX && replyAfterQuote) {
mQuotedHtmlContent.setInsertionLocation(InsertableHtmlContent.InsertionLocation.AFTER_QUOTE);
if (!isDraft) {
text = "<br clear=\"all\">" + text;
@ -955,6 +985,13 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
}
}
// Place signature immediately after quote.
if (!isDraft) {
if (mQuoteStyle == QuoteStyle.PREFIX && !replyAfterQuote && !mAccount.isSignatureBeforeQuotedText()) {
mQuotedHtmlContent.insertIntoQuotedFooter(getSignatureHtml());
}
}
mQuotedHtmlContent.setUserContent(text);
// All done. Build the body.
@ -970,20 +1007,21 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
body.setComposedMessageOffset(0);
return body;
}
} else if (mMessageFormat == MessageFormat.TEXT) {
} else if (messageFormat == MessageFormat.TEXT) {
// Capture composed message length before we start attaching quoted parts and signatures.
Integer composedMessageLength = text.length();
Integer composedMessageOffset = 0;
// Placing the signature before the quoted text does not make sense if replyAfterQuote is true.
if (!isDraft) {
if (!replyAfterQuote && mAccount.isSignatureBeforeQuotedText()) {
if (mQuoteStyle == QuoteStyle.HEADER ||
(!replyAfterQuote && mAccount.isSignatureBeforeQuotedText())) {
text = appendSignature(text);
}
}
if (saveQuotedText) {
if (replyAfterQuote) {
if (mQuoteStyle == QuoteStyle.PREFIX && replyAfterQuote) {
composedMessageOffset = mQuotedText.getText().toString().length() + "\n".length();
text = mQuotedText.getText().toString() + "\n" + text;
} else {
@ -994,7 +1032,8 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
// Note: If user has selected reply after quote AND signature before quote, ignore the
// latter setting and append the signature at the end.
if (!isDraft) {
if (replyAfterQuote || !mAccount.isSignatureBeforeQuotedText()) {
if (mQuoteStyle == QuoteStyle.PREFIX &&
(replyAfterQuote || !mAccount.isSignatureBeforeQuotedText())) {
text = appendSignature(text);
}
}
@ -1002,7 +1041,6 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
TextBody body = new TextBody(text);
body.setComposedMessageLength(composedMessageLength);
body.setComposedMessageOffset(composedMessageOffset);
return body;
} else {
// Shouldn't happen.
@ -1048,7 +1086,8 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
}
// Build the body.
// TODO FIXME - body can be either an HTML or Text part, depending on whether we're in HTML mode or not. Should probably fix this so we don't mix up html and text parts.
// TODO FIXME - body can be either an HTML or Text part, depending on whether we're in
// HTML mode or not. Should probably fix this so we don't mix up html and text parts.
TextBody body = null;
if (mPgpData.getEncryptedData() != null) {
String text = mPgpData.getEncryptedData();
@ -1057,6 +1096,9 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
body = buildText(isDraft);
}
// text/plain part when mMessageFormat == MessageFormat.HTML
TextBody bodyPlain = null;
final boolean hasAttachments = mAttachments.getChildCount() > 0;
if (mMessageFormat == MessageFormat.HTML) {
@ -1066,7 +1108,8 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
MimeMultipart composedMimeMessage = new MimeMultipart();
composedMimeMessage.setSubType("alternative"); // Let the receiver select either the text or the HTML part.
composedMimeMessage.addBodyPart(new MimeBodyPart(body, "text/html"));
composedMimeMessage.addBodyPart(new MimeBodyPart(new TextBody(HtmlConverter.htmlToText(body.getText())), "text/plain"));
bodyPlain = buildText(isDraft, MessageFormat.TEXT);
composedMimeMessage.addBodyPart(new MimeBodyPart(bodyPlain, "text/plain"));
if (hasAttachments) {
// If we're HTML and have attachments, we have a MimeMultipart container to hold the
@ -1097,7 +1140,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
// If this is a draft, add metadata for thawing.
if (isDraft) {
// Add the identity to the message.
message.addHeader(K9.IDENTITY_HEADER, buildIdentityHeader(body));
message.addHeader(K9.IDENTITY_HEADER, buildIdentityHeader(body, bodyPlain));
}
return message;
@ -1155,6 +1198,9 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
private enum IdentityField {
LENGTH("l"),
OFFSET("o"),
FOOTER_OFFSET("fo"),
PLAIN_LENGTH("pl"),
PLAIN_OFFSET("po"),
MESSAGE_FORMAT("f"),
MESSAGE_READ_RECEIPT("r"),
SIGNATURE("s"),
@ -1163,7 +1209,8 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
// TODO - store a reference to the message being replied so we can mark it at the time of send.
ORIGINAL_MESSAGE("m"),
CURSOR_POSITION("p"), // Where in the message your cursor was when you saved.
QUOTED_TEXT_MODE("q");
QUOTED_TEXT_MODE("q"),
QUOTE_STYLE("qs");
private final String value;
@ -1181,7 +1228,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
* @return
*/
public static IdentityField[] getIntegerFields() {
return new IdentityField[] { LENGTH, OFFSET };
return new IdentityField[] { LENGTH, OFFSET, FOOTER_OFFSET, PLAIN_LENGTH, PLAIN_OFFSET };
}
}
@ -1199,6 +1246,20 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
* @return Identity string.
*/
private String buildIdentityHeader(final TextBody body) {
return buildIdentityHeader(body, null);
}
/**
* Build the identity header string. This string contains metadata about a draft message to be
* used upon loading a draft for composition. This should be generated at the time of saving a
* draft.<br>
* <br>
* This is a URL-encoded key/value pair string. The list of possible values are in {@link IdentityField}.
* @param body {@link TextBody} to analyze for body length and offset.
* @param bodyPlain {@link TextBody} to analyze for body length and offset. May be null.
* @return Identity string.
*/
private String buildIdentityHeader(final TextBody body, final TextBody bodyPlain) {
Uri.Builder uri = new Uri.Builder();
if (body.getComposedMessageLength() != null && body.getComposedMessageOffset() != null) {
// See if the message body length is already in the TextBody.
@ -1209,6 +1270,24 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
uri.appendQueryParameter(IdentityField.LENGTH.value(), Integer.toString(body.getText().length()));
uri.appendQueryParameter(IdentityField.OFFSET.value(), Integer.toString(0));
}
if (mQuotedHtmlContent != null) {
uri.appendQueryParameter(IdentityField.FOOTER_OFFSET.value(),
Integer.toString(mQuotedHtmlContent.getFooterInsertionPoint()));
}
if (bodyPlain != null) {
if (bodyPlain.getComposedMessageLength() != null && bodyPlain.getComposedMessageOffset() != null) {
// See if the message body length is already in the TextBody.
uri.appendQueryParameter(IdentityField.PLAIN_LENGTH.value(), bodyPlain.getComposedMessageLength().toString());
uri.appendQueryParameter(IdentityField.PLAIN_OFFSET.value(), bodyPlain.getComposedMessageOffset().toString());
} else {
// If not, calculate it now.
uri.appendQueryParameter(IdentityField.PLAIN_LENGTH.value(), Integer.toString(body.getText().length()));
uri.appendQueryParameter(IdentityField.PLAIN_OFFSET.value(), Integer.toString(0));
}
}
// Save the quote style (useful for forwards).
uri.appendQueryParameter(IdentityField.QUOTE_STYLE.value(), mQuoteStyle.name());
// Save the message format for this offset.
uri.appendQueryParameter(IdentityField.MESSAGE_FORMAT.value(), mMessageFormat.name());
@ -1325,6 +1404,20 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
return text;
}
/**
* Get an HTML version of the signature in the #mSignatureView, if any.
* @return HTML version of signature.
*/
private String getSignatureHtml() {
String signature = "";
if (mIdentity.getSignatureUse()) {
signature = mSignatureView.getText().toString();
if(!StringUtils.isNullOrEmpty(signature)) {
signature = HtmlConverter.textToHtmlFragment("\n" + signature);
}
}
return signature;
}
private void sendMessage() {
new SendMessageTask().execute();
@ -1440,10 +1533,10 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
private void onReadReceipt() {
CharSequence txt;
if (mReadReceipt == false) {
txt=getString(R.string.read_receipt_enabled);
txt = getString(R.string.read_receipt_enabled);
mReadReceipt = true;
} else {
txt=getString(R.string.read_receipt_disabled);
txt = getString(R.string.read_receipt_disabled);
mReadReceipt = false;
}
Context context = getApplicationContext();
@ -1834,13 +1927,10 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
@Override
public void onBackPressed() {
// This will be called either automatically for you on 2.0
// or later, or by the code above on earlier versions of the
// platform.
if (mDraftNeedsSaving) {
showDialog(DIALOG_SAVE_OR_DISCARD_DRAFT_MESSAGE);
} else {
finish();
super.onBackPressed();
}
}
@ -1868,24 +1958,6 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
return super.onCreateDialog(id);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (
// TODO - when we move to android 2.0, uncomment this.
// android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.ECLAIR &&
keyCode == KeyEvent.KEYCODE_BACK
&& event.getRepeatCount() == 0
&& K9.manageBack()) {
// Take care of calling this method on earlier versions of
// the platform where it doesn't exist.
onBackPressed();
return true;
}
return super.onKeyDown(keyCode, event);
}
/**
* Returns true if all attachments were able to be attached, otherwise returns false.
*/
@ -2034,6 +2106,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
} else {
mSubjectView.setText(subject);
}
mQuoteStyle = QuoteStyle.HEADER;
// Quote the message and setup the UI.
populateUIWithQuotedMessage(true);
@ -2146,6 +2219,19 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
Integer bodyOffset = k9identity.get(IdentityField.OFFSET) != null
? Integer.parseInt(k9identity.get(IdentityField.OFFSET))
: 0;
Integer bodyFooterOffset = k9identity.get(IdentityField.FOOTER_OFFSET) != null
? Integer.parseInt(k9identity.get(IdentityField.FOOTER_OFFSET))
: null;
Integer bodyPlainLength = k9identity.get(IdentityField.PLAIN_LENGTH) != null
? Integer.parseInt(k9identity.get(IdentityField.PLAIN_LENGTH))
: null;
Integer bodyPlainOffset = k9identity.get(IdentityField.PLAIN_OFFSET) != null
? Integer.parseInt(k9identity.get(IdentityField.PLAIN_OFFSET))
: null;
mQuoteStyle = k9identity.get(IdentityField.QUOTE_STYLE) != null
? QuoteStyle.valueOf(k9identity.get(IdentityField.QUOTE_STYLE))
: mAccount.getQuoteStyle();
// Always respect the user's current composition format preference, even if the
// draft was saved in a different format.
// TODO - The current implementation doesn't allow a user in HTML mode to edit a draft that wasn't saved with K9mail.
@ -2185,34 +2271,19 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
mQuotedHtmlContent.setQuotedContent(quotedHTML);
// We don't know if bodyOffset refers to the header or to the footer
mQuotedHtmlContent.setHeaderInsertionPoint(bodyOffset);
mQuotedHtmlContent.setFooterInsertionPoint(bodyOffset);
if (bodyFooterOffset != null) {
mQuotedHtmlContent.setFooterInsertionPoint(bodyFooterOffset);
} else {
mQuotedHtmlContent.setFooterInsertionPoint(bodyOffset);
}
mQuotedHTML.loadDataWithBaseURL("http://", mQuotedHtmlContent.getQuotedContent(), "text/html", "utf-8", null);
}
}
} else if (mMessageFormat == MessageFormat.TEXT) {
Part textPart = MimeUtility.findFirstPartByMimeType(message, "text/plain");
if (textPart != null) {
String text = MimeUtility.getTextFromPart(textPart);
if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "Loading message with offset " + bodyOffset + ", length " + bodyLength + ". Text length is " + text.length() + ".");
}
// If we had a body length (and it was valid), separate the composition from the quoted text
// and put them in their respective places in the UI.
if (bodyLength != null && bodyLength + 1 < text.length()) { // + 1 to get rid of the newline we added when saving the draft
String bodyText = text.substring(bodyOffset, bodyOffset + bodyLength);
// Regenerate the quoted text without our user content in it.
StringBuilder quotedText = new StringBuilder();
quotedText.append(text.substring(0, bodyOffset)); // stuff before the reply
quotedText.append(text.substring(bodyOffset + bodyLength));
mMessageContentView.setText(bodyText);
mQuotedText.setText(quotedText.toString());
} else {
mMessageContentView.setText(text);
}
if (bodyPlainOffset != null && bodyPlainLength != null) {
processSourceMessageText(message, bodyPlainOffset, bodyPlainLength, false);
}
} else if (mMessageFormat == MessageFormat.TEXT) {
processSourceMessageText(message, bodyOffset, bodyLength, true);
} else {
Log.e(K9.LOG_TAG, "Unhandled message format.");
}
@ -2238,11 +2309,68 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
}
}
/*
* Pull out the parts of the now loaded source message and apply them to the new message
* depending on the type of message being composed.
* @param message Source message
* @param bodyOffset Insertion point for reply.
* @param bodyLength Length of reply.
* @param viewMessageContent Update mMessageContentView or not.
* @throws MessagingException
*/
private void processSourceMessageText(Message message, Integer bodyOffset, Integer bodyLength,
boolean viewMessageContent) throws MessagingException {
Part textPart = MimeUtility.findFirstPartByMimeType(message, "text/plain");
if (textPart != null) {
String text = MimeUtility.getTextFromPart(textPart);
if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "Loading message with offset " + bodyOffset + ", length " + bodyLength + ". Text length is " + text.length() + ".");
}
// If we had a body length (and it was valid), separate the composition from the quoted text
// and put them in their respective places in the UI.
if (bodyLength != null && bodyLength + 1 < text.length()) { // + 1 to get rid of the newline we added when saving the draft
String bodyText = text.substring(bodyOffset, bodyOffset + bodyLength);
// Regenerate the quoted text without our user content in it nor added newlines.
StringBuilder quotedText = new StringBuilder();
if (bodyOffset == 0 && text.substring(bodyLength, bodyLength + 2).equals("\n\n")) {
// top-posting: ignore two newlines at start of quote
quotedText.append(text.substring(bodyLength + 2));
} else if (bodyOffset + bodyLength == text.length() &&
text.substring(bodyOffset - 1, bodyOffset).equals("\n")) {
// bottom-posting: ignore newline at end of quote
quotedText.append(text.substring(0, bodyOffset - 1));
} else {
quotedText.append(text.substring(0, bodyOffset)); // stuff before the reply
quotedText.append(text.substring(bodyOffset + bodyLength));
}
if (viewMessageContent) mMessageContentView.setText(bodyText);
mQuotedText.setText(quotedText.toString());
} else {
if (viewMessageContent) mMessageContentView.setText(text);
}
}
}
// Regexes to check for signature.
private static final Pattern DASH_SIGNATURE_PLAIN = Pattern.compile("\r\n-- \r\n.*", Pattern.DOTALL);
private static final Pattern DASH_SIGNATURE_HTML = Pattern.compile("(<br( /)?>|\r?\n)-- <br( /)?>", Pattern.CASE_INSENSITIVE);
private static final Pattern BLOCKQUOTE_START = Pattern.compile("<blockquote", Pattern.CASE_INSENSITIVE);
private static final Pattern BLOCKQUOTE_END = Pattern.compile("</blockquote>", Pattern.CASE_INSENSITIVE);
/**
* Build and populate the UI with the quoted message.
* @throws MessagingException
*/
private void populateUIWithQuotedMessage(boolean shown) throws MessagingException {
if (mMessageFormat == MessageFormat.AUTO) {
mMessageFormat = MimeUtility.findFirstPartByMimeType(mSourceMessage, "text/html") == null
? MessageFormat.TEXT
: MessageFormat.HTML;
}
// TODO -- I am assuming that mSourceMessageBody will always be a text part. Is this a safe assumption?
// Handle the original message in the reply
@ -2251,13 +2379,94 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
? mSourceMessageBody
: getBodyTextFromMessage(mSourceMessage, mMessageFormat);
if (mMessageFormat == MessageFormat.HTML) {
// Strip signature.
// closing tags such as </div>, </span>, </table>, </pre> will be cut off.
if (mAccount.isStripSignature() && (ACTION_REPLY_ALL.equals(getIntent().getAction()) ||
ACTION_REPLY.equals(getIntent().getAction()))) {
Matcher dashSignatureHtml = DASH_SIGNATURE_HTML.matcher(content);
if (dashSignatureHtml.find()) {
Matcher blockquoteStart = BLOCKQUOTE_START.matcher(content);
Matcher blockquoteEnd = BLOCKQUOTE_END.matcher(content);
List<Integer> start = new ArrayList<Integer>();
List<Integer> end = new ArrayList<Integer>();
while(blockquoteStart.find()) {
start.add(blockquoteStart.start());
}
while(blockquoteEnd.find()) {
end.add(blockquoteEnd.start());
}
if (start.size() != end.size()) {
Log.d(K9.LOG_TAG, "There are " + start.size() + " <blockquote> tags, but " +
end.size() + " </blockquote> tags. Refusing to strip.");
} else if (start.size() > 0) {
// Ignore quoted signatures in blockquotes.
dashSignatureHtml.region(0, start.get(0));
if (dashSignatureHtml.find()) {
// before first <blockquote>.
content = content.substring(0, dashSignatureHtml.start());
} else {
for (int i = 0; i < start.size() - 1; i++) {
// within blockquotes.
if (end.get(i) < start.get(i+1)) {
dashSignatureHtml.region(end.get(i), start.get(i+1));
if (dashSignatureHtml.find()) {
content = content.substring(0, dashSignatureHtml.start());
break;
}
}
}
if (end.get(end.size() - 1) < content.length()) {
// after last </blockquote>.
dashSignatureHtml.region(end.get(end.size() - 1), content.length());
if (dashSignatureHtml.find()) {
content = content.substring(0, dashSignatureHtml.start());
}
}
}
} else {
// No blockquotes found.
content = content.substring(0, dashSignatureHtml.start());
}
}
// Fix the stripping off of closing tags if a signature was stripped,
// as well as clean up the HTML of the quoted message.
HtmlCleaner cleaner = new HtmlCleaner();
CleanerProperties properties = cleaner.getProperties();
// see http://htmlcleaner.sourceforge.net/parameters.php for descriptions
properties.setNamespacesAware(false);
properties.setAdvancedXmlEscape(false);
properties.setOmitXmlDeclaration(true);
properties.setOmitDoctypeDeclaration(false);
properties.setTranslateSpecialEntities(false);
properties.setRecognizeUnicodeChars(false);
TagNode node = cleaner.clean(content);
SimpleHtmlSerializer htmlSerialized = new SimpleHtmlSerializer(properties);
try {
content = htmlSerialized.getAsString(node, "UTF8");
} catch (java.io.IOException ioe) {
// Can't imagine this happening.
Log.e(K9.LOG_TAG, "Problem cleaning quoted message.", ioe);
}
}
// Add the HTML reply header to the top of the content.
mQuotedHtmlContent = quoteOriginalHtmlMessage(mSourceMessage, content, mAccount.getQuoteStyle());
mQuotedHtmlContent = quoteOriginalHtmlMessage(mSourceMessage, content, mQuoteStyle);
// Load the message with the reply header.
mQuotedHTML.loadDataWithBaseURL("http://", mQuotedHtmlContent.getQuotedContent(), "text/html", "utf-8", null);
mQuotedText.setText(quoteOriginalTextMessage(mSourceMessage,
getBodyTextFromMessage(mSourceMessage, MessageFormat.TEXT), mQuoteStyle));
} else if (mMessageFormat == MessageFormat.TEXT) {
mQuotedText.setText(quoteOriginalTextMessage(mSourceMessage, content, mAccount.getQuoteStyle()));
if (mAccount.isStripSignature() && (ACTION_REPLY_ALL.equals(getIntent().getAction()) ||
ACTION_REPLY.equals(getIntent().getAction()))) {
if (DASH_SIGNATURE_PLAIN.matcher(content).find()) {
content = DASH_SIGNATURE_PLAIN.matcher(content).replaceFirst("\r\n");
}
}
mQuotedText.setText(quoteOriginalTextMessage(mSourceMessage, content, mQuoteStyle));
}
if (shown) {

View File

@ -18,6 +18,7 @@ import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.style.AbsoluteSizeSpan;
import android.text.style.ForegroundColorSpan;
import android.text.style.StyleSpan;
import android.util.Log;
@ -897,9 +898,6 @@ public class MessageList
@Override
public void onBackPressed() {
// This will be called either automatically for you on 2.0
// or later, or by the code above on earlier versions of the
// platform.
if (K9.manageBack()) {
if (mQueryString == null) {
onShowFolderList();
@ -907,24 +905,12 @@ public class MessageList
onAccounts();
}
} else {
finish();
super.onBackPressed();
}
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (
// XXX TODO - when we go to android 2.0, uncomment this
// android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.ECLAIR &&
keyCode == KeyEvent.KEYCODE_BACK
&& event.getRepeatCount() == 0
) {
// Take care of calling this method on earlier versions of
// the platform where it doesn't exist.
onBackPressed();
return true;
}
// Shortcuts that work no matter what is selected
switch (keyCode) {
@ -2181,7 +2167,7 @@ public class MessageList
0,
noSender.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
str.setSpan(K9.createAbsoluteSizeSpan(mFontSizes.getMessageListSender()),
str.setSpan(new AbsoluteSizeSpan(mFontSizes.getMessageListSender(), true),
0,
noSender.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
@ -2270,7 +2256,7 @@ public class MessageList
0,
message.sender.length() + 1,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
str.setSpan(K9.createAbsoluteSizeSpan(mFontSizes.getMessageListSender()),
str.setSpan(new AbsoluteSizeSpan(mFontSizes.getMessageListSender(), true),
0,
message.sender.length() + 1,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

View File

@ -27,8 +27,6 @@ import com.fsck.k9.view.SingleMessageView;
import com.fsck.k9.view.AttachmentView.AttachmentFileDownloadCallback;
import java.io.File;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
public class MessageView extends K9Activity implements OnClickListener {
@ -42,27 +40,6 @@ public class MessageView extends K9Activity implements OnClickListener {
private static final int ACTIVITY_CHOOSE_FOLDER_COPY = 2;
private static final int ACTIVITY_CHOOSE_DIRECTORY = 3;
/**
* Whether parent class have the onBackPressed() method (with no argument)
*/
private static final boolean HAS_SUPER_ON_BACK_METHOD;
static {
boolean hasOnBackMethod;
try {
final Class <? super MessageView > superClass = MessageView.class.getSuperclass();
final Method method = superClass.getMethod("onBackPressed", new Class[] {});
hasOnBackMethod = (method.getModifiers() & Modifier.PUBLIC) == Modifier.PUBLIC;
} catch (final SecurityException e) {
if (K9.DEBUG) {
Log.v(K9.LOG_TAG, "Security exception while checking for 'onBackPressed' method", e);
}
hasOnBackMethod = false;
} catch (final NoSuchMethodException e) {
hasOnBackMethod = false;
}
HAS_SUPER_ON_BACK_METHOD = hasOnBackMethod;
}
private SingleMessageView mMessageView;
private PgpData mPgpData;
@ -263,17 +240,12 @@ public class MessageView extends K9Activity implements OnClickListener {
@Override
public void onBackPressed() {
// This will be called either automatically for you on 2.0
// or later, or by the code above on earlier versions of the
// platform.
if (K9.manageBack()) {
String folder = (mMessage != null) ? mMessage.getFolder().getName() : null;
MessageList.actionHandleFolder(this, mAccount, folder);
finish();
} else if (HAS_SUPER_ON_BACK_METHOD) {
super.onBackPressed();
} else {
finish();
super.onBackPressed();
}
}
@ -649,7 +621,7 @@ public class MessageView extends K9Activity implements OnClickListener {
* Called from UI thread when user select Delete
*/
private void onDelete() {
if (K9.confirmDelete()) {
if (K9.confirmDelete() || (K9.confirmDeleteStarred() && mMessage.isSet(Flag.FLAGGED))) {
showDialog(R.id.dialog_confirm_delete);
} else {
delete();

View File

@ -9,7 +9,6 @@ import android.os.Bundle;
import android.os.Vibrator;
import android.preference.*;
import android.util.Log;
import android.view.KeyEvent;
import java.util.Iterator;
import java.util.Map;
@ -93,6 +92,7 @@ public class AccountSettings extends K9PreferenceActivity {
private static final String PREFERENCE_QUOTE_STYLE = "quote_style";
private static final String PREFERENCE_DEFAULT_QUOTED_TEXT_SHOWN = "default_quoted_text_shown";
private static final String PREFERENCE_REPLY_AFTER_QUOTE = "reply_after_quote";
private static final String PREFERENCE_STRIP_SIGNATURE = "strip_signature";
private static final String PREFERENCE_SYNC_REMOTE_DELETIONS = "account_sync_remote_deletetions";
private static final String PREFERENCE_CRYPTO_APP = "crypto_app";
private static final String PREFERENCE_CRYPTO_AUTO_SIGNATURE = "crypto_auto_signature";
@ -152,6 +152,7 @@ public class AccountSettings extends K9PreferenceActivity {
private EditTextPreference mAccountQuotePrefix;
private CheckBoxPreference mAccountDefaultQuotedTextShown;
private CheckBoxPreference mReplyAfterQuote;
private CheckBoxPreference mStripSignature;
private CheckBoxPreference mSyncRemoteDeletions;
private CheckBoxPreference mSaveAllHeaders;
private CheckBoxPreference mPushPollOnConnect;
@ -241,6 +242,9 @@ public class AccountSettings extends K9PreferenceActivity {
mReplyAfterQuote = (CheckBoxPreference) findPreference(PREFERENCE_REPLY_AFTER_QUOTE);
mReplyAfterQuote.setChecked(mAccount.isReplyAfterQuote());
mStripSignature = (CheckBoxPreference) findPreference(PREFERENCE_STRIP_SIGNATURE);
mStripSignature.setChecked(mAccount.isStripSignature());
mComposingScreen = (PreferenceScreen) findPreference(PREFERENCE_SCREEN_COMPOSING);
Preference.OnPreferenceChangeListener quoteStyleListener = new Preference.OnPreferenceChangeListener() {
@ -726,6 +730,7 @@ public class AccountSettings extends K9PreferenceActivity {
mAccount.setQuotePrefix(mAccountQuotePrefix.getText());
mAccount.setDefaultQuotedTextShown(mAccountDefaultQuotedTextShown.isChecked());
mAccount.setReplyAfterQuote(mReplyAfterQuote.isChecked());
mAccount.setStripSignature(mStripSignature.isChecked());
mAccount.setCryptoApp(mCryptoApp.getValue());
mAccount.setCryptoAutoSignature(mCryptoAutoSignature.isChecked());
mAccount.setLocalStorageProviderId(mLocalStorageProvider.getValue());
@ -809,11 +814,9 @@ public class AccountSettings extends K9PreferenceActivity {
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
saveSettings();
}
return super.onKeyDown(keyCode, event);
public void onBackPressed() {
saveSettings();
super.onBackPressed();
}
private void onCompositionSettings() {
@ -910,8 +913,8 @@ public class AccountSettings extends K9PreferenceActivity {
}
}
allFolderValues = new String[folders.size()+1];
allFolderLabels = new String[folders.size()+1];
allFolderValues = new String[folders.size() + 1];
allFolderLabels = new String[folders.size() + 1];
allFolderValues[0] = K9.FOLDER_NONE;
allFolderLabels[0] = K9.FOLDER_NONE;

View File

@ -225,7 +225,12 @@ public class AccountSetupBasics extends K9Activity
mAccount.setDraftsFolderName(getString(R.string.special_mailbox_name_drafts));
mAccount.setTrashFolderName(getString(R.string.special_mailbox_name_trash));
mAccount.setArchiveFolderName(getString(R.string.special_mailbox_name_archive));
mAccount.setSpamFolderName(getString(R.string.special_mailbox_name_spam));
// Yahoo! has a special folder for Spam, called "Bulk Mail".
if (incomingUriTemplate.getHost().toLowerCase().endsWith(".yahoo.com")) {
mAccount.setSpamFolderName("Bulk Mail");
} else {
mAccount.setSpamFolderName(getString(R.string.special_mailbox_name_spam));
}
mAccount.setSentFolderName(getString(R.string.special_mailbox_name_sent));
AccountSetupCheckSettings.actionCheckSettings(this, mAccount, true, true);
} catch (UnsupportedEncodingException enc) {

View File

@ -3,7 +3,6 @@ package com.fsck.k9.activity.setup;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
import android.widget.CompoundButton;
import android.widget.CheckBox;
@ -121,11 +120,9 @@ public class AccountSetupComposition extends K9Activity {
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
saveSettings();
}
return super.onKeyDown(keyCode, event);
public void onBackPressed() {
saveSettings();
super.onBackPressed();
}
@Override

View File

@ -8,7 +8,6 @@ import android.preference.CheckBoxPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.util.Log;
import android.view.KeyEvent;
import com.fsck.k9.*;
import com.fsck.k9.activity.K9PreferenceActivity;
import com.fsck.k9.mail.Folder.FolderClass;
@ -151,16 +150,13 @@ public class FolderSettings extends K9PreferenceActivity {
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
try {
saveSettings();
} catch (MessagingException e) {
Log.e(K9.LOG_TAG, "Saving folder settings failed", e);
}
public void onBackPressed() {
try {
saveSettings();
} catch (MessagingException e) {
Log.e(K9.LOG_TAG, "Saving folder settings failed", e);
}
return super.onKeyDown(keyCode, event);
super.onBackPressed();
}
}

View File

@ -7,7 +7,6 @@ import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.Bundle;
import android.preference.*;
import android.view.KeyEvent;
import com.fsck.k9.*;
import com.fsck.k9.activity.K9PreferenceActivity;
@ -160,10 +159,8 @@ public class FontSizeSettings extends K9PreferenceActivity {
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
saveSettings();
}
return super.onKeyDown(keyCode, event);
public void onBackPressed() {
saveSettings();
super.onBackPressed();
}
}

View File

@ -17,7 +17,6 @@ import android.preference.CheckBoxPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceClickListener;
import android.view.KeyEvent;
import android.widget.Toast;
import com.fsck.k9.K9;
@ -193,11 +192,13 @@ public class Prefs extends K9PreferenceActivity {
mConfirmActions = (CheckBoxListPreference) findPreference(PREFERENCE_CONFIRM_ACTIONS);
mConfirmActions.setItems(new CharSequence[] {
getString(R.string.global_settings_confirm_action_delete),
getString(R.string.global_settings_confirm_action_delete_starred),
getString(R.string.global_settings_confirm_action_spam),
getString(R.string.global_settings_confirm_action_mark_all_as_read)
});
mConfirmActions.setCheckedItems(new boolean[] {
K9.confirmDelete(),
K9.confirmDeleteStarred(),
K9.confirmSpam(),
K9.confirmMarkAllAsRead()
});
@ -266,7 +267,7 @@ public class Prefs extends K9PreferenceActivity {
mZoomControlsEnabled.setChecked(K9.zoomControlsEnabled());
mMobileOptimizedLayout = (CheckBoxPreference) findPreference(PREFERENCE_MESSAGEVIEW_MOBILE_LAYOUT);
if (Integer.parseInt(Build.VERSION.SDK) <= 7) {
if (Build.VERSION.SDK_INT <= 7) {
mMobileOptimizedLayout.setEnabled(false);
}
@ -356,8 +357,9 @@ public class Prefs extends K9PreferenceActivity {
K9.setManageBack(mManageBack.isChecked());
K9.setStartIntegratedInbox(!mHideSpecialAccounts.isChecked() && mStartIntegratedInbox.isChecked());
K9.setConfirmDelete(mConfirmActions.getCheckedItems()[0]);
K9.setConfirmSpam(mConfirmActions.getCheckedItems()[1]);
K9.setConfirmMarkAllAsRead(mConfirmActions.getCheckedItems()[2]);
K9.setConfirmDeleteStarred(mConfirmActions.getCheckedItems()[1]);
K9.setConfirmSpam(mConfirmActions.getCheckedItems()[2]);
K9.setConfirmMarkAllAsRead(mConfirmActions.getCheckedItems()[3]);
K9.setKeyguardPrivacy(mPrivacyMode.isChecked());
K9.setMeasureAccounts(mMeasureAccounts.isChecked());
K9.setCountSearchMessages(mCountSearch.isChecked());
@ -401,16 +403,15 @@ public class Prefs extends K9PreferenceActivity {
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
saveSettings();
if (K9.manageBack()) {
Accounts.listAccounts(this);
finish();
return true;
}
public void onBackPressed() {
saveSettings();
if (K9.manageBack()) {
Accounts.listAccounts(this);
finish();
} else {
super.onBackPressed();
}
return super.onKeyDown(keyCode, event);
}
private void onFontSizeSettings() {

View File

@ -1,115 +0,0 @@
package com.fsck.k9.helper;
import com.fsck.k9.K9;
import android.os.Build;
import android.util.Log;
/**
* Helper class to get the current state of the auto-sync setting.
*/
public class AutoSyncHelper {
/**
* False, if we never tried to load the class for this SDK version.
* True, otherwise.
*
* Note: if sAutoSync is null and sChecked is true, then an error occurred
* while loading the class for the SDK version we're running on.
*/
private static boolean sChecked = false;
/**
* Instance of the SDK specific class that implements the IAutoSync
* interface.
*/
private static IAutoSync sAutoSync = null;
/**
* String for the auto-sync changed Intent. This isn't currently exposed by the API
*/
public static String SYNC_CONN_STATUS_CHANGE = "com.android.sync.SYNC_CONN_STATUS_CHANGED";
/**
* Try loading the class that implements IAutoSync for this SDK version.
*
* @return the IAutoSync object for this SDK version, or null if something
* went wrong.
*/
private static IAutoSync loadAutoSync() {
/*
* We're trying to load the class for this SDK version. If anything
* goes wrong after this point, we don't want to try again.
*/
sChecked = true;
/*
* Check the version of the SDK we are running on. Choose an
* implementation class designed for that version of the SDK.
*/
int sdkVersion = Integer.parseInt(Build.VERSION.SDK);
String className = null;
if (sdkVersion == Build.VERSION_CODES.CUPCAKE) {
className = "com.fsck.k9.helper.AutoSyncSdk3";
} else if (sdkVersion == Build.VERSION_CODES.DONUT) {
className = "com.fsck.k9.helper.AutoSyncSdk4";
} else if (sdkVersion >= Build.VERSION_CODES.ECLAIR) {
className = "com.fsck.k9.helper.AutoSyncSdk5";
}
/*
* Find the required class by name and instantiate it.
*/
try {
Class <? extends IAutoSync > clazz =
Class.forName(className).asSubclass(IAutoSync.class);
IAutoSync autoSync = clazz.newInstance();
autoSync.initialize(K9.app);
return autoSync;
} catch (ClassNotFoundException e) {
Log.e(K9.LOG_TAG, "Couldn't find class: " + className, e);
} catch (InstantiationException e) {
Log.e(K9.LOG_TAG, "Couldn't instantiate class: " + className, e);
} catch (IllegalAccessException e) {
Log.e(K9.LOG_TAG, "Couldn't access class: " + className, e);
} catch (NoSuchMethodException e) {
if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "Couldn't load method to get auto-sync state", e);
}
}
return null;
}
/**
* Checks whether we can query the auto-sync state using
* getMasterSyncAutomatically() or not.
*
* @return true, if calls to getMasterSyncAutomatically() will return the
* state of the auto-sync setting. false, otherwise.
*/
public static boolean isAvailable() {
if (!sChecked) {
sAutoSync = loadAutoSync();
}
return (sAutoSync != null);
}
/**
* Query the state of the auto-sync setting.
*
* @return the state of the auto-sync setting.
* @see IAutoSync
*/
public static boolean getMasterSyncAutomatically() {
if (!sChecked) {
sAutoSync = loadAutoSync();
}
if (sAutoSync == null) {
throw new RuntimeException(
"Called getMasterSyncAutomatically() before checking if it's available.");
}
return sAutoSync.getMasterSyncAutomatically();
}
}

View File

@ -1,39 +0,0 @@
package com.fsck.k9.helper;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import android.content.ContentResolver;
import android.content.Context;
import android.os.Handler;
public class AutoSyncSdk3 implements IAutoSync {
private Method mGetListenForNetworkTickles;
private Object mQueryMap;
public void initialize(Context context) throws NoSuchMethodException {
/*
* There's no documented/official way to query the state of the
* auto-sync setting for a normal application in SDK 1.5/API 3.
*
* We use reflection to get an Sync.Settings.QueryMap" object, so we
* can call its getListenForNetworkTickles() method. This will return
* the current auto-sync state.
*/
try {
Class<?> clazz = Class.forName("android.provider.Sync$Settings$QueryMap");
Constructor<?> c = clazz.getConstructor(ContentResolver.class, boolean.class, Handler.class);
mQueryMap = c.newInstance(context.getContentResolver(), true, null);
mGetListenForNetworkTickles = mQueryMap.getClass().getMethod("getListenForNetworkTickles");
} catch (Exception e) {
throw new NoSuchMethodException();
}
}
public boolean getMasterSyncAutomatically() {
try {
return (Boolean) mGetListenForNetworkTickles.invoke(mQueryMap);
} catch (Exception e) {
return false;
}
}
}

View File

@ -1,41 +0,0 @@
package com.fsck.k9.helper;
import java.lang.reflect.Method;
import com.fsck.k9.K9;
import android.content.ContentResolver;
import android.content.Context;
import android.util.Log;
public class AutoSyncSdk4 implements IAutoSync {
private Method mGetListenForNetworkTickles;
private Object mContentService;
public void initialize(Context context) throws NoSuchMethodException {
/*
* There's no documented/official way to query the state of the
* auto-sync setting for a normal application in SDK 1.6/API 4.
*
* We use reflection to get an ContentService object, so we can call its
* getListenForNetworkTickles() method. This will return the current
* auto-sync state.
*/
try {
Method getContentService = ContentResolver.class.getMethod("getContentService");
mContentService = getContentService.invoke(null);
mGetListenForNetworkTickles = mContentService.getClass().getMethod("getListenForNetworkTickles");
} catch (Exception e) {
throw new NoSuchMethodException();
}
}
public boolean getMasterSyncAutomatically() {
try {
return (Boolean) mGetListenForNetworkTickles.invoke(mContentService);
} catch (Exception e) {
Log.e(K9.LOG_TAG, "Could not query for network tickle", e);
return true;
}
}
}

View File

@ -1,18 +0,0 @@
package com.fsck.k9.helper;
import android.content.ContentResolver;
import android.content.Context;
public class AutoSyncSdk5 implements IAutoSync {
public void initialize(Context context) throws NoSuchMethodException {
// Nothing to do here
}
public boolean getMasterSyncAutomatically() {
/*
* SDK 2.0/API 5 introduced an official method to query the auto-sync
* state.
*/
return ContentResolver.getMasterSyncAutomatically();
}
}

View File

@ -18,8 +18,8 @@ import com.fsck.k9.mail.Address;
* A class that uses the latest contacts API available on the device will be
* loaded at runtime.
*
* @see ContactsSdk3_4
* @see ContactsSdk5
* @see ContactsSdk5p
*/
public abstract class Contacts {
/**
@ -40,12 +40,8 @@ public abstract class Contacts {
* Check the version of the SDK we are running on. Choose an
* implementation class designed for that version of the SDK.
*/
int sdkVersion = Integer.parseInt(Build.VERSION.SDK);
String className = null;
if (sdkVersion <= Build.VERSION_CODES.DONUT) {
className = "com.fsck.k9.helper.ContactsSdk3_4";
} else if (sdkVersion <= Build.VERSION_CODES.ECLAIR_MR1) {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.ECLAIR_MR1) {
/*
* The new API was introduced with SDK 5. But Android versions < 2.2
* need some additional code to be able to search for phonetic names.

View File

@ -1,263 +0,0 @@
package com.fsck.k9.helper;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;
import android.provider.Contacts;
import android.provider.Contacts.ContactMethods;
import com.fsck.k9.mail.Address;
import com.fsck.k9.K9;
/**
* Access the contacts on the device using the old API (introduced in SDK 1).
*
* @see android.provider.Contacts
*/
@SuppressWarnings("deprecation")
public class ContactsSdk3_4 extends com.fsck.k9.helper.Contacts {
/**
* The order in which the search results are returned by
* {@link #searchContacts(CharSequence)}.
*/
private static final String SORT_ORDER =
Contacts.ContactMethods.TIMES_CONTACTED + " DESC, " +
Contacts.ContactMethods.DISPLAY_NAME + ", " +
Contacts.ContactMethods._ID;
/**
* Array of columns to load from the database.
*
* Important: The _ID field is needed by
* {@link com.fsck.k9.EmailAddressAdapter} or more specificly by
* {@link android.widget.ResourceCursorAdapter}.
*/
private static final String PROJECTION[] = {
Contacts.ContactMethods._ID,
Contacts.ContactMethods.DISPLAY_NAME,
Contacts.ContactMethods.DATA,
Contacts.ContactMethods.PERSON_ID
};
/**
* Index of the name field in the projection. This must match the order in
* {@link #PROJECTION}.
*/
private static final int NAME_INDEX = 1;
/**
* Index of the email address field in the projection. This must match the
* order in {@link #PROJECTION}.
*/
private static final int EMAIL_INDEX = 2;
/**
* Index of the contact id field in the projection. This must match the order in
* {@link #PROJECTION}.
*/
private static final int CONTACT_ID_INDEX = 3;
public ContactsSdk3_4(final Context context) {
super(context);
}
@Override
public void createContact(final Address email) {
final Uri contactUri = Uri.fromParts("mailto", email.getAddress(), null);
final Intent contactIntent = new Intent(Contacts.Intents.SHOW_OR_CREATE_CONTACT);
contactIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
contactIntent.setData(contactUri);
// Pass along full E-mail string for possible create dialog
contactIntent.putExtra(Contacts.Intents.EXTRA_CREATE_DESCRIPTION,
email.toString());
// Only provide personal name hint if we have one
final String senderPersonal = email.getPersonal();
if (senderPersonal != null) {
contactIntent.putExtra(Contacts.Intents.Insert.NAME, senderPersonal);
}
mContext.startActivity(contactIntent);
}
@Override
public boolean isInContacts(final String emailAddress) {
boolean result = false;
final Cursor c = getContactByAddress(emailAddress);
if (c != null) {
if (c.getCount() > 0) {
result = true;
}
c.close();
}
return result;
}
@Override
public Cursor searchContacts(final CharSequence constraint) {
final String where;
final String[] args;
if (constraint == null) {
where = Contacts.ContactMethods.KIND + " = " + Contacts.KIND_EMAIL;
args = null;
} else {
where = Contacts.ContactMethods.KIND + " = " + Contacts.KIND_EMAIL +
" AND " +
"(" +
// Match if name starts with "constraint"
Contacts.People.NAME + " LIKE ?" +
" OR " +
// Match if name contains a word that starts with "constraint"
Contacts.People.NAME + " LIKE ?" +
" OR " +
// Match if phonetic name starts with "constraint"
Contacts.People.PHONETIC_NAME + " LIKE ?" +
" OR " +
// Match if phonetic name contains a word that starts with "constraint"
Contacts.People.PHONETIC_NAME + " LIKE ?" +
" OR " +
// Match if email address starts with "constraint"
Contacts.ContactMethods.DATA + " LIKE ?" +
")";
final String filter = constraint.toString() + "%";
final String filter2 = "% " + filter;
args = new String[] {filter, filter2, filter, filter2, filter};
}
final Cursor c = mContentResolver.query(
Contacts.ContactMethods.CONTENT_URI,
PROJECTION,
where,
args,
SORT_ORDER);
if (c != null) {
/*
* To prevent expensive execution in the UI thread:
* Cursors get lazily executed, so if you don't call anything on
* the cursor before returning it from the background thread you'll
* have a complied program for the cursor, but it won't have been
* executed to generate the data yet. Often the execution is more
* expensive than the compilation...
*/
c.getCount();
}
return c;
}
@Override
public String getNameForAddress(String address) {
if (address == null) {
return null;
}
final Cursor c = getContactByAddress(address);
String name = null;
if (c != null) {
if (c.getCount() > 0) {
c.moveToFirst();
name = getName(c);
}
c.close();
}
return name;
}
@Override
public String getName(Cursor c) {
return c.getString(NAME_INDEX);
}
@Override
public String getEmail(Cursor c) {
return c.getString(EMAIL_INDEX);
}
@Override
public void markAsContacted(final Address[] addresses) {
//TODO: Optimize! Potentially a lot of database queries
for (final Address address : addresses) {
final Cursor c = getContactByAddress(address.getAddress());
if (c != null) {
if (c.getCount() > 0) {
c.moveToFirst();
final long personId = c.getLong(CONTACT_ID_INDEX);
Contacts.People.markAsContacted(mContentResolver, personId);
}
c.close();
}
}
}
@Override
public Intent contactPickerIntent() {
return new Intent(Intent.ACTION_PICK, Contacts.People.CONTENT_URI);
}
@Override
public String getEmailFromContactPicker(final Intent data) {
Cursor cursor = null;
Cursor cursor2 = null;
String email = "";
try {
Uri result = data.getData();
cursor = mContentResolver.query(result, null, null, null, null);
if (cursor.moveToFirst()) {
String emailId = cursor.getString(cursor.getColumnIndex(Contacts.People.PRIMARY_EMAIL_ID));
cursor2 = mContext.getContentResolver().query(
ContactMethods.CONTENT_EMAIL_URI,
new String[] { ContactMethods.DATA },
"contact_methods._id=?",
new String[] { emailId },
null);
if (cursor2.moveToFirst()) {
email = cursor2.getString(0);
}
}
} catch (Exception e) {
Log.e(K9.LOG_TAG, "Failed to get email data", e);
} finally {
Utility.closeQuietly(cursor);
Utility.closeQuietly(cursor2);
}
return email;
}
/**
* Return a {@link Cursor} instance that can be used to fetch information
* about the contact with the given email address.
*
* @param address The email address to search for.
* @return A {@link Cursor} instance that can be used to fetch information
* about the contact with the given email address
*/
private Cursor getContactByAddress(String address) {
final String where = Contacts.ContactMethods.KIND + " = " + Contacts.KIND_EMAIL +
" AND " +
Contacts.ContactMethods.DATA + " = ?";
final String[] args = new String[] {address};
final Cursor c = mContentResolver.query(
Contacts.ContactMethods.CONTENT_URI,
PROJECTION,
where,
args,
SORT_ORDER);
return c;
}
}

View File

@ -1,26 +0,0 @@
package com.fsck.k9.helper;
import android.content.Context;
/**
* Classes that implement this interface know how to query the system for the
* current state of the auto-sync setting. This method differs from SDK 3 to
* SDK 5, so there are specialized implementations for each SDK version.
*/
public interface IAutoSync {
/**
* Do the necessary reflection magic to get the necessary objects and/or
* methods to later query the state of the auto-sync setting.
*
* @param context The application context object.
* @throws NoSuchMethodException if something went wrong.
*/
public void initialize(Context context) throws NoSuchMethodException;
/**
* Query the state of the auto-sync setting.
*
* @return the state of the auto-sync setting.
*/
public boolean getMasterSyncAutomatically();
}

View File

@ -64,7 +64,7 @@ public class MimeHeader {
}
public Set<String> getHeaderNames() {
Set<String> names = new HashSet<String>();
Set<String> names = new LinkedHashSet<String>();
for (Field field : mFields) {
names.add(field.name);
}

View File

@ -1081,7 +1081,7 @@ public class MimeUtility {
*/
if (contentTransferEncoding != null) {
contentTransferEncoding =
MimeUtility.getHeaderParameter(contentTransferEncoding, null);
MimeUtility.getHeaderParameter(contentTransferEncoding, null);
if ("quoted-printable".equalsIgnoreCase(contentTransferEncoding)) {
in = new QuotedPrintableInputStream(in);
} else if ("base64".equalsIgnoreCase(contentTransferEncoding)) {
@ -1107,7 +1107,7 @@ public class MimeUtility {
* @throws MessagingException
*/
public static void collectParts(Part part, ArrayList<Part> viewables,
ArrayList<Part> attachments) throws MessagingException {
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
@ -1334,10 +1334,10 @@ public class MimeUtility {
private static String getJisVariantFromAddress(String address) {
if (isInDomain(address, "docomo.ne.jp") || isInDomain(address, "dwmail.jp") ||
isInDomain(address, "pdx.ne.jp") || isInDomain(address, "willcom.com"))
isInDomain(address, "pdx.ne.jp") || isInDomain(address, "willcom.com"))
return "docomo";
else if (isInDomain(address, "softbank.ne.jp") || isInDomain(address, "vodafone.ne.jp") ||
isInDomain(address, "disney.ne.jp") || isInDomain(address, "vertuclub.ne.jp"))
isInDomain(address, "disney.ne.jp") || isInDomain(address, "vertuclub.ne.jp"))
return "softbank";
else if (isInDomain(address, "ezweb.ne.jp") || isInDomain(address, "ido.ne.jp"))
return "kddi";
@ -1372,14 +1372,14 @@ public class MimeUtility {
// iso-2022-jp variants are supported by no versions as of Dec 2010.
if (charset.length() > 19 && charset.startsWith("x-") &&
charset.endsWith("-iso-2022-jp-2007") && !Charset.isSupported(charset)) {
charset.endsWith("-iso-2022-jp-2007") && !Charset.isSupported(charset)) {
in = new Iso2022JpToShiftJisInputStream(in);
charset = "x-" + charset.substring(2, charset.length() - 17) + "-shift_jis-2007";
}
// shift_jis variants are supported by Eclair and later.
if (charset.length() > 17 && charset.startsWith("x-") &&
charset.endsWith("-shift_jis-2007") && !Charset.isSupported(charset)) {
charset.endsWith("-shift_jis-2007") && !Charset.isSupported(charset)) {
// If the JIS variant is iPhone, map the Unicode private use area in iPhone to the one in Android after
// converting the character set from the standard Shift JIS to Unicode.
if (charset.substring(2, charset.length() - 15).equals("iphone"))
@ -1400,7 +1400,7 @@ public class MimeUtility {
}
if (!supported) {
Log.e(K9.LOG_TAG, "I don't know how to deal with the charset " + charset +
". Falling back to US-ASCII");
". Falling back to US-ASCII");
charset = "US-ASCII";
}
/*
@ -2401,12 +2401,12 @@ public class MimeUtility {
public static void setCharset(String charset, Part part) throws MessagingException {
part.setHeader(MimeHeader.HEADER_CONTENT_TYPE,
part.getMimeType() + ";\n charset=" + getExternalCharset(charset));
part.getMimeType() + ";\n charset=" + getExternalCharset(charset));
}
public static String getExternalCharset(String charset) {
if (charset.length() > 17 && charset.startsWith("x-") &&
charset.endsWith("-shift_jis-2007"))
charset.endsWith("-shift_jis-2007"))
return "shift_jis";
return charset;

View File

@ -1,5 +1,6 @@
package com.fsck.k9.mail.store;
import android.text.TextUtils;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.filter.FixedLengthInputStream;
import com.fsck.k9.mail.filter.PeekableInputStream;
@ -44,10 +45,8 @@ public class ImapResponseParser {
parseUntaggedResponse();
readTokens(response);
} else if (ch == '+') {
response.mCommandContinuationRequested =
parseCommandContinuationRequest();
//TODO: Add special "resp-text" parsing
readTokens(response);
response.mCommandContinuationRequested = parseCommandContinuationRequest();
parseResponseText(response);
} else {
response.mTag = parseTaggedResponse();
readTokens(response);
@ -67,24 +66,72 @@ public class ImapResponseParser {
private void readTokens(ImapResponse response) throws IOException {
response.clear();
Object token;
while ((token = readToken(response)) != null) {
if (!(token instanceof ImapList)) {
response.add(token);
}
/*
* TODO: Check for responses ("OK", "PREAUTH", "BYE", "NO", "BAD")
* that can contain resp-text tokens. If found, hand over to a special
* method that parses a resp-text token. There's no need to use
* readToken()/parseToken() on that data.
*
* See RFC 3501, Section 9 Formal Syntax (resp-text)
*/
String firstToken = (String) readToken(response);
response.add(firstToken);
if (isStatusResponse(firstToken)) {
parseResponseText(response);
} else {
Object token;
while ((token = readToken(response)) != null) {
if (!(token instanceof ImapList)) {
response.add(token);
}
}
}
response.mCompleted = true;
}
/**
* Parse {@code resp-text} tokens
*
* <p>
* Responses "OK", "PREAUTH", "BYE", "NO", "BAD", and continuation request responses can
* contain {@code resp-text} tokens. We parse the {@code resp-text-code} part as tokens and
* read the rest as sequence of characters to avoid the parser interpreting things like
* "{123}" as start of a literal.
* </p>
* <p>Example:</p>
* <p>
* {@code * OK [UIDVALIDITY 3857529045] UIDs valid}
* </p>
* <p>
* See RFC 3501, Section 9 Formal Syntax (resp-text)
* </p>
*
* @param parent
* The {@link ImapResponse} instance that holds the parsed tokens of the response.
*
* @throws IOException
* If there's a network error.
*
* @see #isStatusResponse(String)
*/
private void parseResponseText(ImapResponse parent) throws IOException {
skipIfSpace();
int next = mIn.peek();
if (next == '[') {
parseSequence(parent);
skipIfSpace();
}
String rest = readStringUntil('\r');
expect('\n');
if (!TextUtils.isEmpty(rest)) {
// The rest is free-form text.
parent.add(rest);
}
}
private void skipIfSpace() throws IOException {
if (mIn.peek() == ' ') {
expect(' ');
}
}
/**
* Reads the next token of the response. The token can be one of: String -
* for NIL, QUOTED, NUMBER, ATOM. Object - for LITERAL.
@ -480,6 +527,14 @@ public class ImapResponseParser {
}
}
public boolean isStatusResponse(String symbol) {
return symbol.equalsIgnoreCase("OK") ||
symbol.equalsIgnoreCase("NO") ||
symbol.equalsIgnoreCase("BAD") ||
symbol.equalsIgnoreCase("PREAUTH") ||
symbol.equalsIgnoreCase("BYE");
}
public static boolean equalsIgnoreCase(Object o1, Object o2) {
if (o1 != null && o2 != null && o1 instanceof String && o2 instanceof String) {
String s1 = (String)o1;

View File

@ -596,11 +596,81 @@ public class ImapStore extends Store {
}
/**
* Attempt to auto-configure folders by attributes if the server advertises that capability.
*
* The parsing here is essentially the same as
* {@link #listFolders(com.fsck.k9.mail.store.ImapStore.ImapConnection, boolean)}; we should try to consolidate
* this at some point. :(
* @param connection IMAP Connection
* @throws IOException uh oh!
* @throws MessagingException uh oh!
*/
private void autoconfigureFolders(final ImapConnection connection) throws IOException, MessagingException {
String commandResponse = null;
String commandOptions = "";
if (connection.capabilities.contains("XLIST")) {
if (K9.DEBUG) Log.d(K9.LOG_TAG, "Folder auto-configuration: Using XLIST.");
commandResponse = "XLIST";
} else if(connection.capabilities.contains("SPECIAL-USE")) {
if (K9.DEBUG) Log.d(K9.LOG_TAG, "Folder auto-configuration: Using RFC6154/SPECIAL-USE.");
commandResponse = "LIST";
commandOptions = " (SPECIAL-USE)";
} else {
if (K9.DEBUG) Log.d(K9.LOG_TAG, "No detected folder auto-configuration methods.");
return;
}
final List<ImapResponse> responses =
connection.executeSimpleCommand(String.format("%s%s \"\" %s", commandResponse, commandOptions,
encodeString(getCombinedPrefix() + "*")));
for (ImapResponse response : responses) {
if (ImapResponseParser.equalsIgnoreCase(response.get(0), commandResponse)) {
String decodedFolderName;
try {
decodedFolderName = decodeFolderName(response.getString(3));
} catch (CharacterCodingException e) {
Log.w(K9.LOG_TAG, "Folder name not correctly encoded with the UTF-7 variant " +
"as defined by RFC 3501: " + response.getString(3), e);
// We currently just skip folders with malformed names.
continue;
}
if (mPathDelimeter == null) {
mPathDelimeter = response.getString(2);
mCombinedPrefix = null;
}
ImapList attributes = response.getList(1);
for (int i = 0, count = attributes.size(); i < count; i++) {
String attribute = attributes.getString(i);
if (attribute.equals("\\Drafts")) {
mAccount.setDraftsFolderName(decodedFolderName);
if (K9.DEBUG) Log.d(K9.LOG_TAG, "Folder auto-configuration detected draft folder: " + decodedFolderName);
} else if (attribute.equals("\\Sent")) {
mAccount.setSentFolderName(decodedFolderName);
if (K9.DEBUG) Log.d(K9.LOG_TAG, "Folder auto-configuration detected sent folder: " + decodedFolderName);
} else if (attribute.equals("\\Spam")) {
mAccount.setSpamFolderName(decodedFolderName);
if (K9.DEBUG) Log.d(K9.LOG_TAG, "Folder auto-configuration detected spam folder: " + decodedFolderName);
} else if (attribute.equals("\\Trash")) {
mAccount.setTrashFolderName(decodedFolderName);
if (K9.DEBUG) Log.d(K9.LOG_TAG, "Folder auto-configuration detected trash folder: " + decodedFolderName);
}
}
}
}
}
@Override
public void checkSettings() throws MessagingException {
try {
ImapConnection connection = new ImapConnection(new StoreImapSettings());
connection.open();
autoconfigureFolders(connection);
connection.close();
} catch (IOException ioe) {
throw new MessagingException(K9.app.getString(R.string.error_unable_to_connect), ioe);

View File

@ -1779,7 +1779,7 @@ public class LocalStore extends Store implements Serializable {
}
cursor = db.rawQuery(
"SELECT message_id, name, value FROM headers " + "WHERE message_id in ( " + questions + ") ",
"SELECT message_id, name, value FROM headers " + "WHERE message_id in ( " + questions + ") ORDER BY id ASC",
ids.toArray(EMPTY_STRING_ARRAY));

View File

@ -55,6 +55,7 @@ public class AccountSettings {
R.array.account_settings_message_age_values));
s.put("messageFormat",
new EnumSetting(Account.MessageFormat.class, Account.DEFAULT_MESSAGE_FORMAT));
s.put("messageFormatAuto", new BooleanSetting(Account.DEFAULT_MESSAGE_FORMAT_AUTO)); // added to version 2
s.put("messageReadReceipt", new BooleanSetting(Account.DEFAULT_MESSAGE_READ_RECEIPT));
s.put("notificationUnreadCount", new BooleanSetting(true));
s.put("notifyMailCheck", new BooleanSetting(false));
@ -65,6 +66,7 @@ public class AccountSettings {
s.put("quoteStyle",
new EnumSetting(Account.QuoteStyle.class, Account.DEFAULT_QUOTE_STYLE));
s.put("replyAfterQuote", new BooleanSetting(Account.DEFAULT_REPLY_AFTER_QUOTE));
s.put("stripSignature", new BooleanSetting(Account.DEFAULT_STRIP_SIGNATURE)); // added to version 2
s.put("ring", new BooleanSetting(true));
s.put("ringtone", new RingtoneSetting("content://settings/system/notification_sound"));
s.put("saveAllHeaders", new BooleanSetting(true));

View File

@ -29,6 +29,7 @@ public class GlobalSettings {
s.put("changeRegisteredNameColor", new BooleanSetting(false));
s.put("compactLayouts", new BooleanSetting(false));
s.put("confirmDelete", new BooleanSetting(false));
s.put("confirmDeleteStarred", new BooleanSetting(false)); // added to version 2
s.put("confirmMarkAllAsRead", new BooleanSetting(false));
s.put("confirmSpam", new BooleanSetting(false));
s.put("countSearchMessages", new BooleanSetting(false));

View File

@ -32,7 +32,7 @@ public class Settings {
*
* @see SettingsExporter
*/
public static final int VERSION = 1;
public static final int VERSION = 2;
public static Map<String, String> validate(Map<String, SettingsDescription> settings,
Map<String, String> importedSettings, boolean useDefaultValues) {

View File

@ -12,7 +12,6 @@ import android.net.Uri;
import android.util.Log;
import com.fsck.k9.K9;
import com.fsck.k9.helper.AutoSyncHelper;
public class BootReceiver extends CoreReceiver {
@ -41,7 +40,7 @@ public class BootReceiver extends CoreReceiver {
} else if (ConnectivityManager.CONNECTIVITY_ACTION.equals(action)) {
MailService.connectivityChange(context, tmpWakeLockId);
tmpWakeLockId = null;
} else if (AutoSyncHelper.SYNC_CONN_STATUS_CHANGE.equals(action)) {
} else if ("com.android.sync.SYNC_CONN_STATUS_CHANGED".equals(action)) {
K9.BACKGROUND_OPS bOps = K9.getBackgroundOps();
if (bOps == K9.BACKGROUND_OPS.WHEN_CHECKED_AUTO_SYNC) {
MailService.actionReset(context, tmpWakeLockId);

View File

@ -199,9 +199,7 @@ public abstract class CoreService extends Service {
}
@Override
public final void onStart(Intent intent, int startId) {
// deprecated method but still used for backwards compatibility with Android version <2.0
public final int onStartCommand(Intent intent, int flags, int startId) {
/*
* When a process is killed due to low memory, it's later restarted and services that were
* started with START_STICKY are started with the intent being null.
@ -213,7 +211,7 @@ public abstract class CoreService extends Service {
*/
if (intent == null) {
stopSelf(startId);
return;
return START_NOT_STICKY;
}
// Acquire new wake lock
@ -253,9 +251,9 @@ public abstract class CoreService extends Service {
// Run the actual start-code of the service
mImmediateShutdown = true;
int startFlag;
try {
super.onStart(intent, startId);
startService(intent, startId);
startFlag = startService(intent, startId);
} finally {
try {
// Release the wake lock acquired at the start of this method
@ -267,9 +265,12 @@ public abstract class CoreService extends Service {
// this service.
if (mAutoShutdown && mImmediateShutdown && startId != -1) {
stopSelf(startId);
startFlag = START_NOT_STICKY;
}
} catch (Exception e) { /* ignore */ }
}
return startFlag;
}
/**
@ -371,7 +372,7 @@ public abstract class CoreService extends Service {
}
/**
* Subclasses need to implement this instead of overriding {@link #onStart(Intent, int)}.
* Subclasses need to implement this instead of overriding {@link #onStartCommand(Intent, int, int)}.
*
* <p>
* This allows {@link CoreService} to manage the service lifecycle, incl. wake lock management.
@ -382,8 +383,12 @@ public abstract class CoreService extends Service {
* @param startId
* A unique integer representing this specific request to start. Use with
* {@link #stopSelfResult(int)}.
*
* @return The return value indicates what semantics the system should use for the service's
* current started state. It may be one of the constants associated with the
* {@link Service#START_CONTINUATION_MASK} bits.
*/
public abstract void startService(Intent intent, int startId);
public abstract int startService(Intent intent, int startId);
@Override
public void onLowMemory() {

View File

@ -4,6 +4,7 @@ package com.fsck.k9.service;
import java.util.Collection;
import java.util.Date;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
@ -18,7 +19,6 @@ import com.fsck.k9.K9;
import com.fsck.k9.Preferences;
import com.fsck.k9.Account.FolderMode;
import com.fsck.k9.controller.MessagingController;
import com.fsck.k9.helper.AutoSyncHelper;
import com.fsck.k9.mail.Pusher;
public class MailService extends CoreService {
@ -84,7 +84,7 @@ public class MailService extends CoreService {
}
@Override
public void startService(Intent intent, int startId) {
public int startService(Intent intent, int startId) {
long startTime = System.currentTimeMillis();
boolean oldIsSyncDisabled = isSyncDisabled();
ConnectivityManager connectivityManager = (ConnectivityManager)getApplication().getSystemService(Context.CONNECTIVITY_SERVICE);
@ -98,12 +98,7 @@ public class MailService extends CoreService {
hasConnectivity = state == State.CONNECTED;
}
boolean backgroundData = connectivityManager.getBackgroundDataSetting();
boolean autoSync = true;
if (AutoSyncHelper.isAvailable()) {
autoSync = AutoSyncHelper.getMasterSyncAutomatically();
Log.i(K9.LOG_TAG, "AutoSync help is available, autoSync = " + autoSync);
}
boolean autoSync = ContentResolver.getMasterSyncAutomatically();
K9.BACKGROUND_OPS bOps = K9.getBackgroundOps();
@ -170,6 +165,8 @@ public class MailService extends CoreService {
if (K9.DEBUG)
Log.i(K9.LOG_TAG, "MailService.onStart took " + (System.currentTimeMillis() - startTime) + "ms");
return START_NOT_STICKY;
}
@Override

View File

@ -42,7 +42,7 @@ public class PollService extends CoreService {
}
@Override
public void startService(Intent intent, int startId) {
public int startService(Intent intent, int startId) {
if (START_SERVICE.equals(intent.getAction())) {
if (K9.DEBUG)
Log.i(K9.LOG_TAG, "PollService started with startId = " + startId);
@ -68,6 +68,7 @@ public class PollService extends CoreService {
stopSelf();
}
return START_NOT_STICKY;
}
@Override

View File

@ -27,7 +27,8 @@ public class PushService extends CoreService {
}
@Override
public void startService(Intent intent, int startId) {
public int startService(Intent intent, int startId) {
int startFlag = START_STICKY;
if (START_SERVICE.equals(intent.getAction())) {
if (K9.DEBUG)
Log.i(K9.LOG_TAG, "PushService started with startId = " + startId);
@ -35,8 +36,10 @@ public class PushService extends CoreService {
if (K9.DEBUG)
Log.i(K9.LOG_TAG, "PushService stopping with startId = " + startId);
stopSelf(startId);
startFlag = START_NOT_STICKY;
}
return startFlag;
}
@Override

View File

@ -34,7 +34,7 @@ public class RemoteControlService extends CoreService {
public static final int REMOTE_CONTROL_SERVICE_WAKE_LOCK_TIMEOUT = 20000;
@Override
public void startService(final Intent intent, final int startId) {
public int startService(final Intent intent, final int startId) {
if (K9.DEBUG)
Log.i(K9.LOG_TAG, "RemoteControlService started with startId = " + startId);
final Preferences preferences = Preferences.getPreferences(this);
@ -155,6 +155,8 @@ public class RemoteControlService extends CoreService {
}
, RemoteControlService.REMOTE_CONTROL_SERVICE_WAKE_LOCK_TIMEOUT, startId);
}
return START_NOT_STICKY;
}
}

View File

@ -113,12 +113,13 @@ public class SleepService extends CoreService {
}
@Override
public void startService(Intent intent, int startId) {
public int startService(Intent intent, int startId) {
try {
if (intent.getAction().startsWith(ALARM_FIRED)) {
Integer id = intent.getIntExtra(LATCH_ID, -1);
endSleep(id);
}
return START_NOT_STICKY;
}
finally {
stopSelf(startId);

View File

@ -26,7 +26,7 @@ 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.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
@ -256,7 +256,7 @@ public class MessageHeader extends LinearLayout {
* message view header. But do show "From", "To", and "Cc" again.
* This time including the email addresses. See issue 1805.
*/
Set<String> headerNames = new HashSet<String>(message.getHeaderNames());
Set<String> headerNames = new LinkedHashSet<String>(message.getHeaderNames());
headerNames.remove("Subject");
for (String headerName : headerNames) {
String[] headerValues = message.getHeader(headerName);

View File

@ -91,9 +91,7 @@ public class MessageWebView extends WebView {
// SINGLE_COLUMN layout was broken on Android < 2.2, so we
// administratively disable it
if (
(Integer.parseInt(Build.VERSION.SDK) > 7)
&& K9.mobileOptimizedLayout()) {
if (Build.VERSION.SDK_INT > 7 && K9.mobileOptimizedLayout()) {
webSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN);
} else {
webSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NARROW_COLUMNS);

View File

@ -0,0 +1,67 @@
package com.fsck.k9.mail.store;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import com.fsck.k9.mail.filter.PeekableInputStream;
import com.fsck.k9.mail.store.ImapResponseParser.ImapList;
import com.fsck.k9.mail.store.ImapResponseParser.ImapResponse;
import junit.framework.TestCase;
public class ImapResponseParserTest extends TestCase {
public void testSimpleOkResponse() throws IOException {
ImapResponseParser parser = createParser("* OK\r\n");
ImapResponse response = parser.readResponse();
assertNotNull(response);
assertEquals(1, response.size());
assertEquals("OK", response.get(0));
}
public void testOkResponseWithText() throws IOException {
ImapResponseParser parser = createParser("* OK Some text here\r\n");
ImapResponse response = parser.readResponse();
assertNotNull(response);
assertEquals(2, response.size());
assertEquals("OK", response.get(0));
assertEquals("Some text here", response.get(1));
}
public void testOkResponseWithRespTextCode() throws IOException {
ImapResponseParser parser = createParser("* OK [UIDVALIDITY 3857529045]\r\n");
ImapResponse response = parser.readResponse();
assertNotNull(response);
assertEquals(2, response.size());
assertEquals("OK", response.get(0));
assertTrue(response.get(1) instanceof ImapList);
ImapList respTextCode = (ImapList) response.get(1);
assertEquals(2, respTextCode.size());
assertEquals("UIDVALIDITY", respTextCode.get(0));
assertEquals("3857529045", respTextCode.get(1));
}
public void testOkResponseWithRespTextCodeAndText() throws IOException {
ImapResponseParser parser = createParser("* OK [token1 token2] {x} test [...]\r\n");
ImapResponse response = parser.readResponse();
assertNotNull(response);
assertEquals(3, response.size());
assertEquals("OK", response.get(0));
assertTrue(response.get(1) instanceof ImapList);
assertEquals("{x} test [...]", response.get(2));
ImapList respTextCode = (ImapList) response.get(1);
assertEquals(2, respTextCode.size());
assertEquals("token1", respTextCode.get(0));
assertEquals("token2", respTextCode.get(1));
}
private ImapResponseParser createParser(String response) {
ByteArrayInputStream in = new ByteArrayInputStream(response.getBytes());
PeekableInputStream pin = new PeekableInputStream(in);
return new ImapResponseParser(pin);
}
}