mirror of
https://github.com/moparisthebest/k-9
synced 2025-01-05 18:58:10 -05:00
Merge branch 'master' into material_design
This commit is contained in:
commit
e5a90e015a
83
README.md
Normal file
83
README.md
Normal 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)
|
||||
|
@ -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'
|
||||
}
|
||||
}
|
||||
|
@ -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">
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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'
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -46,7 +46,7 @@ public class BinaryTempFileMessageBody extends BinaryTempFileBody implements Com
|
||||
IOUtils.copy(in, out);
|
||||
}
|
||||
} finally {
|
||||
in.close();
|
||||
IOUtils.closeQuietly(in);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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.
|
@ -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() {
|
@ -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
|
@ -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
|
@ -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() {
|
@ -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 {
|
@ -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() {
|
@ -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 {
|
||||
|
@ -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"
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 =
|
||||
|
@ -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());
|
||||
|
@ -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,
|
||||
|
@ -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() {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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");
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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"
|
||||
|
@ -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
|
@ -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
|
@ -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\">"
|
@ -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) {
|
@ -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 {
|
||||
|
@ -3,4 +3,3 @@ include ':k9mail-library'
|
||||
include ':plugins:Android-PullToRefresh:library'
|
||||
include ':plugins:HoloColorPicker'
|
||||
include ':plugins:openpgp-api-library'
|
||||
include ':tests-on-jvm'
|
||||
|
@ -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'
|
@ -1,7 +0,0 @@
|
||||
package android.text;
|
||||
|
||||
public class TextUtils {
|
||||
public static boolean isEmpty(CharSequence str) {
|
||||
return (str == null || str.length() == 0);
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
package com.fsck.k9;
|
||||
|
||||
public class K9 {
|
||||
public static boolean DEBUG = false;
|
||||
}
|
68
tools/debian_build.sh
Executable file
68
tools/debian_build.sh
Executable 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
|
Loading…
Reference in New Issue
Block a user