diff --git a/README.md b/README.md new file mode 100644 index 000000000..c36d9c188 --- /dev/null +++ b/README.md @@ -0,0 +1,83 @@ +# K-9 Mail +[![Build Status](https://k9mail.ci.cloudbees.com/job/master/badge/icon)](https://k9mail.ci.cloudbees.com/job/master/) +[![Join the chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/k9mail/k-9) + +K-9 Mail is an open-source email client for Android. + + +## Download + +K-9 Mail can be downloaded from a couple of sources: + +- [Google Play](https://play.google.com/store/apps/details?id=com.fsck.k9) +- [F-Droid](https://f-droid.org/repository/browse/?fdid=com.fsck.k9) +- [Github Releases](https://github.com/k9mail/k-9/releases) +- [Amazon Appstore for Android](http://www.amazon.com/dp/B004JK61K0) + +You might also be interested in becoming a [beta tester](https://github.com/k9mail/k-9/wiki/BetaTester) +or an [alpha tester](https://github.com/k9mail/k-9/wiki/AlphaTester) to get an early look at new versions. + + +## Release Notes + +Check out the [Release Notes](https://github.com/k9mail/k-9/wiki/ReleaseNotes) to find out what changed +in each version of K-9 Mail. + + +## Need Help? + +If the app is not behaving like it should, you might find these resources helpful: + +- [User Manual](https://github.com/k9mail/k-9/wiki/Manual) +- [Frequently Asked Questions](https://github.com/k9mail/k-9/wiki/FrequentlyAskedQuestions) +- [Support Forum/Mailing List](http://groups.google.com/group/k-9-mail) +- [Google+ Community](https://plus.google.com/communities/109228641058741937099) + + +## Translations + +Interested in helping to translate K-9 Mail? Contribute here: + +https://www.transifex.com/projects/p/k9mail/ + + +## Contributing + +Please fork this repository and contribute back using [pull requests](https://github.com/k9mail/k-9/pulls). + +Any contributions, large or small, major features, bug fixes, unit/integration tests are welcomed and appreciated +but will be thoroughly reviewed and discussed. +Please make sure you read the [Code Style Guidelines](https://github.com/k9mail/k-9/wiki/CodeStyle). + + +## Communication + +Aside from discussing changes in [pull requests](https://github.com/k9mail/k-9/pulls) and +[issues](https://github.com/k9mail/k-9/issues) we use the following communication services: + +- IRC chat, [#k-9 on the Freenode network](http://webchat.freenode.net/?channels=%23k-9) +- [Gitter](https://gitter.im/k9mail/k-9) +- [Developer mailing list](https://groups.google.com/forum/#!forum/k-9-dev) + + +## License + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +## Sponsors + +CloudBees' [FOSS program](https://www.cloudbees.com/resources/foss) allows us to use their DEV@cloud service for free. + +![built on DEV@cloud](https://www.cloudbees.com/sites/default/files/styles/large/public/Button-Built-on-CB-1.png) + diff --git a/build.gradle b/build.gradle index 6f91a86ec..16a8c0754 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:1.0.0' + classpath 'com.android.tools.build:gradle:1.2.3' classpath 'com.jakewharton.sdkmanager:gradle-plugin:0.12.0' } } diff --git a/config/lint/lint.xml b/config/lint/lint.xml index ad613c633..c38682ef5 100644 --- a/config/lint/lint.xml +++ b/config/lint/lint.xml @@ -1,6 +1,7 @@ + diff --git a/gradle/plugins/findbugs-android.gradle b/gradle/plugins/findbugs-android.gradle index ee83f1808..7a22caf72 100644 --- a/gradle/plugins/findbugs-android.gradle +++ b/gradle/plugins/findbugs-android.gradle @@ -1,14 +1,29 @@ apply plugin: 'findbugs' -check.dependsOn 'findbugs' -task findbugs(type: FindBugs, dependsOn: ['compileDebugJava', 'compileDebugTestJava']) { - ignoreFailures = true - classes = fileTree('build/intermediates/classes/debug/') + - fileTree('build/intermediates/classes/test/debug/') - source = project.android.sourceSets.main.java.getSrcDirs() + - project.android.sourceSets.androidTest.java.getSrcDirs() - classpath = files() - effort = 'max' - includeFilter = file("$rootProject.projectDir/config/findbugs/include_filter.xml") - excludeFilter = file("$rootProject.projectDir/config/findbugs/exclude_filter.xml") +afterEvaluate { + def variants = plugins.hasPlugin('com.android.application') ? + android.applicationVariants : android.libraryVariants + + variants.each { variant -> + def task = project.task("findBugs${variant.name.capitalize()}", type: FindBugs) { + group = 'verification' + description = "Run FindBugs for the ${variant.description}." + + effort = 'max' + ignoreFailures = true + + includeFilter = file("$rootProject.projectDir/config/findbugs/include_filter.xml") + excludeFilter = file("$rootProject.projectDir/config/findbugs/exclude_filter.xml") + + def variantCompile = variant.javaCompile + + classes = fileTree(variantCompile.destinationDir) + source = variantCompile.source + classpath = variantCompile.classpath.plus(project.files(android.bootClasspath)) + + dependsOn(variantCompile) + } + + tasks.getByName('check').dependsOn(task) + } } diff --git a/k9mail-library/build.gradle b/k9mail-library/build.gradle index b32294f08..317862f87 100644 --- a/k9mail-library/build.gradle +++ b/k9mail-library/build.gradle @@ -1,6 +1,7 @@ apply plugin: 'com.android.library' apply from: '../gradle/plugins/checkstyle-android.gradle' apply from: '../gradle/plugins/findbugs-android.gradle' +apply plugin: 'jacoco' repositories { jcenter() @@ -12,6 +13,17 @@ dependencies { compile 'commons-io:commons-io:2.4' compile 'com.jcraft:jzlib:1.0.7' compile 'com.beetstra.jutf7:jutf7:1.0.0' + + androidTestCompile 'com.android.support.test:testing-support-lib:0.1' + androidTestCompile 'com.madgag.spongycastle:pg:1.51.0.0' + + testCompile('org.robolectric:robolectric:3.0-rc3') { + exclude group: 'org.hamcrest', module: 'hamcrest-core' + } + testCompile 'org.hamcrest:hamcrest-core:1.3' + testCompile('junit:junit:4.10') { + exclude group: 'org.hamcrest', module: 'hamcrest-core' + } } android { @@ -21,6 +33,14 @@ android { defaultConfig { minSdkVersion 15 targetSdkVersion 21 + + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + + buildTypes { + debug { + testCoverageEnabled rootProject.testCoverage + } } lintOptions { @@ -40,5 +60,6 @@ android { exclude 'META-INF/LICENSE.txt' exclude 'META-INF/NOTICE' exclude 'META-INF/NOTICE.txt' + exclude 'LICENSE.txt' } } diff --git a/k9mail/src/androidTest/java/com/fsck/k9/mail/MessageTest.java b/k9mail-library/src/androidTest/java/com/fsck/k9/mail/MessageTest.java similarity index 100% rename from k9mail/src/androidTest/java/com/fsck/k9/mail/MessageTest.java rename to k9mail-library/src/androidTest/java/com/fsck/k9/mail/MessageTest.java diff --git a/k9mail/src/androidTest/java/com/fsck/k9/mail/PgpMimeMessageTest.java b/k9mail-library/src/androidTest/java/com/fsck/k9/mail/PgpMimeMessageTest.java similarity index 100% rename from k9mail/src/androidTest/java/com/fsck/k9/mail/PgpMimeMessageTest.java rename to k9mail-library/src/androidTest/java/com/fsck/k9/mail/PgpMimeMessageTest.java diff --git a/k9mail/src/androidTest/java/com/fsck/k9/mail/ReconstructMessageTest.java b/k9mail-library/src/androidTest/java/com/fsck/k9/mail/ReconstructMessageTest.java similarity index 100% rename from k9mail/src/androidTest/java/com/fsck/k9/mail/ReconstructMessageTest.java rename to k9mail-library/src/androidTest/java/com/fsck/k9/mail/ReconstructMessageTest.java diff --git a/k9mail/src/androidTest/java/com/fsck/k9/mail/ssl/TrustManagerFactoryTest.java b/k9mail-library/src/androidTest/java/com/fsck/k9/mail/ssl/TrustManagerFactoryTest.java similarity index 100% rename from k9mail/src/androidTest/java/com/fsck/k9/mail/ssl/TrustManagerFactoryTest.java rename to k9mail-library/src/androidTest/java/com/fsck/k9/mail/ssl/TrustManagerFactoryTest.java diff --git a/k9mail-library/src/main/java/com/fsck/k9/mail/K9MailLib.java b/k9mail-library/src/main/java/com/fsck/k9/mail/K9MailLib.java index 38c4b10dd..393622edf 100644 --- a/k9mail-library/src/main/java/com/fsck/k9/mail/K9MailLib.java +++ b/k9mail-library/src/main/java/com/fsck/k9/mail/K9MailLib.java @@ -55,7 +55,7 @@ public class K9MailLib { } } - public static interface DebugStatus { + public interface DebugStatus { boolean enabled(); boolean debugSensitive(); @@ -68,7 +68,7 @@ public class K9MailLib { debugStatus = status; } - private static interface WritableDebugStatus extends DebugStatus { + private interface WritableDebugStatus extends DebugStatus { void setEnabled(boolean enabled); void setSensitive(boolean sensitive); diff --git a/k9mail-library/src/main/java/com/fsck/k9/mail/Part.java b/k9mail-library/src/main/java/com/fsck/k9/mail/Part.java index d78132f21..937c3ee1e 100644 --- a/k9mail-library/src/main/java/com/fsck/k9/mail/Part.java +++ b/k9mail-library/src/main/java/com/fsck/k9/mail/Part.java @@ -22,6 +22,9 @@ public interface Part { String getContentId(); + /** + * Returns an array of headers of the given name. The array may be empty. + */ String[] getHeader(String name) throws MessagingException; boolean isMimeType(String mimeType) throws MessagingException; diff --git a/k9mail-library/src/main/java/com/fsck/k9/mail/internet/BinaryTempFileBody.java b/k9mail-library/src/main/java/com/fsck/k9/mail/internet/BinaryTempFileBody.java index 157b4046c..c14df1c5a 100644 --- a/k9mail-library/src/main/java/com/fsck/k9/mail/internet/BinaryTempFileBody.java +++ b/k9mail-library/src/main/java/com/fsck/k9/mail/internet/BinaryTempFileBody.java @@ -46,24 +46,26 @@ public class BinaryTempFileBody implements RawDataBody, SizeAware { try { File newFile = File.createTempFile("body", null, mTempDirectory); - OutputStream out = new FileOutputStream(newFile); + final OutputStream out = new FileOutputStream(newFile); try { + OutputStream wrappedOut = null; if (MimeUtil.ENC_QUOTED_PRINTABLE.equals(encoding)) { - out = new QuotedPrintableOutputStream(out, false); + wrappedOut = new QuotedPrintableOutputStream(out, false); } else if (MimeUtil.ENC_BASE64.equals(encoding)) { - out = new Base64OutputStream(out); + wrappedOut = new Base64OutputStream(out); } else { throw new RuntimeException("Target encoding not supported: " + encoding); } InputStream in = getInputStream(); try { - IOUtils.copy(in, out); + IOUtils.copy(in, wrappedOut); } finally { - in.close(); + IOUtils.closeQuietly(in); + IOUtils.closeQuietly(wrappedOut); } } finally { - out.close(); + IOUtils.closeQuietly(out); } mFile = newFile; @@ -100,7 +102,7 @@ public class BinaryTempFileBody implements RawDataBody, SizeAware { try { IOUtils.copy(in, out); } finally { - in.close(); + IOUtils.closeQuietly(in); } } diff --git a/k9mail-library/src/main/java/com/fsck/k9/mail/internet/BinaryTempFileMessageBody.java b/k9mail-library/src/main/java/com/fsck/k9/mail/internet/BinaryTempFileMessageBody.java index a47f2dffd..d4eb0435d 100644 --- a/k9mail-library/src/main/java/com/fsck/k9/mail/internet/BinaryTempFileMessageBody.java +++ b/k9mail-library/src/main/java/com/fsck/k9/mail/internet/BinaryTempFileMessageBody.java @@ -46,7 +46,7 @@ public class BinaryTempFileMessageBody extends BinaryTempFileBody implements Com IOUtils.copy(in, out); } } finally { - in.close(); + IOUtils.closeQuietly(in); } } diff --git a/k9mail-library/src/main/java/com/fsck/k9/mail/internet/JisSupport.java b/k9mail-library/src/main/java/com/fsck/k9/mail/internet/JisSupport.java index a6a30329a..fcc4086f0 100644 --- a/k9mail-library/src/main/java/com/fsck/k9/mail/internet/JisSupport.java +++ b/k9mail-library/src/main/java/com/fsck/k9/mail/internet/JisSupport.java @@ -49,8 +49,8 @@ class JisSupport { private static String getJisVariantFromMailerHeaders(Message message) throws MessagingException { - String mailerHeaders[] = message.getHeader("X-Mailer"); - if (mailerHeaders == null || mailerHeaders.length == 0) + String[] mailerHeaders = message.getHeader("X-Mailer"); + if (mailerHeaders.length == 0) return null; if (mailerHeaders[0].startsWith("iPhone Mail ") || mailerHeaders[0].startsWith("iPad Mail ")) @@ -61,8 +61,8 @@ class JisSupport { private static String getJisVariantFromReceivedHeaders(Part message) throws MessagingException { - String receivedHeaders[] = message.getHeader("Received"); - if (receivedHeaders == null) + String[] receivedHeaders = message.getHeader("Received"); + if (receivedHeaders.length == 0) return null; for (String receivedHeader : receivedHeaders) { diff --git a/k9mail-library/src/main/java/com/fsck/k9/mail/internet/MimeHeader.java b/k9mail-library/src/main/java/com/fsck/k9/mail/internet/MimeHeader.java index 318ea6da0..aafd72d5d 100644 --- a/k9mail-library/src/main/java/com/fsck/k9/mail/internet/MimeHeader.java +++ b/k9mail-library/src/main/java/com/fsck/k9/mail/internet/MimeHeader.java @@ -26,7 +26,7 @@ public class MimeHeader { public String getFirstHeader(String name) { String[] header = getHeader(name); - if (header == null) { + if (header.length == 0) { return null; } return header[0]; @@ -65,9 +65,6 @@ public class MimeHeader { values.add(field.getValue()); } } - if (values.isEmpty()) { - return null; - } return values.toArray(EMPTY_STRING_ARRAY); } diff --git a/k9mail-library/src/main/java/com/fsck/k9/mail/ssl/DefaultTrustedSocketFactory.java b/k9mail-library/src/main/java/com/fsck/k9/mail/ssl/DefaultTrustedSocketFactory.java index 3c9fe800f..c2a509dbe 100644 --- a/k9mail-library/src/main/java/com/fsck/k9/mail/ssl/DefaultTrustedSocketFactory.java +++ b/k9mail-library/src/main/java/com/fsck/k9/mail/ssl/DefaultTrustedSocketFactory.java @@ -1,16 +1,5 @@ package com.fsck.k9.mail.ssl; -import android.content.Context; -import android.text.TextUtils; -import android.util.Log; - -import com.fsck.k9.mail.MessagingException; - -import javax.net.ssl.KeyManager; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSocket; -import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.TrustManager; import java.io.IOException; import java.net.Socket; @@ -20,6 +9,17 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import android.content.Context; +import android.text.TextUtils; +import android.util.Log; + +import com.fsck.k9.mail.MessagingException; +import javax.net.ssl.KeyManager; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; + import static com.fsck.k9.mail.K9MailLib.LOG_TAG; @@ -27,11 +27,16 @@ import static com.fsck.k9.mail.K9MailLib.LOG_TAG; * Filter and reorder list of cipher suites and TLS versions. */ public class DefaultTrustedSocketFactory implements TrustedSocketFactory { - protected static final String ENABLED_CIPHERS[]; - protected static final String ENABLED_PROTOCOLS[]; + protected static final String[] ENABLED_CIPHERS; + protected static final String[] ENABLED_PROTOCOLS; - // Order taken from OpenSSL 1.0.1c - protected static final String ORDERED_KNOWN_CIPHERS[] = { + protected static final String[] ORDERED_KNOWN_CIPHERS = { + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", @@ -43,7 +48,6 @@ public class DefaultTrustedSocketFactory implements TrustedSocketFactory { "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA", "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA", - "SSL_RSA_WITH_3DES_EDE_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", @@ -51,14 +55,6 @@ public class DefaultTrustedSocketFactory implements TrustedSocketFactory { "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA", "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_128_CBC_SHA", - "TLS_ECDHE_RSA_WITH_RC4_128_SHA", - "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", - "TLS_ECDH_RSA_WITH_RC4_128_SHA", - "TLS_ECDH_ECDSA_WITH_RC4_128_SHA", - "SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA", - "SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA", - "SSL_RSA_WITH_RC4_128_SHA", - "SSL_RSA_WITH_RC4_128_MD5", }; protected static final String[] BLACKLISTED_CIPHERS = { @@ -69,10 +65,23 @@ public class DefaultTrustedSocketFactory implements TrustedSocketFactory { "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA", "SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", "SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", + "SSL_RSA_WITH_3DES_EDE_CBC_SHA", + "SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA", + "SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA", + "TLS_ECDHE_RSA_WITH_RC4_128_SHA", + "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", + "TLS_ECDH_RSA_WITH_RC4_128_SHA", + "TLS_ECDH_ECDSA_WITH_RC4_128_SHA", + "SSL_RSA_WITH_RC4_128_SHA", + "SSL_RSA_WITH_RC4_128_MD5", }; - protected static final String ORDERED_KNOWN_PROTOCOLS[] = { - "TLSv1.2", "TLSv1.1", "TLSv1", "SSLv3" + protected static final String[] ORDERED_KNOWN_PROTOCOLS = { + "TLSv1.2", "TLSv1.1", "TLSv1" + }; + + protected static final String[] BLACKLISTED_PROTOCOLS = { + "SSLv3" }; static { @@ -101,7 +110,7 @@ public class DefaultTrustedSocketFactory implements TrustedSocketFactory { reorder(enabledCiphers, ORDERED_KNOWN_CIPHERS, BLACKLISTED_CIPHERS); ENABLED_PROTOCOLS = (supportedProtocols == null) ? null : - reorder(supportedProtocols, ORDERED_KNOWN_PROTOCOLS, null); + reorder(supportedProtocols, ORDERED_KNOWN_PROTOCOLS, BLACKLISTED_PROTOCOLS); } public DefaultTrustedSocketFactory(Context context) { diff --git a/k9mail-library/src/main/java/com/fsck/k9/mail/store/imap/ImapStore.java b/k9mail-library/src/main/java/com/fsck/k9/mail/store/imap/ImapStore.java index e02d84d84..95137802a 100644 --- a/k9mail-library/src/main/java/com/fsck/k9/mail/store/imap/ImapStore.java +++ b/k9mail-library/src/main/java/com/fsck/k9/mail/store/imap/ImapStore.java @@ -1937,7 +1937,7 @@ public class ImapStore extends RemoteStore { */ String[] messageIdHeader = message.getHeader("Message-ID"); - if (messageIdHeader == null || messageIdHeader.length == 0) { + if (messageIdHeader.length == 0) { if (K9MailLib.isDebug()) Log.d(LOG_TAG, "Did not get a message-id in order to search for UID for " + getLogId()); return null; diff --git a/k9mail-library/src/main/java/com/fsck/k9/mail/transport/SmtpTransport.java b/k9mail-library/src/main/java/com/fsck/k9/mail/transport/SmtpTransport.java index c6292a569..1e2ccec6a 100644 --- a/k9mail-library/src/main/java/com/fsck/k9/mail/transport/SmtpTransport.java +++ b/k9mail-library/src/main/java/com/fsck/k9/mail/transport/SmtpTransport.java @@ -491,7 +491,6 @@ public class SmtpTransport extends Transport { private void sendMessageTo(List addresses, Message message) throws MessagingException { - boolean possibleSend = false; close(); open(); @@ -503,13 +502,11 @@ public class SmtpTransport extends Transport { // the size of messages, count the message's size before sending it if (mLargestAcceptableMessage > 0 && message.hasAttachments()) { if (message.calculateSize() > mLargestAcceptableMessage) { - MessagingException me = new MessagingException("Message too large for server"); - //TODO this looks rather suspicious... shouldn't it be true? - me.setPermanentFailure(possibleSend); - throw me; + throw new MessagingException("Message too large for server", true); } } + boolean entireMessageSent = false; Address[] from = message.getFrom(); try { executeSimpleCommand("MAIL FROM:" + "<" + from[0].getAddress() + ">" @@ -527,20 +524,14 @@ public class SmtpTransport extends Transport { // We use BufferedOutputStream. So make sure to call flush() ! msgOut.flush(); - possibleSend = true; // After the "\r\n." is attempted, we may have sent the message + entireMessageSent = true; // After the "\r\n." is attempted, we may have sent the message executeSimpleCommand("\r\n."); + } catch (NegativeSmtpReplyException e) { + throw e; } catch (Exception e) { MessagingException me = new MessagingException("Unable to send message", e); + me.setPermanentFailure(entireMessageSent); - // "5xx text" -responses are permanent failures - String msg = e.getMessage(); - if (msg != null && msg.startsWith("5")) { - Log.w(LOG_TAG, "handling 5xx SMTP error code as a permanent failure"); - possibleSend = false; - } - - //TODO this looks rather suspicious... why is possibleSend used, and why are 5xx NOT permanent (in contrast to the log text) - me.setPermanentFailure(possibleSend); throw me; } finally { close(); @@ -775,11 +766,15 @@ public class SmtpTransport extends Transport { private final String mReplyText; public NegativeSmtpReplyException(int replyCode, String replyText) { - super("Negative SMTP reply: " + replyCode + " " + replyText); + super("Negative SMTP reply: " + replyCode + " " + replyText, isPermanentSmtpError(replyCode)); mReplyCode = replyCode; mReplyText = replyText; } + private static boolean isPermanentSmtpError(int replyCode) { + return replyCode >= 500 && replyCode <= 599; + } + public int getReplyCode() { return mReplyCode; } diff --git a/tests-on-jvm/src/test/java/com/fsck/k9/mail/AddressTest.java b/k9mail-library/src/test/java/com/fsck/k9/mail/AddressTest.java similarity index 88% rename from tests-on-jvm/src/test/java/com/fsck/k9/mail/AddressTest.java rename to k9mail-library/src/test/java/com/fsck/k9/mail/AddressTest.java index 7b9c2b891..d8080f063 100644 --- a/tests-on-jvm/src/test/java/com/fsck/k9/mail/AddressTest.java +++ b/k9mail-library/src/test/java/com/fsck/k9/mail/AddressTest.java @@ -2,10 +2,15 @@ package com.fsck.k9.mail; import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; import static org.junit.Assert.assertEquals; +@RunWith(RobolectricTestRunner.class) +@Config(manifest = Config.NONE) public class AddressTest { /** * test the possibility to parse "From:" fields with no email. diff --git a/tests-on-jvm/src/test/java/com/fsck/k9/mail/Address_quoteAtoms.java b/k9mail-library/src/test/java/com/fsck/k9/mail/Address_quoteAtoms.java similarity index 87% rename from tests-on-jvm/src/test/java/com/fsck/k9/mail/Address_quoteAtoms.java rename to k9mail-library/src/test/java/com/fsck/k9/mail/Address_quoteAtoms.java index 6de516565..5a2fa88cb 100644 --- a/tests-on-jvm/src/test/java/com/fsck/k9/mail/Address_quoteAtoms.java +++ b/k9mail-library/src/test/java/com/fsck/k9/mail/Address_quoteAtoms.java @@ -2,10 +2,15 @@ package com.fsck.k9.mail; import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; import static org.junit.Assert.assertEquals; +@RunWith(RobolectricTestRunner.class) +@Config(manifest = Config.NONE) public class Address_quoteAtoms { @Test public void testNoQuote() { diff --git a/tests-on-jvm/src/test/java/com/fsck/k9/mail/filter/EOLConvertingOutputStreamTest.java b/k9mail-library/src/test/java/com/fsck/k9/mail/filter/EOLConvertingOutputStreamTest.java similarity index 100% rename from tests-on-jvm/src/test/java/com/fsck/k9/mail/filter/EOLConvertingOutputStreamTest.java rename to k9mail-library/src/test/java/com/fsck/k9/mail/filter/EOLConvertingOutputStreamTest.java diff --git a/tests-on-jvm/src/test/java/com/fsck/k9/mail/internet/CharsetSupportTest.java b/k9mail-library/src/test/java/com/fsck/k9/mail/internet/CharsetSupportTest.java similarity index 95% rename from tests-on-jvm/src/test/java/com/fsck/k9/mail/internet/CharsetSupportTest.java rename to k9mail-library/src/test/java/com/fsck/k9/mail/internet/CharsetSupportTest.java index d1918be58..3d5141884 100644 --- a/tests-on-jvm/src/test/java/com/fsck/k9/mail/internet/CharsetSupportTest.java +++ b/k9mail-library/src/test/java/com/fsck/k9/mail/internet/CharsetSupportTest.java @@ -2,10 +2,15 @@ package com.fsck.k9.mail.internet; import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; import static org.junit.Assert.assertEquals; +@RunWith(RobolectricTestRunner.class) +@Config(manifest = Config.NONE) public class CharsetSupportTest { @Test diff --git a/tests-on-jvm/src/test/java/com/fsck/k9/mail/internet/DecoderUtilTest.java b/k9mail-library/src/test/java/com/fsck/k9/mail/internet/DecoderUtilTest.java similarity index 94% rename from tests-on-jvm/src/test/java/com/fsck/k9/mail/internet/DecoderUtilTest.java rename to k9mail-library/src/test/java/com/fsck/k9/mail/internet/DecoderUtilTest.java index 0f58ddf70..5a24544c5 100644 --- a/tests-on-jvm/src/test/java/com/fsck/k9/mail/internet/DecoderUtilTest.java +++ b/k9mail-library/src/test/java/com/fsck/k9/mail/internet/DecoderUtilTest.java @@ -2,10 +2,15 @@ package com.fsck.k9.mail.internet; import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; import static org.junit.Assert.assertEquals; +@RunWith(RobolectricTestRunner.class) +@Config(manifest = Config.NONE) public class DecoderUtilTest { @Test diff --git a/tests-on-jvm/src/test/java/com/fsck/k9/mail/internet/MimeMessageParseTest.java b/k9mail-library/src/test/java/com/fsck/k9/mail/internet/MimeMessageParseTest.java similarity index 98% rename from tests-on-jvm/src/test/java/com/fsck/k9/mail/internet/MimeMessageParseTest.java rename to k9mail-library/src/test/java/com/fsck/k9/mail/internet/MimeMessageParseTest.java index 3fc7e4b4a..1dcc88f91 100644 --- a/tests-on-jvm/src/test/java/com/fsck/k9/mail/internet/MimeMessageParseTest.java +++ b/k9mail-library/src/test/java/com/fsck/k9/mail/internet/MimeMessageParseTest.java @@ -18,10 +18,15 @@ import com.fsck.k9.mail.Body; import com.fsck.k9.mail.BodyPart; import com.fsck.k9.mail.Message.RecipientType; import com.fsck.k9.mail.Multipart; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; import static org.junit.Assert.assertEquals; +@RunWith(RobolectricTestRunner.class) +@Config(manifest = Config.NONE) public class MimeMessageParseTest { @Before public void setup() { diff --git a/tests-on-jvm/src/test/java/com/fsck/k9/mail/internet/MimeUtilityTest.java b/k9mail-library/src/test/java/com/fsck/k9/mail/internet/MimeUtilityTest.java similarity index 100% rename from tests-on-jvm/src/test/java/com/fsck/k9/mail/internet/MimeUtilityTest.java rename to k9mail-library/src/test/java/com/fsck/k9/mail/internet/MimeUtilityTest.java diff --git a/tests-on-jvm/src/test/java/com/fsck/k9/mail/store/imap/ImapListTest.java b/k9mail-library/src/test/java/com/fsck/k9/mail/store/imap/ImapListTest.java similarity index 100% rename from tests-on-jvm/src/test/java/com/fsck/k9/mail/store/imap/ImapListTest.java rename to k9mail-library/src/test/java/com/fsck/k9/mail/store/imap/ImapListTest.java diff --git a/tests-on-jvm/src/test/java/com/fsck/k9/mail/store/imap/ImapResponseParserTest.java b/k9mail-library/src/test/java/com/fsck/k9/mail/store/imap/ImapResponseParserTest.java similarity index 95% rename from tests-on-jvm/src/test/java/com/fsck/k9/mail/store/imap/ImapResponseParserTest.java rename to k9mail-library/src/test/java/com/fsck/k9/mail/store/imap/ImapResponseParserTest.java index d9ce8b445..31e867cc5 100644 --- a/tests-on-jvm/src/test/java/com/fsck/k9/mail/store/imap/ImapResponseParserTest.java +++ b/k9mail-library/src/test/java/com/fsck/k9/mail/store/imap/ImapResponseParserTest.java @@ -3,6 +3,9 @@ package com.fsck.k9.mail.store.imap; import com.fsck.k9.mail.filter.PeekableInputStream; import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -16,6 +19,8 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +@RunWith(RobolectricTestRunner.class) +@Config(manifest = Config.NONE) public class ImapResponseParserTest { @Test public void testSimpleOkResponse() throws IOException { diff --git a/tests-on-jvm/src/test/java/com/fsck/k9/mail/store/imap/ImapStoreUriTest.java b/k9mail-library/src/test/java/com/fsck/k9/mail/store/imap/ImapStoreUriTest.java similarity index 100% rename from tests-on-jvm/src/test/java/com/fsck/k9/mail/store/imap/ImapStoreUriTest.java rename to k9mail-library/src/test/java/com/fsck/k9/mail/store/imap/ImapStoreUriTest.java diff --git a/tests-on-jvm/src/test/java/com/fsck/k9/mail/store/imap/ImapUtilityTest.java b/k9mail-library/src/test/java/com/fsck/k9/mail/store/imap/ImapUtilityTest.java similarity index 96% rename from tests-on-jvm/src/test/java/com/fsck/k9/mail/store/imap/ImapUtilityTest.java rename to k9mail-library/src/test/java/com/fsck/k9/mail/store/imap/ImapUtilityTest.java index 283660fbb..f91ec80b1 100644 --- a/tests-on-jvm/src/test/java/com/fsck/k9/mail/store/imap/ImapUtilityTest.java +++ b/k9mail-library/src/test/java/com/fsck/k9/mail/store/imap/ImapUtilityTest.java @@ -18,12 +18,17 @@ package com.fsck.k9.mail.store.imap; import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; import java.util.List; import static org.junit.Assert.assertArrayEquals; +@RunWith(RobolectricTestRunner.class) +@Config(manifest = Config.NONE) public class ImapUtilityTest { @Test public void testGetImapSequenceValues() { diff --git a/k9mail/build.gradle b/k9mail/build.gradle index 1bbc410d4..d4b0dfb28 100644 --- a/k9mail/build.gradle +++ b/k9mail/build.gradle @@ -2,12 +2,10 @@ apply plugin: 'android-sdk-manager' apply plugin: 'com.android.application' apply from: '../gradle/plugins/checkstyle-android.gradle' apply from: '../gradle/plugins/findbugs-android.gradle' +apply plugin: 'jacoco' repositories { jcenter() - maven { - url "https://oss.sonatype.org/content/repositories/snapshots/" - } } dependencies { @@ -24,10 +22,17 @@ dependencies { androidTestCompile 'com.android.support.test:testing-support-lib:0.1' androidTestCompile 'com.android.support.test.espresso:espresso-core:2.0' - androidTestCompile('com.icegreen:greenmail:1.4.1-SNAPSHOT') { + androidTestCompile('com.icegreen:greenmail:1.4.1') { exclude group: 'junit' } - androidTestCompile 'com.madgag.spongycastle:pg:1.51.0.0' + + testCompile('org.robolectric:robolectric:3.0-rc3') { + exclude group: 'org.hamcrest', module: 'hamcrest-core' + } + testCompile 'org.hamcrest:hamcrest-core:1.3' + testCompile('junit:junit:4.10') { + exclude group: 'org.hamcrest', module: 'hamcrest-core' + } } android { diff --git a/k9mail/src/main/AndroidManifest.xml b/k9mail/src/main/AndroidManifest.xml index ca890d986..6e19e7360 100644 --- a/k9mail/src/main/AndroidManifest.xml +++ b/k9mail/src/main/AndroidManifest.xml @@ -2,8 +2,8 @@ + android:versionCode="23060" + android:versionName="5.106"> = 1)) { + if (inReplyTo.length >= 1) { mInReplyTo = inReplyTo[0]; } // Read References header from draft final String[] references = message.getHeader("References"); - if ((references != null) && (references.length >= 1)) { + if (references.length >= 1) { mReferences = references[0]; } @@ -2379,8 +2379,10 @@ public class MessageCompose extends K9Activity implements OnClickListener, // Decode the identity header when loading a draft. // See buildIdentityHeader(TextBody) for a detailed description of the composition of this blob. Map k9identity = new HashMap(); - if (message.getHeader(K9.IDENTITY_HEADER) != null && message.getHeader(K9.IDENTITY_HEADER).length > 0 && message.getHeader(K9.IDENTITY_HEADER)[0] != null) { - k9identity = IdentityHeaderParser.parse(message.getHeader(K9.IDENTITY_HEADER)[0]); + String[] identityHeaders = message.getHeader(K9.IDENTITY_HEADER); + + if (identityHeaders.length > 0 && identityHeaders[0] != null) { + k9identity = IdentityHeaderParser.parse(identityHeaders[0]); } Identity newIdentity = new Identity(); diff --git a/k9mail/src/main/java/com/fsck/k9/activity/MessageReference.java b/k9mail/src/main/java/com/fsck/k9/activity/MessageReference.java index e21e10112..107fae67b 100644 --- a/k9mail/src/main/java/com/fsck/k9/activity/MessageReference.java +++ b/k9mail/src/main/java/com/fsck/k9/activity/MessageReference.java @@ -176,9 +176,9 @@ public class MessageReference implements Parcelable { String folderName = source.readString(); String flag = source.readString(); if (flag != null) { - ref = new MessageReference(uid, accountUuid, folderName, Flag.valueOf(flag)); + ref = new MessageReference(accountUuid, folderName, uid, Flag.valueOf(flag)); } else { - ref = new MessageReference(uid, accountUuid, folderName, null); + ref = new MessageReference(accountUuid, folderName, uid, null); } return ref; } diff --git a/k9mail/src/main/java/com/fsck/k9/activity/setup/AccountSetupIncoming.java b/k9mail/src/main/java/com/fsck/k9/activity/setup/AccountSetupIncoming.java index dfca997fb..ca5013ad9 100644 --- a/k9mail/src/main/java/com/fsck/k9/activity/setup/AccountSetupIncoming.java +++ b/k9mail/src/main/java/com/fsck/k9/activity/setup/AccountSetupIncoming.java @@ -153,6 +153,8 @@ public class AccountSetupIncoming extends K9Activity implements OnClickListener mAccount = Preferences.getPreferences(this).getAccount(accountUuid); } + boolean editSettings = Intent.ACTION_EDIT.equals(getIntent().getAction()); + try { ServerSettings settings = RemoteStore.decodeStoreUri(mAccount.getStoreUri()); @@ -203,7 +205,7 @@ public class AccountSetupIncoming extends K9Activity implements OnClickListener findViewById(R.id.webdav_owa_path_section).setVisibility(View.GONE); findViewById(R.id.webdav_auth_path_section).setVisibility(View.GONE); - if (!Intent.ACTION_EDIT.equals(getIntent().getAction())) { + if (!editSettings) { findViewById(R.id.imap_folder_setup_section).setVisibility(View.GONE); } } else if (Type.WebDAV == settings.type) { @@ -237,7 +239,9 @@ public class AccountSetupIncoming extends K9Activity implements OnClickListener throw new Exception("Unknown account type: " + mAccount.getStoreUri()); } - mAccount.setDeletePolicy(AccountCreator.getDefaultDeletePolicy(settings.type)); + if (!editSettings) { + mAccount.setDeletePolicy(AccountCreator.getDefaultDeletePolicy(settings.type)); + } // Note that mConnectionSecurityChoices is configured above based on server type ConnectionSecurityAdapter securityTypesAdapter = diff --git a/k9mail/src/main/java/com/fsck/k9/activity/setup/Prefs.java b/k9mail/src/main/java/com/fsck/k9/activity/setup/Prefs.java index 96d34846d..c4482e9cf 100644 --- a/k9mail/src/main/java/com/fsck/k9/activity/setup/Prefs.java +++ b/k9mail/src/main/java/com/fsck/k9/activity/setup/Prefs.java @@ -79,6 +79,8 @@ public class Prefs extends K9PreferenceActivity { private static final String PREFERENCE_MESSAGEVIEW_RETURN_TO_LIST = "messageview_return_to_list"; private static final String PREFERENCE_MESSAGEVIEW_SHOW_NEXT = "messageview_show_next"; private static final String PREFERENCE_QUIET_TIME_ENABLED = "quiet_time_enabled"; + private static final String PREFERENCE_DISABLE_NOTIFICATION_DURING_QUIET_TIME = + "disable_notifications_during_quiet_time"; private static final String PREFERENCE_QUIET_TIME_STARTS = "quiet_time_starts"; private static final String PREFERENCE_QUIET_TIME_ENDS = "quiet_time_ends"; private static final String PREFERENCE_NOTIF_QUICK_DELETE = "notification_quick_delete"; @@ -142,6 +144,7 @@ public class Prefs extends K9PreferenceActivity { private CheckBoxListPreference mVisibleRefileActions; private CheckBoxPreference mQuietTimeEnabled; + private CheckBoxPreference mDisableNotificationDuringQuietTime; private com.fsck.k9.preferences.TimePickerPreference mQuietTimeStarts; private com.fsck.k9.preferences.TimePickerPreference mQuietTimeEnds; private ListPreference mNotificationQuickDelete; @@ -309,6 +312,9 @@ public class Prefs extends K9PreferenceActivity { mQuietTimeEnabled = (CheckBoxPreference) findPreference(PREFERENCE_QUIET_TIME_ENABLED); mQuietTimeEnabled.setChecked(K9.getQuietTimeEnabled()); + mDisableNotificationDuringQuietTime = (CheckBoxPreference) findPreference( + PREFERENCE_DISABLE_NOTIFICATION_DURING_QUIET_TIME); + mDisableNotificationDuringQuietTime.setChecked(!K9.isNotificationDuringQuietTimeEnabled()); mQuietTimeStarts = (TimePickerPreference) findPreference(PREFERENCE_QUIET_TIME_STARTS); mQuietTimeStarts.setDefaultValue(K9.getQuietTimeStarts()); mQuietTimeStarts.setSummary(K9.getQuietTimeStarts()); @@ -485,6 +491,7 @@ public class Prefs extends K9PreferenceActivity { K9.setMessageViewCopyActionVisible(enabledRefileActions[VISIBLE_REFILE_ACTIONS_COPY]); K9.setMessageViewSpamActionVisible(enabledRefileActions[VISIBLE_REFILE_ACTIONS_SPAM]); + K9.setNotificationDuringQuietTimeEnabled(!mDisableNotificationDuringQuietTime.isChecked()); K9.setQuietTimeStarts(mQuietTimeStarts.getTime()); K9.setQuietTimeEnds(mQuietTimeEnds.getTime()); K9.setWrapFolderNames(mWrapFolderNames.isChecked()); diff --git a/k9mail/src/main/java/com/fsck/k9/controller/MessagingController.java b/k9mail/src/main/java/com/fsck/k9/controller/MessagingController.java index 3eff01fe5..4f08559c7 100644 --- a/k9mail/src/main/java/com/fsck/k9/controller/MessagingController.java +++ b/k9mail/src/main/java/com/fsck/k9/controller/MessagingController.java @@ -211,8 +211,8 @@ public class MessagingController implements Runnable { /** * List of messages that should be used for the inbox-style overview. * It's sorted from newest to oldest message. - * Don't modify this list directly, but use {@link addMessage} and - * {@link removeMatchingMessage} instead. + * Don't modify this list directly, but use {@link #addMessage(com.fsck.k9.mailstore.LocalMessage)} and + * {@link #removeMatchingMessage(android.content.Context, com.fsck.k9.activity.MessageReference)} instead. */ LinkedList messages; /** @@ -1294,16 +1294,17 @@ public class MessagingController implements Runnable { Log.d(K9.LOG_TAG, "SYNC: About to fetch " + unsyncedMessages.size() + " unsynced messages for folder " + folder); - fetchUnsyncedMessages(account, remoteFolder, localFolder, unsyncedMessages, smallMessages, largeMessages, progress, todo, fp); + fetchUnsyncedMessages(account, remoteFolder, unsyncedMessages, smallMessages, largeMessages, progress, todo, fp); - // If a message didn't exist, messageFinished won't be called, but we shouldn't try again - // If we got here, nothing failed + String updatedPushState = localFolder.getPushState(); for (Message message : unsyncedMessages) { - String newPushState = remoteFolder.getNewPushState(localFolder.getPushState(), message); + String newPushState = remoteFolder.getNewPushState(updatedPushState, message); if (newPushState != null) { - localFolder.setPushState(newPushState); + updatedPushState = newPushState; } } + localFolder.setPushState(updatedPushState); + if (K9.DEBUG) { Log.d(K9.LOG_TAG, "SYNC: Synced unsynced messages for folder " + folder); } @@ -1441,7 +1442,6 @@ public class MessagingController implements Runnable { } private void fetchUnsyncedMessages(final Account account, final Folder remoteFolder, - final LocalFolder localFolder, List unsyncedMessages, final List smallMessages, final List largeMessages, @@ -1452,22 +1452,12 @@ public class MessagingController implements Runnable { final Date earliestDate = account.getEarliestPollDate(); - /* - * Messages to be batch written - */ - final List chunk = new ArrayList(UNSYNC_CHUNK_SIZE); - remoteFolder.fetch(unsyncedMessages, fp, new MessageRetrievalListener() { @Override public void messageFinished(T message, int number, int ofTotal) { try { - String newPushState = remoteFolder.getNewPushState(localFolder.getPushState(), message); - if (newPushState != null) { - localFolder.setPushState(newPushState); - } if (message.isSet(Flag.DELETED) || message.olderThan(earliestDate)) { - if (K9.DEBUG) { if (message.isSet(Flag.DELETED)) { Log.v(K9.LOG_TAG, "Newly downloaded message " + account + ":" + folder + ":" + message.getUid() @@ -1490,24 +1480,6 @@ public class MessagingController implements Runnable { } else { smallMessages.add(message); } - - // And include it in the view - if (message.getSubject() != null && message.getFrom() != null) { - /* - * We check to make sure that we got something worth - * showing (subject and from) because some protocols - * (POP) may not be able to give us headers for - * ENVELOPE, only size. - */ - - // keep message for delayed storing - chunk.add(message); - - if (chunk.size() >= UNSYNC_CHUNK_SIZE) { - writeUnsyncedMessages(chunk, localFolder, account, folder); - chunk.clear(); - } - } } catch (Exception e) { Log.e(K9.LOG_TAG, "Error while storing downloaded message.", e); addErrorMessage(account, null, e); @@ -1523,48 +1495,8 @@ public class MessagingController implements Runnable { } }); - if (!chunk.isEmpty()) { - writeUnsyncedMessages(chunk, localFolder, account, folder); - chunk.clear(); - } } - /** - * Actual storing of messages - * - *
- * FIXME: This method should really be moved in the above MessageRetrievalListener once {@link MessageRetrievalListener#messagesFinished(int)} is properly invoked by various stores - * - * @param messages Never null. - * @param localFolder - * @param account - * @param folder - */ - private void writeUnsyncedMessages(final List messages, final LocalFolder localFolder, final Account account, final String folder) { - if (K9.DEBUG) { - Log.v(K9.LOG_TAG, "Batch writing " + Integer.toString(messages.size()) + " messages"); - } - try { - // Store the new message locally - localFolder.appendMessages(messages); - - for (final Message message : messages) { - final LocalMessage localMessage = localFolder.getMessage(message.getUid()); - syncFlags(localMessage, message); - if (K9.DEBUG) - Log.v(K9.LOG_TAG, "About to notify listeners that we got a new unsynced message " - + account + ":" + folder + ":" + message.getUid()); - for (final MessagingListener l : getListeners()) { - l.synchronizeMailboxAddOrUpdateMessage(account, folder, localMessage); - } - } - } catch (final Exception e) { - Log.e(K9.LOG_TAG, "Error while storing downloaded message.", e); - addErrorMessage(account, null, e); - } - } - - private boolean shouldImportMessage(final Account account, final String folder, final Message message, final AtomicInteger progress, final Date earliestDate) { if (account.isSearchByDateCapable() && message.olderThan(earliestDate)) { @@ -3435,6 +3367,7 @@ public class MessagingController implements Runnable { public void sendPendingMessagesSynchronous(final Account account) { Folder localFolder = null; Exception lastFailure = null; + boolean wasPermanentFailure = false; try { Store localStore = account.getLocalStore(); localFolder = localStore.getFolder( @@ -3492,7 +3425,7 @@ public class MessagingController implements Runnable { try { - if (message.getHeader(K9.IDENTITY_HEADER) != null) { + if (message.getHeader(K9.IDENTITY_HEADER).length > 0) { Log.v(K9.LOG_TAG, "The user has set the Outbox and Drafts folder to the same thing. " + "This message appears to be a draft, so K-9 will not send it"); continue; @@ -3531,38 +3464,40 @@ public class MessagingController implements Runnable { processPendingCommands(account); } - } catch (Exception e) { - // 5.x.x errors from the SMTP server are "PERMFAIL" - // move the message over to drafts rather than leaving it in the outbox - // This is a complete hack, but is worlds better than the previous - // "don't even bother" functionality - if (getRootCauseMessage(e).startsWith("5")) { - localFolder.moveMessages(Collections.singletonList(message), (LocalFolder) localStore.getFolder(account.getDraftsFolderName())); - } + } catch (CertificateValidationException e) { + lastFailure = e; + wasPermanentFailure = false; notifyUserIfCertificateProblem(context, e, account, false); - addErrorMessage(account, "Failed to send message", e); - message.setFlag(Flag.X_SEND_FAILED, true); - Log.e(K9.LOG_TAG, "Failed to send message", e); - for (MessagingListener l : getListeners()) { - l.synchronizeMailboxFailed(account, localFolder.getName(), getRootCauseMessage(e)); - } + handleSendFailure(account, localStore, localFolder, message, e, wasPermanentFailure); + } catch (MessagingException e) { lastFailure = e; + wasPermanentFailure = e.isPermanentFailure(); + + handleSendFailure(account, localStore, localFolder, message, e, wasPermanentFailure); + } catch (Exception e) { + lastFailure = e; + wasPermanentFailure = true; + + handleSendFailure(account, localStore, localFolder, message, e, wasPermanentFailure); } } catch (Exception e) { - Log.e(K9.LOG_TAG, "Failed to fetch message for sending", e); - for (MessagingListener l : getListeners()) { - l.synchronizeMailboxFailed(account, localFolder.getName(), getRootCauseMessage(e)); - } - addErrorMessage(account, "Failed to fetch message for sending", e); lastFailure = e; + wasPermanentFailure = false; + + Log.e(K9.LOG_TAG, "Failed to fetch message for sending", e); + + addErrorMessage(account, "Failed to fetch message for sending", e); + notifySynchronizeMailboxFailed(account, localFolder, e); } } + for (MessagingListener l : getListeners()) { l.sendPendingMessagesCompleted(account); } + if (lastFailure != null) { - if (getRootCauseMessage(lastFailure).startsWith("5")) { + if (wasPermanentFailure) { notifySendPermFailed(account, lastFailure); } else { notifySendTempFailed(account, lastFailure); @@ -3585,6 +3520,35 @@ public class MessagingController implements Runnable { } } + private void handleSendFailure(Account account, Store localStore, Folder localFolder, Message message, + Exception exception, boolean permanentFailure) throws MessagingException { + + Log.e(K9.LOG_TAG, "Failed to send message", exception); + + if (permanentFailure) { + moveMessageToDraftsFolder(account, localFolder, localStore, message); + } + + addErrorMessage(account, "Failed to send message", exception); + message.setFlag(Flag.X_SEND_FAILED, true); + + notifySynchronizeMailboxFailed(account, localFolder, exception); + } + + private void moveMessageToDraftsFolder(Account account, Folder localFolder, Store localStore, Message message) + throws MessagingException { + LocalFolder draftsFolder = (LocalFolder) localStore.getFolder(account.getDraftsFolderName()); + localFolder.moveMessages(Collections.singletonList(message), draftsFolder); + } + + private void notifySynchronizeMailboxFailed(Account account, Folder localFolder, Exception exception) { + String folderName = localFolder.getName(); + String errorMessage = getRootCauseMessage(exception); + for (MessagingListener listener : getListeners()) { + listener.synchronizeMailboxFailed(account, folderName, errorMessage); + } + } + public void getAccountStats(final Context context, final Account account, final MessagingListener listener) { @@ -4751,8 +4715,11 @@ public class MessagingController implements Runnable { /** * Creates a notification of a newly received message. */ - private void notifyAccount(Context context, Account account, - LocalMessage message, int previousUnreadMessageCount) { + private void notifyAccount(Context context, Account account, LocalMessage message, int previousUnreadMessageCount) { + if (K9.isQuietTime() && !K9.isNotificationDuringQuietTimeEnabled()) { + return; + } + final NotificationData data = getNotificationData(account, previousUnreadMessageCount); synchronized (data) { notifyAccountWithDataLocked(context, account, message, data); @@ -4853,6 +4820,7 @@ public class MessagingController implements Runnable { NotificationActionService.getReplyIntent(context, account, message.makeMessageReference())); } + // Mark Read on phone builder.addAction( platformSupportsLockScreenNotifications() ? R.drawable.ic_action_mark_as_read_dark_vector @@ -4864,15 +4832,51 @@ public class MessagingController implements Runnable { boolean showDeleteAction = deleteOption == NotificationQuickDelete.ALWAYS || (deleteOption == NotificationQuickDelete.FOR_SINGLE_MSG && newMessages == 1); + NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender(); if (showDeleteAction) { // we need to pass the action directly to the activity, otherwise the // status bar won't be pulled up and we won't see the confirmation (if used) + + // Delete on phone builder.addAction( platformSupportsLockScreenNotifications() ? R.drawable.ic_action_delete_dark_vector : R.drawable.ic_action_delete_dark, context.getString(R.string.notification_action_delete), NotificationDeleteConfirmation.getIntent(context, account, allRefs)); + + // Delete on wear only if no confirmation is required + if (!K9.confirmDeleteFromNotification()) { + NotificationCompat.Action wearActionDelete = + new NotificationCompat.Action.Builder( + R.drawable.ic_action_delete_dark, + context.getString(R.string.notification_action_delete), + NotificationDeleteConfirmation.getIntent(context, account, allRefs)) + .build(); + builder.extend(wearableExtender.addAction(wearActionDelete)); + } + } + if (NotificationActionService.isArchiveAllMessagesWearAvaliable(context, account, data.messages)) { + + // Archive on wear + NotificationCompat.Action wearActionArchive = + new NotificationCompat.Action.Builder( + R.drawable.ic_action_delete_dark, + context.getString(R.string.notification_action_archive), + NotificationActionService.getArchiveAllMessagesIntent(context, account, allRefs)) + .build(); + builder.extend(wearableExtender.addAction(wearActionArchive)); + } + if (NotificationActionService.isSpamAllMessagesWearAvaliable(context, account, data.messages)) { + + // Archive on wear + NotificationCompat.Action wearActionSpam = + new NotificationCompat.Action.Builder( + R.drawable.ic_action_delete_dark, + context.getString(R.string.notification_action_spam), + NotificationActionService.getSpamAllMessagesIntent(context, account, allRefs)) + .build(); + builder.extend(wearableExtender.addAction(wearActionSpam)); } } else { String accountNotice = context.getString(R.string.notification_new_one_account_fmt, diff --git a/k9mail/src/main/java/com/fsck/k9/fragment/MessageListFragment.java b/k9mail/src/main/java/com/fsck/k9/fragment/MessageListFragment.java index 4a5bf8f07..78c14f8db 100644 --- a/k9mail/src/main/java/com/fsck/k9/fragment/MessageListFragment.java +++ b/k9mail/src/main/java/com/fsck/k9/fragment/MessageListFragment.java @@ -1024,15 +1024,10 @@ public class MessageListFragment extends Fragment implements OnItemClickListener } private String getFolderNameById(Account account, long folderId) { - try { - Folder folder = getFolderById(account, folderId); - if (folder != null) { - return folder.getName(); - } - } catch (Exception e) { - Log.e(K9.LOG_TAG, "getFolderNameById() failed.", e); + Folder folder = getFolderById(account, folderId); + if (folder != null) { + return folder.getName(); } - return null; } @@ -1042,9 +1037,8 @@ public class MessageListFragment extends Fragment implements OnItemClickListener LocalFolder localFolder = localStore.getFolderById(folderId); localFolder.open(Folder.OPEN_MODE_RO); return localFolder; - } catch (Exception e) { - Log.e(K9.LOG_TAG, "getFolderNameById() failed.", e); - return null; + } catch (MessagingException e) { + throw new RuntimeException(e); } } @@ -3162,10 +3156,8 @@ public class MessageListFragment extends Fragment implements OnItemClickListener try { return folder.getMessage(uid); } catch (MessagingException e) { - Log.e(K9.LOG_TAG, "Something went wrong while fetching a message", e); + throw new RuntimeException(e); } - - return null; } private List getCheckedMessages() { diff --git a/k9mail/src/main/java/com/fsck/k9/mailstore/LocalFolder.java b/k9mail/src/main/java/com/fsck/k9/mailstore/LocalFolder.java index 7f8fa7bef..4ae04b723 100644 --- a/k9mail/src/main/java/com/fsck/k9/mailstore/LocalFolder.java +++ b/k9mail/src/main/java/com/fsck/k9/mailstore/LocalFolder.java @@ -1311,6 +1311,8 @@ public class LocalFolder extends Folder implements Serializable { multipartToContentValues(cv, (Multipart) body); } else if (body == null) { missingPartToContentValues(cv, part); + } else if (body instanceof Message) { + messageMarkerToContentValues(cv); } else { file = leafPartToContentValues(cv, part, body); } @@ -1344,6 +1346,10 @@ public class LocalFolder extends Folder implements Serializable { cv.put("decoded_body_size", attachment.size); } + private void messageMarkerToContentValues(ContentValues cv) throws MessagingException { + cv.put("data_location", DataLocation.CHILD_PART_CONTAINS_DATA); + } + private File leafPartToContentValues(ContentValues cv, Part part, Body body) throws MessagingException, IOException { AttachmentViewInfo attachment = LocalMessageExtractor.extractAttachmentInfo(part); @@ -1451,7 +1457,7 @@ public class LocalFolder extends Folder implements Serializable { private String getTransferEncoding(Part part) throws MessagingException { String[] contentTransferEncoding = part.getHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING); - if (contentTransferEncoding != null && contentTransferEncoding.length > 0) { + if (contentTransferEncoding.length > 0) { return contentTransferEncoding[0].toLowerCase(Locale.US); } @@ -1466,6 +1472,9 @@ public class LocalFolder extends Folder implements Serializable { BodyPart childPart = multipart.getBodyPart(i); stack.push(new PartContainer(parentMessageId, childPart)); } + } else if (body instanceof Message) { + Message innerMessage = (Message) body; + stack.push(new PartContainer(parentMessageId, innerMessage)); } } @@ -1812,14 +1821,14 @@ public class LocalFolder extends Folder implements Serializable { // Get the message IDs from the "References" header line String[] referencesArray = message.getHeader("References"); List messageIds = null; - if (referencesArray != null && referencesArray.length > 0) { + if (referencesArray.length > 0) { messageIds = Utility.extractMessageIds(referencesArray[0]); } // Append the first message ID from the "In-Reply-To" header line String[] inReplyToArray = message.getHeader("In-Reply-To"); String inReplyTo; - if (inReplyToArray != null && inReplyToArray.length > 0) { + if (inReplyToArray.length > 0) { inReplyTo = Utility.extractMessageId(inReplyToArray[0]); if (inReplyTo != null) { if (messageIds == null) { @@ -1994,5 +2003,6 @@ public class LocalFolder extends Folder implements Serializable { static final int MISSING = 0; static final int IN_DATABASE = 1; static final int ON_DISK = 2; + static final int CHILD_PART_CONTAINS_DATA = 3; } } diff --git a/k9mail/src/main/java/com/fsck/k9/mailstore/LocalMessageExtractor.java b/k9mail/src/main/java/com/fsck/k9/mailstore/LocalMessageExtractor.java index 2835046a4..06d64bc1f 100644 --- a/k9mail/src/main/java/com/fsck/k9/mailstore/LocalMessageExtractor.java +++ b/k9mail/src/main/java/com/fsck/k9/mailstore/LocalMessageExtractor.java @@ -560,7 +560,7 @@ public class LocalMessageExtractor { // attachments. if (contentDisposition != null && MimeUtility.getHeaderParameter(contentDisposition, null).matches("^(?i:inline)") && - part.getHeader(MimeHeader.HEADER_CONTENT_ID) != null) { + part.getHeader(MimeHeader.HEADER_CONTENT_ID).length > 0) { firstClassAttachment = false; } diff --git a/k9mail/src/main/java/com/fsck/k9/preferences/GlobalSettings.java b/k9mail/src/main/java/com/fsck/k9/preferences/GlobalSettings.java index be3de86d9..6ac2ac684 100644 --- a/k9mail/src/main/java/com/fsck/k9/preferences/GlobalSettings.java +++ b/k9mail/src/main/java/com/fsck/k9/preferences/GlobalSettings.java @@ -266,6 +266,9 @@ public class GlobalSettings { new V(38, new EnumSetting(NotificationQuickDelete.class, NotificationQuickDelete.NEVER)) )); + s.put("notificationDuringQuietTimeEnabled", Settings.versions( + new V(39, new BooleanSetting(true)) + )); SETTINGS = Collections.unmodifiableMap(s); diff --git a/k9mail/src/main/java/com/fsck/k9/preferences/Settings.java b/k9mail/src/main/java/com/fsck/k9/preferences/Settings.java index 9c466dcf0..b9c55a7f3 100644 --- a/k9mail/src/main/java/com/fsck/k9/preferences/Settings.java +++ b/k9mail/src/main/java/com/fsck/k9/preferences/Settings.java @@ -35,7 +35,7 @@ public class Settings { * * @see SettingsExporter */ - public static final int VERSION = 38; + public static final int VERSION = 39; public static Map validate(int version, Map> settings, diff --git a/k9mail/src/main/java/com/fsck/k9/provider/MessageProvider.java b/k9mail/src/main/java/com/fsck/k9/provider/MessageProvider.java index f6fb010e8..93a7884eb 100644 --- a/k9mail/src/main/java/com/fsck/k9/provider/MessageProvider.java +++ b/k9mail/src/main/java/com/fsck/k9/provider/MessageProvider.java @@ -203,9 +203,14 @@ public class MessageProvider extends ContentProvider { @Override public String getField(final MessageInfoHolder source) { final LocalMessage message = source.message; - return CONTENT_URI + "/delete_message/" - + message.getAccount().getAccountNumber() + "/" - + message.getFolder().getName() + "/" + message.getUid(); + int accountNumber = message.getAccount().getAccountNumber(); + return CONTENT_URI.buildUpon() + .appendPath("delete_message") + .appendPath(Integer.toString(accountNumber)) + .appendPath(message.getFolder().getName()) + .appendPath(message.getUid()) + .build() + .toString(); } } public static class SenderExtractor implements FieldExtractor { @@ -1017,15 +1022,10 @@ public class MessageProvider extends ContentProvider { // Note: can only delete a message - List segments = null; - int accountId = -1; - String folderName = null; - String msgUid = null; - - segments = uri.getPathSegments(); - accountId = Integer.parseInt(segments.get(1)); - folderName = segments.get(2); - msgUid = segments.get(3); + List segments = uri.getPathSegments(); + int accountId = Integer.parseInt(segments.get(1)); + String folderName = segments.get(2); + String msgUid = segments.get(3); // get account Account myAccount = null; @@ -1039,6 +1039,10 @@ public class MessageProvider extends ContentProvider { } } + if (myAccount == null) { + Log.e(K9.LOG_TAG, "Could not find account with id " + accountId); + } + // get localstore parameter LocalMessage msg = null; try { diff --git a/k9mail/src/main/java/com/fsck/k9/service/NotificationActionService.java b/k9mail/src/main/java/com/fsck/k9/service/NotificationActionService.java index 8b4e59440..3dd050884 100644 --- a/k9mail/src/main/java/com/fsck/k9/service/NotificationActionService.java +++ b/k9mail/src/main/java/com/fsck/k9/service/NotificationActionService.java @@ -2,6 +2,7 @@ package com.fsck.k9.service; import java.io.Serializable; import java.util.ArrayList; +import java.util.LinkedList; import java.util.List; import com.fsck.k9.Account; @@ -18,10 +19,16 @@ import android.content.Context; import android.content.Intent; import android.util.Log; +/** + * Service called by actions in notifications. + * Provides a number of default actions to trigger. + */ public class NotificationActionService extends CoreService { private final static String REPLY_ACTION = "com.fsck.k9.service.NotificationActionService.REPLY_ACTION"; private final static String READ_ALL_ACTION = "com.fsck.k9.service.NotificationActionService.READ_ALL_ACTION"; private final static String DELETE_ALL_ACTION = "com.fsck.k9.service.NotificationActionService.DELETE_ALL_ACTION"; + private final static String ARCHIVE_ALL_ACTION = "com.fsck.k9.service.NotificationActionService.ARCHIVE_ALL_ACTION"; + private final static String SPAM_ALL_ACTION = "com.fsck.k9.service.NotificationActionService.SPAM_ALL_ACTION"; private final static String ACKNOWLEDGE_ACTION = "com.fsck.k9.service.NotificationActionService.ACKNOWLEDGE_ACTION"; private final static String EXTRA_ACCOUNT = "account"; @@ -63,6 +70,69 @@ public class NotificationActionService extends CoreService { return i; } + /** + * Check if for the given parameters the ArchiveAllMessages intent is possible for Android Wear. + * (No confirmation on the phone required and moving these messages to the spam-folder possible)
+ * Since we can not show a toast like on the phone screen, we must not offer actions that can not be performed. + * @see #getArchiveAllMessagesIntent(android.content.Context, com.fsck.k9.Account, java.io.Serializable) + * @param context the context to get a {@link MessagingController} + * @param account the account (must allow moving messages to allow true as a result) + * @param messages the messages to move to the spam folder (must be synchronized to allow true as a result) + * @return true if the ArchiveAllMessages intent is available for the given messages + */ + public static boolean isArchiveAllMessagesWearAvaliable(Context context, final Account account, final LinkedList messages) { + final MessagingController controller = MessagingController.getInstance(context); + return (account.getArchiveFolderName() != null && !(account.getArchiveFolderName().equals(account.getSpamFolderName()) && K9.confirmSpam()) && isMovePossible(controller, account, account.getSentFolderName(), messages)); + } + + public static PendingIntent getArchiveAllMessagesIntent(Context context, final Account account, final Serializable refs) { + Intent i = new Intent(context, NotificationActionService.class); + i.putExtra(EXTRA_ACCOUNT, account.getUuid()); + i.putExtra(EXTRA_MESSAGE_LIST, refs); + i.setAction(ARCHIVE_ALL_ACTION); + + return PendingIntent.getService(context, account.getAccountNumber(), i, PendingIntent.FLAG_UPDATE_CURRENT); + + } + + /** + * Check if for the given parameters the SpamAllMessages intent is possible for Android Wear. + * (No confirmation on the phone required and moving these messages to the spam-folder possible)
+ * Since we can not show a toast like on the phone screen, we must not offer actions that can not be performed. + * @see #getSpamAllMessagesIntent(android.content.Context, com.fsck.k9.Account, java.io.Serializable) + * @param context the context to get a {@link MessagingController} + * @param account the account (must allow moving messages to allow true as a result) + * @param messages the messages to move to the spam folder (must be synchronized to allow true as a result) + * @return true if the SpamAllMessages intent is available for the given messages + */ + public static boolean isSpamAllMessagesWearAvaliable(Context context, final Account account, final LinkedList messages) { + final MessagingController controller = MessagingController.getInstance(context); + return (account.getSpamFolderName() != null && !K9.confirmSpam() && isMovePossible(controller, account, account.getSentFolderName(), messages)); + } + + public static PendingIntent getSpamAllMessagesIntent(Context context, final Account account, final Serializable refs) { + Intent i = new Intent(context, NotificationActionService.class); + i.putExtra(EXTRA_ACCOUNT, account.getUuid()); + i.putExtra(EXTRA_MESSAGE_LIST, refs); + i.setAction(SPAM_ALL_ACTION); + + return PendingIntent.getService(context, account.getAccountNumber(), i, PendingIntent.FLAG_UPDATE_CURRENT); + } + + private static boolean isMovePossible(MessagingController controller, Account account, String dstFolder, List messages) { + if (!controller.isMoveCapable(account)) { + return false; + } + if (K9.FOLDER_NONE.equalsIgnoreCase(dstFolder)) { + return false; + } + for(LocalMessage messageToMove : messages) { + if (!controller.isMoveCapable(messageToMove)) { + return false; + } + } + return true; + } @Override public int startService(Intent intent, int startId) { if (K9.DEBUG) @@ -98,6 +168,59 @@ public class NotificationActionService extends CoreService { } controller.deleteMessages(messages, null); + } else if (ARCHIVE_ALL_ACTION.equals(action)) { + if (K9.DEBUG) + Log.i(K9.LOG_TAG, "NotificationActionService archiving messages"); + + List refs = + intent.getParcelableArrayListExtra(EXTRA_MESSAGE_LIST); + List messages = new ArrayList(); + + for (MessageReference ref : refs) { + LocalMessage m = ref.restoreToLocalMessage(this.getApplicationContext()); + if (m != null) { + messages.add(m); + } + } + + String dstFolder = account.getArchiveFolderName(); + if (dstFolder != null + && !(dstFolder.equals(account.getSpamFolderName()) && K9.confirmSpam()) + && isMovePossible(controller, account, dstFolder, messages)) { + for(LocalMessage messageToMove : messages) { + if (!controller.isMoveCapable(messageToMove)) { + //Toast toast = Toast.makeText(getActivity(), R.string.move_copy_cannot_copy_unsynced_message, Toast.LENGTH_LONG); + //toast.show(); + continue; + } + String srcFolder = messageToMove.getFolder().getName(); + controller.moveMessage(account, srcFolder, messageToMove, dstFolder, null); + } + } + } else if (SPAM_ALL_ACTION.equals(action)) { + if (K9.DEBUG) + Log.i(K9.LOG_TAG, "NotificationActionService moving messages to spam"); + + List refs = + intent.getParcelableArrayListExtra(EXTRA_MESSAGE_LIST); + List messages = new ArrayList(); + + for (MessageReference ref : refs) { + LocalMessage m = ref.restoreToLocalMessage(this); + if (m != null) { + messages.add(m); + } + } + + String dstFolder = account.getSpamFolderName(); + if (dstFolder != null + && !K9.confirmSpam() + && isMovePossible(controller, account, dstFolder, messages)) { + for(LocalMessage messageToMove : messages) { + String srcFolder = messageToMove.getFolder().getName(); + controller.moveMessage(account, srcFolder, messageToMove, dstFolder, null); + } + } } else if (REPLY_ACTION.equals(action)) { if (K9.DEBUG) Log.i(K9.LOG_TAG, "NotificationActionService initiating reply"); diff --git a/k9mail/src/main/java/com/fsck/k9/view/RigidWebView.java b/k9mail/src/main/java/com/fsck/k9/view/RigidWebView.java index 2031648b7..43e344ba5 100644 --- a/k9mail/src/main/java/com/fsck/k9/view/RigidWebView.java +++ b/k9mail/src/main/java/com/fsck/k9/view/RigidWebView.java @@ -18,6 +18,7 @@ package com.fsck.k9.view; import android.content.Context; +import android.os.Build; import android.util.AttributeSet; import android.util.Log; import android.webkit.WebView; @@ -34,6 +35,7 @@ import com.fsck.k9.helper.Utility; * contents with percent-based height will force the WebView to infinitely expand (or shrink). */ public class RigidWebView extends WebView { + private static final boolean NO_THROTTLE = Build.VERSION.SDK_INT >= 21; //Build.VERSION_CODES.LOLLIPOP public RigidWebView(Context context) { super(context); @@ -64,8 +66,14 @@ public class RigidWebView extends WebView { @Override protected void onSizeChanged(int w, int h, int ow, int oh) { + if (NO_THROTTLE) { + super.onSizeChanged(w, h, ow, oh); + return; + } + mRealWidth = w; mRealHeight = h; + long now = mClock.getTime(); boolean recentlySized = (now - mLastSizeChangeTime < MIN_RESIZE_INTERVAL); diff --git a/k9mail/src/main/res/values-de/strings.xml b/k9mail/src/main/res/values-de/strings.xml index d006470e8..ed79da500 100644 --- a/k9mail/src/main/res/values-de/strings.xml +++ b/k9mail/src/main/res/values-de/strings.xml @@ -167,6 +167,8 @@ Um Fehler zu melden, neue Funktionen vorzuschlagen oder Fragen zu stellen, besuc Antworten Gelesen Löschen + Spam + Archivieren Zertifikatsproblem (%s) Überprüfen Sie Ihre Servereinstellungen Neue E-Mails in %s:%s werden abgerufen diff --git a/k9mail/src/main/res/values-zh-rTW/strings.xml b/k9mail/src/main/res/values-zh-rTW/strings.xml index 3cf136a1f..7283a5ed9 100644 --- a/k9mail/src/main/res/values-zh-rTW/strings.xml +++ b/k9mail/src/main/res/values-zh-rTW/strings.xml @@ -129,7 +129,7 @@ 正在重建帳戶「%s 您有新郵件 您有%d封未讀郵件(%s - + 來自%s已超過%d則訊息 + + 來自%2$s已超過%1$d則訊息 回覆 開啟 刪除 diff --git a/k9mail/src/main/res/values/strings.xml b/k9mail/src/main/res/values/strings.xml index 5d977423d..22a7ddc17 100644 --- a/k9mail/src/main/res/values/strings.xml +++ b/k9mail/src/main/res/values/strings.xml @@ -205,11 +205,13 @@ Please submit bug reports, contribute new features and ask questions at %d new messages %d Unread (%s) - + %d more on %s + + %1$d more on %2$s Reply Mark Read Delete + Archive + Spam Certificate error for %s Check your server settings @@ -351,6 +353,8 @@ Please submit bug reports, contribute new features and ask questions at Quiet Time Disable ringing, buzzing and flashing at night + Disable notifications + Completely disable notifications during Quiet Time Quiet Time starts Quiet Time ends @@ -1147,4 +1151,5 @@ Please submit bug reports, contribute new features and ask questions at Incomplete message Click \'Download complete message\' to allow decryption. + diff --git a/k9mail/src/main/res/xml/changelog_master.xml b/k9mail/src/main/res/xml/changelog_master.xml index 1b3e0f69c..cc6946a6e 100644 --- a/k9mail/src/main/res/xml/changelog_master.xml +++ b/k9mail/src/main/res/xml/changelog_master.xml @@ -8,6 +8,9 @@ They are automatically updated with "ant bump-version". --> + + Fixed a bug where messages where not always displayed on Android 5.x + Reverted all changes introduced with v5.104 except for the bugfixes related to Android 5.1 diff --git a/k9mail/src/main/res/xml/global_preferences.xml b/k9mail/src/main/res/xml/global_preferences.xml index 70f23b192..9ce88ffca 100644 --- a/k9mail/src/main/res/xml/global_preferences.xml +++ b/k9mail/src/main/res/xml/global_preferences.xml @@ -303,6 +303,13 @@ android:title="@string/quiet_time" android:summary="@string/quiet_time_description" /> + wrote:\r\n" + - "> a canal\r\n" + - ">\r\n" + - "> Dorothy Jo Gideon espoused:\r\n" + - "> >A man, a plan...\r\n" + - "> Too easy!\r\n" + - "\r\n" + - "Nice job :)\r\n" + - ">> Guess!"; + "\r\n" + + "Bob Barker wrote:\r\n" + + "> a canal\r\n" + + ">\r\n" + + "> Dorothy Jo Gideon espoused:\r\n" + + "> >A man, a plan...\r\n" + + "> Too easy!\r\n" + + "\r\n" + + "Nice job :)\r\n" + + ">> Guess!"; String result = HtmlConverter.textToHtml(message); writeToFile(result); assertEquals("
"
@@ -63,13 +67,13 @@ public class HtmlConverterTest {
     @Test
     public void testTextQuoteToHtmlBlockquoteIndented() {
         String message = "*facepalm*\r\n" +
-            "\r\n" +
-            "Bob Barker  wrote:\r\n" +
-            "> A wise man once said...\r\n" +
-            ">\r\n" +
-            ">     LOL F1RST!!!!!\r\n" +
-            ">\r\n" +
-            "> :)";
+                "\r\n" +
+                "Bob Barker  wrote:\r\n" +
+                "> A wise man once said...\r\n" +
+                ">\r\n" +
+                ">     LOL F1RST!!!!!\r\n" +
+                ">\r\n" +
+                "> :)";
         String result = HtmlConverter.textToHtml(message);
         writeToFile(result);
         assertEquals("
"
@@ -99,12 +103,12 @@ public class HtmlConverterTest {
         assertEquals(HtmlConverter.getQuoteColor(6), HtmlConverter.QUOTE_COLOR_DEFAULT);
 
         String message = "zero\r\n" +
-            "> one\r\n" +
-            ">> two\r\n" +
-            ">>> three\r\n" +
-            ">>>> four\r\n" +
-            ">>>>> five\r\n" +
-            ">>>>>> six";
+                "> one\r\n" +
+                ">> two\r\n" +
+                ">>> three\r\n" +
+                ">>>> four\r\n" +
+                ">>>>> five\r\n" +
+                ">>>>>> six";
         String result = HtmlConverter.textToHtml(message);
         writeToFile(result);
         assertEquals("
"
@@ -134,18 +138,23 @@ public class HtmlConverterTest {
         if (!WRITE_TO_FILE) {
             return;
         }
+
+        FileWriter fstream = null;
+
         try {
             System.err.println(content);
 
             File f = new File(OUTPUT_FILE);
             f.delete();
 
-            FileWriter fstream = new FileWriter(OUTPUT_FILE);
+            fstream = new FileWriter(OUTPUT_FILE);
             BufferedWriter out = new BufferedWriter(fstream);
             out.write(content);
             out.close();
-        } catch (Exception e) {
-            e.printStackTrace();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        } finally {
+            IOUtils.closeQuietly(fstream);
         }
     }
 
@@ -166,11 +175,11 @@ public class HtmlConverterTest {
     @Test
     public void testPreserveSpacesAtFirstForSpecialCharacters() {
         String message =
-                  " \r\n"
-                + "  &\r\n"
-                + "    \n"
-                + "   <\r\n"
-                + "  > \r\n";
+                " \r\n"
+                        + "  &\r\n"
+                        + "    \n"
+                        + "   <\r\n"
+                        + "  > \r\n";
         String result = HtmlConverter.textToHtml(message);
         writeToFile(result);
         assertEquals("
"
diff --git a/tests-on-jvm/src/test/java/com/fsck/k9/helper/HtmlSanitizerTest.java b/k9mail/src/test/java/com/fsck/k9/helper/HtmlSanitizerTest.java
similarity index 100%
rename from tests-on-jvm/src/test/java/com/fsck/k9/helper/HtmlSanitizerTest.java
rename to k9mail/src/test/java/com/fsck/k9/helper/HtmlSanitizerTest.java
diff --git a/k9mail/src/androidTest/java/com/fsck/k9/helper/MessageHelperTest.java b/k9mail/src/test/java/com/fsck/k9/helper/MessageHelperTest.java
similarity index 91%
rename from k9mail/src/androidTest/java/com/fsck/k9/helper/MessageHelperTest.java
rename to k9mail/src/test/java/com/fsck/k9/helper/MessageHelperTest.java
index 485f11520..a2292a61c 100644
--- a/k9mail/src/androidTest/java/com/fsck/k9/helper/MessageHelperTest.java
+++ b/k9mail/src/test/java/com/fsck/k9/helper/MessageHelperTest.java
@@ -3,27 +3,29 @@ package com.fsck.k9.helper;
 
 import android.content.Context;
 import android.graphics.Color;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.runner.AndroidJUnit4;
 import android.text.SpannableString;
 
 import com.fsck.k9.mail.Address;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertTrue;
 
 
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
 public class MessageHelperTest {
     private Contacts contacts;
     private Contacts mockContacts;
 
     @Before
     public void setUp() throws Exception {
-        Context context = InstrumentationRegistry.getTargetContext();
+        Context context = RuntimeEnvironment.application;
         contacts = new Contacts(context);
         mockContacts = new Contacts(context) {
             @Override public String getNameForAddress(String address) {
diff --git a/tests-on-jvm/src/test/java/com/fsck/k9/message/TextBodyBuilderTest.java b/k9mail/src/test/java/com/fsck/k9/message/TextBodyBuilderTest.java
similarity index 99%
rename from tests-on-jvm/src/test/java/com/fsck/k9/message/TextBodyBuilderTest.java
rename to k9mail/src/test/java/com/fsck/k9/message/TextBodyBuilderTest.java
index afb95eb2f..cf6b03fcb 100644
--- a/tests-on-jvm/src/test/java/com/fsck/k9/message/TextBodyBuilderTest.java
+++ b/k9mail/src/test/java/com/fsck/k9/message/TextBodyBuilderTest.java
@@ -3,6 +3,7 @@ package com.fsck.k9.message;
 import com.fsck.k9.Account.QuoteStyle;
 import com.fsck.k9.mail.internet.TextBody;
 
+import org.junit.Ignore;
 import org.junit.experimental.theories.DataPoints;
 import org.junit.experimental.theories.Theories;
 import org.junit.experimental.theories.Theory;
@@ -12,6 +13,8 @@ import static org.hamcrest.CoreMatchers.instanceOf;
 import static org.hamcrest.CoreMatchers.is;
 import static org.junit.Assert.assertThat;
 
+//TODO: Get rid of 'Theories' and write simple tests
+@Ignore
 @RunWith(Theories.class)
 public class TextBodyBuilderTest {
 
diff --git a/settings.gradle b/settings.gradle
index a39b9d813..febff8e7f 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -3,4 +3,3 @@ include ':k9mail-library'
 include ':plugins:Android-PullToRefresh:library'
 include ':plugins:HoloColorPicker'
 include ':plugins:openpgp-api-library'
-include ':tests-on-jvm'
diff --git a/tests-on-jvm/build.gradle b/tests-on-jvm/build.gradle
deleted file mode 100644
index 831e87a39..000000000
--- a/tests-on-jvm/build.gradle
+++ /dev/null
@@ -1,39 +0,0 @@
-repositories {
-    mavenCentral()
-}
-
-apply plugin: 'java'
-apply plugin: 'findbugs'
-apply plugin: 'checkstyle'
-apply plugin: 'jacoco'
-
-dependencies {
-    testCompile project(':k9mail')
-    testCompile 'junit:junit:4.12'
-}
-
-sourceSets {
-    test {
-        compileClasspath += files(project(':k9mail').compileDebugJava.destinationDir)
-        compileClasspath += project(':k9mail').compileDebugJava.classpath
-        runtimeClasspath += files(project(':k9mail').compileDebugJava.destinationDir)
-        runtimeClasspath += project(':k9mail').compileDebugJava.classpath
-    }
-}
-
-checkstyle {
-    ignoreFailures = true
-    configFile file("$rootProject.projectDir/config/checkstyle/checkstyle.xml")
-}
-
-findbugs {
-    ignoreFailures = true
-    effort = 'max'
-    includeFilter = file("$rootProject.projectDir/config/findbugs/include_filter.xml")
-    excludeFilter = file("$rootProject.projectDir/config/findbugs/exclude_filter.xml")
-}
-
-check.dependsOn 'checkstyleTest'
-check.dependsOn 'findbugsTest'
-
-compileTestJava.dependsOn ':k9mail:compileDebugJava'
diff --git a/tests-on-jvm/src/test/java/android/text/TextUtils.java b/tests-on-jvm/src/test/java/android/text/TextUtils.java
deleted file mode 100644
index cdc2ddc19..000000000
--- a/tests-on-jvm/src/test/java/android/text/TextUtils.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package android.text;
-
-public class TextUtils {
-    public static boolean isEmpty(CharSequence str) {
-        return (str == null || str.length() == 0);
-    }
-}
diff --git a/tests-on-jvm/src/test/java/android/util/Log.java b/tests-on-jvm/src/test/java/android/util/Log.java
deleted file mode 100644
index 4d952eb26..000000000
--- a/tests-on-jvm/src/test/java/android/util/Log.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package android.util;
-
-public class Log {
-    public static int v(String tag, String message) { return 0; }
-    public static int d(String tag, String message) { return 0; }
-    public static int d(String tag, String message, Throwable throwable) { return 0; }
-    public static int i(String tag, String message) { return 0; }
-    public static int w(String tag, String message) { return 0; }
-    public static int e(String tag, String message) { return 0; }
-    public static int e(String tag, String message, Throwable th) { return 0; }
-}
diff --git a/tests-on-jvm/src/test/java/com/fsck/k9/K9.java b/tests-on-jvm/src/test/java/com/fsck/k9/K9.java
deleted file mode 100644
index 64c6b7c47..000000000
--- a/tests-on-jvm/src/test/java/com/fsck/k9/K9.java
+++ /dev/null
@@ -1,5 +0,0 @@
-package com.fsck.k9;
-
-public class K9 {
-    public static boolean DEBUG = false;
-}
diff --git a/tools/debian_build.sh b/tools/debian_build.sh
new file mode 100755
index 000000000..65dbd0dbc
--- /dev/null
+++ b/tools/debian_build.sh
@@ -0,0 +1,68 @@
+#!/bin/bash
+
+# This script is intended to be used on Debian systems for building
+# the project. It has been tested with Debian 8
+
+USERNAME=$USER
+SIGNING_NAME='k-9'
+SDK_VERSION='r24.3.3'
+SDK_DIR=$HOME/android-sdk
+
+cd ..
+
+PROJECT_HOME=$(pwd)
+
+sudo apt-get install build-essential default-jdk \
+     lib32stdc++6 lib32z1 lib32z1-dev
+
+if [ ! -d $SDK_DIR ]; then
+    mkdir -p $SDK_DIR
+fi
+cd $SDK_DIR
+
+# download the SDK
+if [ ! -f $SDK_DIR/android-sdk_$SDK_VERSION-linux.tgz ]; then
+    wget https://dl.google.com/android/android-sdk_$SDK_VERSION-linux.tgz
+    tar -xzvf android-sdk_$SDK_VERSION-linux.tgz
+fi
+SDK_DIR=$SDK_DIR/android-sdk-linux
+
+echo 'Check that you have the SDK tools installed for Android 17, SDK 19.1'
+if [ ! -f $SDK_DIR/tools/android ]; then
+    echo "$SDK_DIR/tools/android not found"
+    exit -1
+fi
+cd $SDK_DIR
+chmod -R 0755 $SDK_DIR
+chmod a+rx $SDK_DIR/tools
+
+ANDROID_HOME=$SDK_DIR
+echo "sdk.dir=$SDK_DIR" > $ANDROID_HOME/local.properties
+PATH=${PATH}:$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools
+
+android sdk
+cd $PROJECT_HOME
+
+
+if [ ! -f $SDK_DIR/tools/templates/gradle/wrapper/gradlew ]; then
+    echo "$SDK_DIR/tools/templates/gradle/wrapper/gradlew not found"
+    exit -2
+fi
+. $SDK_DIR/tools/templates/gradle/wrapper/gradlew build
+
+#cd ~/develop/$PROJECT_NAME/build/outputs/apk
+#keytool -genkey -v -keystore example.keystore -alias \
+#    "$SIGNING_NAME" -keyalg RSA -keysize 4096
+#jarsigner -verbose -keystore example.keystore \
+#    k9mail-release-unsigned.apk "$SIGNING_NAME"
+
+# cleaning up
+cd $PROJECT_HOME/k9mail/build/outputs/apk
+if [ ! -f k9mail-debug.apk ]; then
+    echo 'k9mail-debug.apk was not found'
+    exit -3
+fi
+echo 'Build script ended successfully'
+echo -n 'apk is available at: '
+echo "$PROJECT_HOME/k9mail/build/outputs/apk/k9mail-debug.apk"
+exit 0