Merge branch 'master' into material_design

This commit is contained in:
Sebastian Kürten 2015-07-17 12:16:32 +02:00
commit e5a90e015a
64 changed files with 734 additions and 363 deletions

83
README.md Normal file
View File

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

View File

@ -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'
}
}

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<lint>
<issue id="MissingTranslation" severity="ignore" />
<issue id="OldTargetApi" severity="ignore" />
<!-- Transifex and Lint disagree on what quantities are necessary -->
<issue id="UnusedQuantity" severity="warning">

View File

@ -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)
}
}

View File

@ -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'
}
}

View File

@ -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);

View File

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

View File

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

View File

@ -46,7 +46,7 @@ public class BinaryTempFileMessageBody extends BinaryTempFileBody implements Com
IOUtils.copy(in, out);
}
} finally {
in.close();
IOUtils.closeQuietly(in);
}
}

View File

@ -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) {

View File

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

View File

@ -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) {

View File

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

View File

@ -491,7 +491,6 @@ public class SmtpTransport extends Transport {
private void sendMessageTo(List<String> 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;
}

View File

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

View File

@ -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() {

View File

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

View File

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

View File

@ -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() {

View File

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

View File

@ -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() {

View File

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

View File

@ -2,8 +2,8 @@
<manifest
package="com.fsck.k9"
xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="23050"
android:versionName="5.105">
android:versionCode="23060"
android:versionName="5.106">
<uses-feature
android:name="android.hardware.touchscreen"

View File

@ -1404,7 +1404,7 @@ public class Account implements BaseAccount, StoreConfig {
if (i < identities.size()) {
return identities.get(i);
}
return null;
throw new IllegalArgumentException("Identity with index " + i + " not found");
}
public boolean isAnIdentity(Address[] addrs) {

View File

@ -237,6 +237,7 @@ public class K9 extends Application {
private static boolean mHideSpecialAccounts = false;
private static boolean mAutofitWidth;
private static boolean mQuietTimeEnabled = false;
private static boolean mNotificationDuringQuietTimeEnabled = true;
private static String mQuietTimeStarts = null;
private static String mQuietTimeEnds = null;
private static String mAttachmentDefaultPath = "";
@ -474,6 +475,7 @@ public class K9 extends Application {
editor.putBoolean("useVolumeKeysForListNavigation", mUseVolumeKeysForListNavigation);
editor.putBoolean("autofitWidth", mAutofitWidth);
editor.putBoolean("quietTimeEnabled", mQuietTimeEnabled);
editor.putBoolean("notificationDuringQuietTimeEnabled", mNotificationDuringQuietTimeEnabled);
editor.putString("quietTimeStarts", mQuietTimeStarts);
editor.putString("quietTimeEnds", mQuietTimeEnds);
@ -708,6 +710,7 @@ public class K9 extends Application {
mAutofitWidth = sprefs.getBoolean("autofitWidth", true);
mQuietTimeEnabled = sprefs.getBoolean("quietTimeEnabled", false);
mNotificationDuringQuietTimeEnabled = sprefs.getBoolean("notificationDuringQuietTimeEnabled", true);
mQuietTimeStarts = sprefs.getString("quietTimeStarts", "21:00");
mQuietTimeEnds = sprefs.getString("quietTimeEnds", "7:00");
@ -970,6 +973,14 @@ public class K9 extends Application {
mQuietTimeEnabled = quietTimeEnabled;
}
public static boolean isNotificationDuringQuietTimeEnabled() {
return mNotificationDuringQuietTimeEnabled;
}
public static void setNotificationDuringQuietTimeEnabled(boolean notificationDuringQuietTimeEnabled) {
mNotificationDuringQuietTimeEnabled = notificationDuringQuietTimeEnabled;
}
public static String getQuietTimeStarts() {
return mQuietTimeStarts;
}

View File

@ -1183,41 +1183,40 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
if (menuInfo != null) {
mSelectedContextAccount = (BaseAccount)getListView().getItemAtPosition(menuInfo.position);
}
Account realAccount = null;
if (mSelectedContextAccount instanceof Account) {
realAccount = (Account)mSelectedContextAccount;
}
switch (item.getItemId()) {
case R.id.delete_account:
onDeleteAccount(realAccount);
break;
case R.id.account_settings:
onEditAccount(realAccount);
break;
case R.id.activate:
onActivateAccount(realAccount);
break;
case R.id.clear_pending:
onClearCommands(realAccount);
break;
case R.id.empty_trash:
onEmptyTrash(realAccount);
break;
case R.id.clear:
onClear(realAccount);
break;
case R.id.recreate:
onRecreate(realAccount);
break;
case R.id.export:
onExport(false, realAccount);
break;
case R.id.move_up:
onMove(realAccount, true);
break;
case R.id.move_down:
onMove(realAccount, false);
break;
Account realAccount = (Account)mSelectedContextAccount;
switch (item.getItemId()) {
case R.id.delete_account:
onDeleteAccount(realAccount);
break;
case R.id.account_settings:
onEditAccount(realAccount);
break;
case R.id.activate:
onActivateAccount(realAccount);
break;
case R.id.clear_pending:
onClearCommands(realAccount);
break;
case R.id.empty_trash:
onEmptyTrash(realAccount);
break;
case R.id.clear:
onClear(realAccount);
break;
case R.id.recreate:
onRecreate(realAccount);
break;
case R.id.export:
onExport(false, realAccount);
break;
case R.id.move_up:
onMove(realAccount, true);
break;
case R.id.move_down:
onMove(realAccount, false);
break;
}
}
return true;
}

View File

@ -2362,13 +2362,13 @@ public class MessageCompose extends K9Activity implements OnClickListener,
// Read In-Reply-To header from draft
final String[] inReplyTo = message.getHeader("In-Reply-To");
if ((inReplyTo != null) && (inReplyTo.length >= 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<IdentityField, String> k9identity = new HashMap<IdentityField, String>();
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();

View File

@ -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;
}

View File

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

View File

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

View File

@ -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<LocalMessage> 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 <T extends Message> void fetchUnsyncedMessages(final Account account, final Folder<T> remoteFolder,
final LocalFolder localFolder,
List<T> unsyncedMessages,
final List<Message> smallMessages,
final List<Message> largeMessages,
@ -1452,22 +1452,12 @@ public class MessagingController implements Runnable {
final Date earliestDate = account.getEarliestPollDate();
/*
* Messages to be batch written
*/
final List<Message> chunk = new ArrayList<Message>(UNSYNC_CHUNK_SIZE);
remoteFolder.fetch(unsyncedMessages, fp,
new MessageRetrievalListener<T>() {
@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
*
* <br>
* FIXME: <strong>This method should really be moved in the above MessageRetrievalListener once {@link MessageRetrievalListener#messagesFinished(int)} is properly invoked by various stores</strong>
*
* @param messages Never <code>null</code>.
* @param localFolder
* @param account
* @param folder
*/
private void writeUnsyncedMessages(final List<Message> 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,

View File

@ -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<LocalMessage> getCheckedMessages() {

View File

@ -1311,6 +1311,8 @@ public class LocalFolder extends Folder<LocalMessage> 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<LocalMessage> 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<LocalMessage> 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<LocalMessage> 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<LocalMessage> implements Serializable {
// Get the message IDs from the "References" header line
String[] referencesArray = message.getHeader("References");
List<String> 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<LocalMessage> 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;
}
}

View File

@ -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;
}

View File

@ -266,6 +266,9 @@ public class GlobalSettings {
new V(38, new EnumSetting<NotificationQuickDelete>(NotificationQuickDelete.class,
NotificationQuickDelete.NEVER))
));
s.put("notificationDuringQuietTimeEnabled", Settings.versions(
new V(39, new BooleanSetting(true))
));
SETTINGS = Collections.unmodifiableMap(s);

View File

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

View File

@ -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<MessageInfoHolder, CharSequence> {
@ -1017,15 +1022,10 @@ public class MessageProvider extends ContentProvider {
// Note: can only delete a message
List<String> 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<String> 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 {

View File

@ -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)<br/>
* 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<LocalMessage> 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)<br/>
* 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<LocalMessage> 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<LocalMessage> 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<MessageReference> refs =
intent.getParcelableArrayListExtra(EXTRA_MESSAGE_LIST);
List<LocalMessage> messages = new ArrayList<LocalMessage>();
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<MessageReference> refs =
intent.getParcelableArrayListExtra(EXTRA_MESSAGE_LIST);
List<LocalMessage> messages = new ArrayList<LocalMessage>();
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");

View File

@ -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);

View File

@ -167,6 +167,8 @@ Um Fehler zu melden, neue Funktionen vorzuschlagen oder Fragen zu stellen, besuc
<string name="notification_action_reply">Antworten</string>
<string name="notification_action_mark_as_read">Gelesen</string>
<string name="notification_action_delete">Löschen</string>
<string name="notification_action_spam">Spam</string>
<string name="notification_action_archive">Archivieren</string>
<string name="notification_certificate_error_title">Zertifikatsproblem (<xliff:g id="account">%s</xliff:g>)</string>
<string name="notification_certificate_error_text">Überprüfen Sie Ihre Servereinstellungen</string>
<string name="notification_bg_sync_ticker">Neue E-Mails in <xliff:g id="account">%s</xliff:g>:<xliff:g id="folder">%s</xliff:g> werden abgerufen</string>

View File

@ -129,7 +129,7 @@
<string name="recreating_account">正在重建帳戶「<xliff:g id="account">%s</xliff:g></string>
<string name="notification_new_title">您有新郵件</string>
<string name="notification_new_one_account_fmt">您有<xliff:g id="unread_message_count">%d</xliff:g>封未讀郵件(<xliff:g id="account">%s</xliff:g></string>
<string name="notification_additional_messages">+ 來自<xliff:g id="account">%s</xliff:g>已超過<xliff:g id="additional_messages">%d</xliff:g>則訊息 </string>
<string name="notification_additional_messages">+ 來自<xliff:g id="account">%2$s</xliff:g>已超過<xliff:g id="additional_messages">%1$d</xliff:g>則訊息 </string>
<string name="notification_action_reply">回覆</string>
<string name="notification_action_mark_as_read">開啟</string>
<string name="notification_action_delete">刪除</string>

View File

@ -205,11 +205,13 @@ Please submit bug reports, contribute new features and ask questions at
<item quantity="other"><xliff:g id="new_message_count">%d</xliff:g> new messages</item>
</plurals>
<string name="notification_new_one_account_fmt"><xliff:g id="unread_message_count">%d</xliff:g> Unread (<xliff:g id="account">%s</xliff:g>)</string>
<string name="notification_additional_messages">+ <xliff:g id="additional_messages">%d</xliff:g> more on <xliff:g id="account">%s</xliff:g></string>
<string name="notification_additional_messages">+ <xliff:g id="additional_messages">%1$d</xliff:g> more on <xliff:g id="account">%2$s</xliff:g></string>
<string name="notification_action_reply">Reply</string>
<string name="notification_action_mark_as_read">Mark Read</string>
<string name="notification_action_delete">Delete</string>
<string name="notification_action_archive">Archive</string>
<string name="notification_action_spam">Spam</string>
<string name="notification_certificate_error_title">Certificate error for <xliff:g id="account">%s</xliff:g></string>
<string name="notification_certificate_error_text">Check your server settings</string>
@ -351,6 +353,8 @@ Please submit bug reports, contribute new features and ask questions at
<string name="quiet_time">Quiet Time</string>
<string name="quiet_time_description">Disable ringing, buzzing and flashing at night</string>
<string name="quiet_time_notification">Disable notifications</string>
<string name="quiet_time_notification_description">Completely disable notifications during Quiet Time</string>
<string name="quiet_time_starts">Quiet Time starts</string>
<string name="quiet_time_ends">Quiet Time ends</string>
@ -1147,4 +1151,5 @@ Please submit bug reports, contribute new features and ask questions at
<string name="crypto_incomplete_message">Incomplete message</string>
<!-- Note: This references message_view_download_remainder -->
<string name="crypto_download_complete_message_to_decrypt">Click \'Download complete message\' to allow decryption.</string>
</resources>

View File

@ -8,6 +8,9 @@
They are automatically updated with "ant bump-version".
-->
<changelog>
<release version="5.106" versioncode="23060">
<change>Fixed a bug where messages where not always displayed on Android 5.x</change>
</release>
<release version="5.105" versioncode="23050">
<change>Reverted all changes introduced with v5.104 except for the bugfixes related to Android 5.1</change>
</release>

View File

@ -303,6 +303,13 @@
android:title="@string/quiet_time"
android:summary="@string/quiet_time_description"
/>
<CheckBoxPreference
android:key="disable_notifications_during_quiet_time"
android:persistent="false"
android:dependency="quiet_time_enabled"
android:title="@string/quiet_time_notification"
android:summary="@string/quiet_time_notification_description"
/>
<com.fsck.k9.preferences.TimePickerPreference
android:key="quiet_time_starts"
android:persistent="false"

View File

@ -1,12 +1,12 @@
package com.fsck.k9.activity;
import android.support.test.runner.AndroidJUnit4;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.MessagingException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
@ -14,7 +14,8 @@ import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
@RunWith(AndroidJUnit4.class)
@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class MessageReferenceTest {
@Test

View File

@ -3,8 +3,6 @@ package com.fsck.k9.crypto;
import java.util.List;
import android.support.test.runner.AndroidJUnit4;
import com.fsck.k9.mail.Part;
import com.fsck.k9.mail.internet.MimeBodyPart;
import com.fsck.k9.mail.internet.MimeMessage;
@ -13,12 +11,15 @@ import com.fsck.k9.mail.internet.MimeMultipart;
import com.fsck.k9.mail.internet.TextBody;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertSame;
@RunWith(AndroidJUnit4.class)
@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class MessageDecryptVerifierTest {
@Test

View File

@ -1,18 +1,22 @@
package com.fsck.k9.helper;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import android.support.test.runner.AndroidJUnit4;
import org.apache.commons.io.IOUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import static junit.framework.Assert.assertEquals;
@RunWith(AndroidJUnit4.class)
@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class HtmlConverterTest {
// Useful if you want to write stuff to a file for debugging in a browser.
private static final boolean WRITE_TO_FILE = Boolean.parseBoolean(System.getProperty("k9.htmlConverterTest.writeToFile", "false"));
@ -21,16 +25,16 @@ public class HtmlConverterTest {
@Test
public void testTextQuoteToHtmlBlockquote() {
String message = "Panama!\r\n" +
"\r\n" +
"Bob Barker <bob@aol.com> wrote:\r\n" +
"> a canal\r\n" +
">\r\n" +
"> Dorothy Jo Gideon <dorothy@aol.com> espoused:\r\n" +
"> >A man, a plan...\r\n" +
"> Too easy!\r\n" +
"\r\n" +
"Nice job :)\r\n" +
">> Guess!";
"\r\n" +
"Bob Barker <bob@aol.com> wrote:\r\n" +
"> a canal\r\n" +
">\r\n" +
"> Dorothy Jo Gideon <dorothy@aol.com> 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("<pre class=\"k9mail\">"
@ -63,13 +67,13 @@ public class HtmlConverterTest {
@Test
public void testTextQuoteToHtmlBlockquoteIndented() {
String message = "*facepalm*\r\n" +
"\r\n" +
"Bob Barker <bob@aol.com> wrote:\r\n" +
"> A wise man once said...\r\n" +
">\r\n" +
"> LOL F1RST!!!!!\r\n" +
">\r\n" +
"> :)";
"\r\n" +
"Bob Barker <bob@aol.com> 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("<pre class=\"k9mail\">"
@ -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("<pre class=\"k9mail\">"
@ -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("<pre class=\"k9mail\">"

View File

@ -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) {

View File

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

View File

@ -3,4 +3,3 @@ include ':k9mail-library'
include ':plugins:Android-PullToRefresh:library'
include ':plugins:HoloColorPicker'
include ':plugins:openpgp-api-library'
include ':tests-on-jvm'

View File

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

View File

@ -1,7 +0,0 @@
package android.text;
public class TextUtils {
public static boolean isEmpty(CharSequence str) {
return (str == null || str.length() == 0);
}
}

View File

@ -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; }
}

View File

@ -1,5 +0,0 @@
package com.fsck.k9;
public class K9 {
public static boolean DEBUG = false;
}

68
tools/debian_build.sh Executable file
View File

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