aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsteckbrief <steckbrief@chefmail.de>2017-02-06 10:01:13 +0100
committersteckbrief <steckbrief@chefmail.de>2017-02-06 10:01:13 +0100
commit754de6bb0449a577d2bb9c28cca6adf0ef9554f6 (patch)
tree279b405d94e0d86d10ed94bd34d919457944ead6
parentcd633f13b8d7327e47994bb5a000f0c0b7089e7f (diff)
relates FS#241: Implementation of http download based on okhttp
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/ConversationsPlusApplication.java21
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/ConversationsPlusColors.java2
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/crypto/PgpEngine.java7
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/dto/RemoteFile.java2
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/entities/FileParams.java14
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/entities/Message.java2
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/entities/Transferable.java4
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/entities/TransferablePlaceholder.java5
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/enums/FileStatus.java5
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/http/Http.java18
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/http/HttpClient.java152
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/http/HttpConnectionManager.java90
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/http/HttpDownloadConnection.java375
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/http/HttpHeadRetrievedListener.java10
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/http/ProgressListener.java14
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/parser/MessageParser.java21
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/services/XmppConnectionService.java2
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferEntity.java15
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferManager.java10
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/delete/DeleteTokenReceived.java2
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/download/AutomaticFileDownload.java61
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/download/HttpDownloadFileTransferEntity.java53
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/download/HttpDownloadFileTransferService.java65
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/download/HttpFileDownloadCallback.java57
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/download/HttpRetrieveHead.java112
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpFileUploader.java50
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpUploadFileTransferEntity.java (renamed from src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpFileTransferEntity.java)19
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpUploadFileTransferService.java4
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpUploadSlotRequestReceived.java4
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpUploadedFileEncryptionUiCallback.java4
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/services/filetransfer/jingle/JingleFileTransferService.java3
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/ui/ConversationActivity.java11
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/ui/ConversationFragment.java14
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/ui/XmppActivity.java120
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/ui/adapter/ConversationAdapter.java3
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/ui/adapter/MessageAdapter.java151
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/utils/FileUtils.java15
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/utils/ImageUtil.java136
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/utils/MessageUtil.java108
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/utils/UIHelper.java25
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/utils/ui/TextViewUtil.java4
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/utils/ui/ViewUtil.java37
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleConnection.java2
-rw-r--r--src/main/res/values/strings.xml2
-rw-r--r--src/test/java/de/thedevstack/conversationsplus/utils/UIHelperTest.java29
45 files changed, 1053 insertions, 807 deletions
diff --git a/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusApplication.java b/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusApplication.java
index d4e3ebc2..2c056867 100644
--- a/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusApplication.java
+++ b/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusApplication.java
@@ -1,8 +1,10 @@
package de.thedevstack.conversationsplus;
+import android.Manifest;
import android.app.Application;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.os.Build;
import android.os.PowerManager;
import android.preference.PreferenceManager;
@@ -11,15 +13,14 @@ import java.security.SecureRandom;
import de.duenndns.ssl.MemorizingTrustManager;
import de.thedevstack.conversationsplus.http.HttpClient;
-import de.thedevstack.conversationsplus.http.HttpConnectionManager;
import de.thedevstack.conversationsplus.persistance.FileBackend;
import de.thedevstack.conversationsplus.services.filetransfer.FileTransferManager;
+import de.thedevstack.conversationsplus.services.filetransfer.http.download.HttpDownloadFileTransferService;
import de.thedevstack.conversationsplus.services.filetransfer.http.upload.HttpUploadFileTransferService;
import de.thedevstack.conversationsplus.services.filetransfer.jingle.JingleFileTransferService;
import de.thedevstack.conversationsplus.utils.ImageUtil;
import de.thedevstack.conversationsplus.utils.PRNGFixes;
import de.thedevstack.conversationsplus.utils.SerialSingleThreadExecutor;
-import okhttp3.OkHttpClient;
/**
* This class is used to provide static access to the applicationcontext.
@@ -46,8 +47,7 @@ public class ConversationsPlusApplication extends Application {
this.initializeSecurity();
ImageUtil.initBitmapCache();
FileBackend.init();
- FileTransferManager.init(new HttpUploadFileTransferService(), new JingleFileTransferService());
- HttpConnectionManager.init();
+ FileTransferManager.init(new HttpUploadFileTransferService(), new JingleFileTransferService(), new HttpDownloadFileTransferService());
HttpClient.init();
}
@@ -132,6 +132,14 @@ public class ConversationsPlusApplication extends Application {
return powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, name);
}
+ public static boolean hasStoragePermission() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ return ConversationsPlusApplication.getAppContext().checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
+ } else {
+ return true;
+ }
+ }
+
public static MemorizingTrustManager getMemorizingTrustManager() {
return getInstance().memorizingTrustManager;
}
@@ -150,11 +158,6 @@ public class ConversationsPlusApplication extends Application {
getInstance().setMemorizingTrustManager(tm);
}
- private static void initHttpClient() {
- OkHttpClient client = new OkHttpClient.Builder()
- .build();
- }
-
public static SecureRandom getSecureRandom() {
return getInstance().secureRandom;
}
diff --git a/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusColors.java b/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusColors.java
index 352e86e3..db9c560f 100644
--- a/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusColors.java
+++ b/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusColors.java
@@ -147,7 +147,7 @@ public final class ConversationsPlusColors {
* @see {@link android.content.res.Resources#getColor(int)}
* @return the color identified by id
*/
- private static int byId(int id) {
+ public static int byId(int id) {
return ConversationsPlusApplication.getAppContext().getResources().getColor(id);
}
diff --git a/src/main/java/de/thedevstack/conversationsplus/crypto/PgpEngine.java b/src/main/java/de/thedevstack/conversationsplus/crypto/PgpEngine.java
index c97b8395..440ca6de 100644
--- a/src/main/java/de/thedevstack/conversationsplus/crypto/PgpEngine.java
+++ b/src/main/java/de/thedevstack/conversationsplus/crypto/PgpEngine.java
@@ -16,6 +16,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
+import de.thedevstack.conversationsplus.services.filetransfer.FileTransferManager;
import de.thedevstack.conversationsplus.utils.StreamUtil;
import de.thedevstack.conversationsplus.R;
import de.thedevstack.conversationsplus.entities.Account;
@@ -23,7 +24,6 @@ import de.thedevstack.conversationsplus.entities.Contact;
import de.thedevstack.conversationsplus.entities.Conversation;
import de.thedevstack.conversationsplus.entities.DownloadableFile;
import de.thedevstack.conversationsplus.entities.Message;
-import de.thedevstack.conversationsplus.http.HttpConnectionManager;
import de.thedevstack.conversationsplus.persistance.FileBackend;
import de.thedevstack.conversationsplus.services.XmppConnectionService;
import de.thedevstack.conversationsplus.ui.UiCallback;
@@ -67,7 +67,10 @@ public class PgpEngine {
&& message.treatAsDownloadable() != Message.Decision.NEVER
&& (message.isHttpUploaded() || ConversationsPlusPreferences.autoDownloadFileLink())
&& ConversationsPlusPreferences.autoAcceptFileSize() > 0) {
- HttpConnectionManager.createNewDownloadConnection(message);
+ FileTransferManager ftm = FileTransferManager.getInstance();
+ if (ftm.accept(message)) {
+ ftm.transferFile(message);
+ }
}
mXmppConnectionService.updateMessage(message);
callback.success(message);
diff --git a/src/main/java/de/thedevstack/conversationsplus/dto/RemoteFile.java b/src/main/java/de/thedevstack/conversationsplus/dto/RemoteFile.java
index 58b64b28..3439460b 100644
--- a/src/main/java/de/thedevstack/conversationsplus/dto/RemoteFile.java
+++ b/src/main/java/de/thedevstack/conversationsplus/dto/RemoteFile.java
@@ -5,7 +5,7 @@ import android.support.annotation.NonNull;
import java.io.Serializable;
/**
- * Created by steckbrief on 22.08.2016.
+ *
*/
public class RemoteFile implements Serializable {
private static final long serialVersionUID = 34564871234564L;
diff --git a/src/main/java/de/thedevstack/conversationsplus/entities/FileParams.java b/src/main/java/de/thedevstack/conversationsplus/entities/FileParams.java
index 7d5741d9..b21472ba 100644
--- a/src/main/java/de/thedevstack/conversationsplus/entities/FileParams.java
+++ b/src/main/java/de/thedevstack/conversationsplus/entities/FileParams.java
@@ -1,6 +1,7 @@
package de.thedevstack.conversationsplus.entities;
import de.thedevstack.conversationsplus.enums.FileStatus;
+import de.thedevstack.conversationsplus.utils.FileUtils;
import de.thedevstack.conversationsplus.utils.MimeUtils;
/**
@@ -12,7 +13,7 @@ public class FileParams {
private String path;
private String url;
private String mimeType;
- private long size = 0;
+ private long size = -1;
private int width = 0;
private int height = 0;
private FileStatus fileStatus;
@@ -136,15 +137,12 @@ public class FileParams {
path = (!path.endsWith(this.name)) ? path + "/" + this.name : path;
} else {
if (!path.endsWith("/")) {
- this.setName(path.substring(path.lastIndexOf('/') + 1));
+ this.setName(FileUtils.getFilenameFromPath(path));
}
}
if (null == this.mimeType) {
- int start = path.lastIndexOf('.') + 1;
- if (start < path.length()) {
- String extension = path.substring(start);
- this.mimeType = MimeUtils.guessMimeTypeFromExtension(extension);
- }
+ String extension = FileUtils.getLastExtension(path);
+ this.mimeType = MimeUtils.guessMimeTypeFromExtension(extension);
}
}
@@ -152,7 +150,7 @@ public class FileParams {
}
public boolean isRemoteAvailable() {
- return null != this.url || FileStatus.UPLOADED == this.fileStatus || FileStatus.DELETE_FAILED == this.fileStatus;
+ return (null != this.url || FileStatus.UPLOADED == this.fileStatus || FileStatus.DELETE_FAILED == this.fileStatus) && FileStatus.NOT_FOUND != this.fileStatus;
}
public void setFileStatus(FileStatus fileStatus) {
diff --git a/src/main/java/de/thedevstack/conversationsplus/entities/Message.java b/src/main/java/de/thedevstack/conversationsplus/entities/Message.java
index a553a3e6..4d7697f2 100644
--- a/src/main/java/de/thedevstack/conversationsplus/entities/Message.java
+++ b/src/main/java/de/thedevstack/conversationsplus/entities/Message.java
@@ -18,8 +18,6 @@ public class Message extends AbstractEntity {
public static final String TABLENAME = "messages";
- public static final String MERGE_SEPARATOR = " \u200B\n\n";
-
public static final int STATUS_RECEIVED = 0;
public static final int STATUS_UNSEND = 1;
public static final int STATUS_SEND = 2;
diff --git a/src/main/java/de/thedevstack/conversationsplus/entities/Transferable.java b/src/main/java/de/thedevstack/conversationsplus/entities/Transferable.java
index 8e2ca20e..10bc8bcf 100644
--- a/src/main/java/de/thedevstack/conversationsplus/entities/Transferable.java
+++ b/src/main/java/de/thedevstack/conversationsplus/entities/Transferable.java
@@ -1,6 +1,8 @@
package de.thedevstack.conversationsplus.entities;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
public interface Transferable {
@@ -23,8 +25,6 @@ public interface Transferable {
int getStatus();
- long getFileSize();
-
int getProgress();
void cancel();
diff --git a/src/main/java/de/thedevstack/conversationsplus/entities/TransferablePlaceholder.java b/src/main/java/de/thedevstack/conversationsplus/entities/TransferablePlaceholder.java
index c51320d1..40292e1f 100644
--- a/src/main/java/de/thedevstack/conversationsplus/entities/TransferablePlaceholder.java
+++ b/src/main/java/de/thedevstack/conversationsplus/entities/TransferablePlaceholder.java
@@ -18,11 +18,6 @@ public class TransferablePlaceholder implements Transferable {
}
@Override
- public long getFileSize() {
- return 0;
- }
-
- @Override
public int getProgress() {
return 0;
}
diff --git a/src/main/java/de/thedevstack/conversationsplus/enums/FileStatus.java b/src/main/java/de/thedevstack/conversationsplus/enums/FileStatus.java
index b6a4ef9a..e4254aeb 100644
--- a/src/main/java/de/thedevstack/conversationsplus/enums/FileStatus.java
+++ b/src/main/java/de/thedevstack/conversationsplus/enums/FileStatus.java
@@ -13,5 +13,8 @@ public enum FileStatus {
NEEDS_UPLOAD,
UNDEFINED,
NEEDS_DOWNLOAD,
- DELETING;
+ DELETING,
+ NOT_FOUND,
+ DOWNLOADING,
+ CHECKING_FILE_SIZE;
}
diff --git a/src/main/java/de/thedevstack/conversationsplus/http/Http.java b/src/main/java/de/thedevstack/conversationsplus/http/Http.java
new file mode 100644
index 00000000..98b4dc82
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/http/Http.java
@@ -0,0 +1,18 @@
+package de.thedevstack.conversationsplus.http;
+
+import java.net.HttpURLConnection;
+
+/**
+ *
+ */
+
+public interface Http {
+ // HTTP Response Codes
+ int HTTP_NOT_FOUND = HttpURLConnection.HTTP_NOT_FOUND;
+
+ // Header Field Names
+ String MIME_REQUEST_PROPERTY_NAME = "Content-Type";
+ String HEADER_NAME_CONTENT_LENGTH = "Content-Length";
+ String HEADER_NAME_ACCEPT_ENCODING = "Accept-Encoding";
+ String USER_AGENT_REQUEST_PROPERTY_NAME = "User-Agent";
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/http/HttpClient.java b/src/main/java/de/thedevstack/conversationsplus/http/HttpClient.java
index 7e12a890..e62be056 100644
--- a/src/main/java/de/thedevstack/conversationsplus/http/HttpClient.java
+++ b/src/main/java/de/thedevstack/conversationsplus/http/HttpClient.java
@@ -1,52 +1,101 @@
package de.thedevstack.conversationsplus.http;
+import android.support.annotation.NonNull;
+
import org.apache.http.conn.ssl.StrictHostnameVerifier;
+import java.io.IOException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
+import java.util.Locale;
import javax.net.ssl.HostnameVerifier;
-import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.X509TrustManager;
+import de.thedevstack.android.logcat.Logging;
import de.thedevstack.conversationsplus.ConversationsPlusApplication;
import de.thedevstack.conversationsplus.utils.CryptoHelper;
import de.thedevstack.conversationsplus.utils.SSLSocketHelper;
+import okhttp3.Call;
+import okhttp3.Callback;
+import okhttp3.Interceptor;
+import okhttp3.MediaType;
import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+import okhttp3.ResponseBody;
+import okio.Buffer;
+import okio.BufferedSource;
+import okio.ForwardingSource;
+import okio.Okio;
+import okio.Source;
/**
- * Created by steckbrief on 22.08.2016.
+ *
*/
-public final class HttpClient {
+public final class HttpClient implements Http {
private static HttpClient INSTANCE;
- private boolean interactive = false;
- private OkHttpClient client;
+ private static final String LOGTAG = "http-client";
+
+ private final OkHttpClient client;
- public static void init() {
+ public static synchronized void init() {
INSTANCE = new HttpClient();
}
- public static synchronized OkHttpClient getClient(boolean interactive) {
- if (INSTANCE.interactive != interactive) {
- INSTANCE.interactive = interactive;
- INSTANCE.buildClient();
+ public static synchronized HttpClient getClient() {
+ if (null == INSTANCE) {
+ init();
}
- return INSTANCE.client;
+ return INSTANCE;
}
- private HttpClient() {
- this.buildClient();
+ private static OkHttpClient.Builder getBuilder(boolean interactive) {
+ OkHttpClient.Builder builder = INSTANCE.client.newBuilder();
+ INSTANCE.initTrustManager(builder, interactive);
+
+ return builder;
+ }
+
+ public static synchronized OkHttpClient getOkHttpClient(boolean interactive) {
+ return getBuilder(interactive).build();
+ }
+
+ public static synchronized Call openCancelableAndProgressListenedCall(String url, final ProgressListener progressListener, boolean interactive) {
+ OkHttpClient.Builder builder = getBuilder(interactive);
+ OkHttpClient client = builder.addNetworkInterceptor(new Interceptor() {
+ @Override public Response intercept(Chain chain) throws IOException {
+ Response originalResponse = chain.proceed(chain.request());
+ return originalResponse.newBuilder()
+ .body(new ProgressResponseBody(originalResponse.body(), progressListener))
+ .build();
+ }
+ })
+ .build();
+
+ return client.newCall(new Request.Builder().url(url).build());
}
- private void buildClient() {
+ public static void retrieveHead(String url, @NonNull Callback callback) throws IOException {
+ OkHttpClient client = HttpClient.getOkHttpClient(true);
+ Request request = new Request.Builder()
+ .url(url)
+ //.addHeader(HEADER_NAME_ACCEPT_ENCODING, "")
+ .head()
+ .build();
+ client.newCall(request).enqueue(callback);
+ }
+
+ private HttpClient() {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
- this.initTrustManager(builder);
+ builder.addInterceptor(new UserAgentInterceptor());
+ builder.addInterceptor(new LoggingInterceptor());
this.client = builder.build();
}
- public void initTrustManager(final OkHttpClient.Builder builder) {
+ private static void initTrustManager(final OkHttpClient.Builder builder, final boolean interactive) {
final X509TrustManager trustManager;
final HostnameVerifier hostnameVerifier;
if (interactive) {
@@ -78,4 +127,75 @@ public final class HttpClient {
} catch (final KeyManagementException | NoSuchAlgorithmException ignored) {
}
}
+
+ private static class UserAgentInterceptor implements Interceptor, Http {
+
+ @Override
+ public Response intercept(Chain chain) throws IOException {
+ Request originalRequest = chain.request();
+ Request requestWithUserAgent = originalRequest.newBuilder()
+ .header(USER_AGENT_REQUEST_PROPERTY_NAME, ConversationsPlusApplication.getNameAndVersion())
+ .build();
+ return chain.proceed(requestWithUserAgent);
+ }
+ }
+
+ private static class LoggingInterceptor implements Interceptor {
+ @Override public Response intercept(Interceptor.Chain chain) throws IOException {
+ Request request = chain.request();
+
+ long t1 = System.nanoTime();
+ Logging.d(LOGTAG, String.format(Locale.getDefault(), "Sending %s request %s on %s%n%s",
+ request.method(), request.url(), chain.connection(), request.headers()));
+
+ Response response = chain.proceed(request);
+
+ long t2 = System.nanoTime();
+ Logging.d(LOGTAG, String.format(Locale.getDefault(), "Received response for %s request %s in %.1fms%n%s",
+ response.request().method(), response.request().url(), (t2 - t1) / 1e6d, response.headers()));
+
+ return response;
+ }
+ }
+
+ private static class ProgressResponseBody extends ResponseBody {
+
+ private final ResponseBody responseBody;
+ private final ProgressListener progressListener;
+ private BufferedSource bufferedSource;
+
+ public ProgressResponseBody(ResponseBody responseBody, ProgressListener progressListener) {
+ this.responseBody = responseBody;
+ this.progressListener = progressListener;
+ }
+
+ @Override public MediaType contentType() {
+ return responseBody.contentType();
+ }
+
+ @Override public long contentLength() {
+ return responseBody.contentLength();
+ }
+
+ @Override public BufferedSource source() {
+ if (bufferedSource == null) {
+ bufferedSource = Okio.buffer(source(responseBody.source()));
+ }
+ return bufferedSource;
+ }
+
+ private Source source(Source source) {
+ return new ForwardingSource(source) {
+
+ @Override public long read(Buffer sink, long byteCount) throws IOException {
+ long bytesRead = super.read(sink, byteCount);
+ // read() returns the number of bytes read, or -1 if this source is exhausted.
+ boolean done = bytesRead == -1;
+ long currentBytesRead = !done ? bytesRead : 0;
+ progressListener.update(currentBytesRead, responseBody.contentLength(), done);
+ return bytesRead;
+ }
+ };
+ }
+ }
}
diff --git a/src/main/java/de/thedevstack/conversationsplus/http/HttpConnectionManager.java b/src/main/java/de/thedevstack/conversationsplus/http/HttpConnectionManager.java
deleted file mode 100644
index 011e2529..00000000
--- a/src/main/java/de/thedevstack/conversationsplus/http/HttpConnectionManager.java
+++ /dev/null
@@ -1,90 +0,0 @@
-package de.thedevstack.conversationsplus.http;
-
-import org.apache.http.conn.ssl.StrictHostnameVerifier;
-
-import java.io.IOException;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.Proxy;
-import java.security.KeyManagementException;
-import java.security.NoSuchAlgorithmException;
-import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
-
-import javax.net.ssl.HostnameVerifier;
-import javax.net.ssl.HttpsURLConnection;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLSocketFactory;
-import javax.net.ssl.X509TrustManager;
-
-import de.thedevstack.conversationsplus.ConversationsPlusApplication;
-import de.thedevstack.conversationsplus.entities.Message;
-import de.thedevstack.conversationsplus.services.AbstractConnectionManager;
-import de.thedevstack.conversationsplus.utils.CryptoHelper;
-import de.thedevstack.conversationsplus.utils.MessageUtil;
-import de.thedevstack.conversationsplus.utils.SSLSocketHelper;
-
-public class HttpConnectionManager extends AbstractConnectionManager {
- private static HttpConnectionManager INSTANCE;
-
- public static void init() {
- INSTANCE = new HttpConnectionManager();
- }
-
- private List<HttpDownloadConnection> downloadConnections = new CopyOnWriteArrayList<>();
-
- public static HttpDownloadConnection createNewDownloadConnection(Message message) {
- return createNewDownloadConnection(message, false);
- }
-
- public static HttpDownloadConnection createNewDownloadConnection(Message message, boolean interactive) {
- if (MessageUtil.needsDownload(message)) {
- HttpDownloadConnection connection = new HttpDownloadConnection(INSTANCE);
- connection.init(message, interactive);
- INSTANCE.downloadConnections.add(connection);
- return connection;
- }
- return null;
- }
-
- public void finishConnection(HttpDownloadConnection connection) {
- this.downloadConnections.remove(connection);
- }
-
- public static void setupTrustManager(final HttpsURLConnection connection, final boolean interactive) {
- final X509TrustManager trustManager;
- final HostnameVerifier hostnameVerifier;
- if (interactive) {
- trustManager = ConversationsPlusApplication.getMemorizingTrustManager();
- hostnameVerifier = ConversationsPlusApplication.getMemorizingTrustManager().wrapHostnameVerifier(
- new StrictHostnameVerifier());
- } else {
- trustManager = ConversationsPlusApplication.getMemorizingTrustManager()
- .getNonInteractive();
- hostnameVerifier = ConversationsPlusApplication.getMemorizingTrustManager()
- .wrapHostnameVerifierNonInteractive(
- new StrictHostnameVerifier());
- }
- try {
- final SSLContext sc = SSLSocketHelper.getSSLContext();
- sc.init(null, new X509TrustManager[]{trustManager},
- ConversationsPlusApplication.getSecureRandom());
-
- final SSLSocketFactory sf = sc.getSocketFactory();
- final String[] cipherSuites = CryptoHelper.getOrderedCipherSuites(
- sf.getSupportedCipherSuites());
- if (cipherSuites.length > 0) {
- sc.getDefaultSSLParameters().setCipherSuites(cipherSuites);
-
- }
-
- connection.setSSLSocketFactory(sf);
- connection.setHostnameVerifier(hostnameVerifier);
- } catch (final KeyManagementException | NoSuchAlgorithmException ignored) {
- }
- }
-
- public Proxy getProxy() throws IOException {
- return new Proxy(Proxy.Type.HTTP, new InetSocketAddress(InetAddress.getLocalHost(), 8118));
- }
-}
diff --git a/src/main/java/de/thedevstack/conversationsplus/http/HttpDownloadConnection.java b/src/main/java/de/thedevstack/conversationsplus/http/HttpDownloadConnection.java
deleted file mode 100644
index 07f308fe..00000000
--- a/src/main/java/de/thedevstack/conversationsplus/http/HttpDownloadConnection.java
+++ /dev/null
@@ -1,375 +0,0 @@
-package de.thedevstack.conversationsplus.http;
-
-import android.os.PowerManager;
-import android.util.Log;
-
-import java.io.BufferedInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.HttpURLConnection;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.concurrent.CancellationException;
-
-import javax.net.ssl.HttpsURLConnection;
-import javax.net.ssl.SSLHandshakeException;
-
-import de.thedevstack.android.logcat.Logging;
-import de.thedevstack.conversationsplus.ConversationsPlusApplication;
-import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
-import de.thedevstack.conversationsplus.entities.FileParams;
-import de.thedevstack.conversationsplus.enums.FileStatus;
-import de.thedevstack.conversationsplus.exceptions.RemoteFileNotFoundException;
-import de.thedevstack.conversationsplus.utils.MessageUtil;
-import de.thedevstack.conversationsplus.utils.StreamUtil;
-import de.thedevstack.conversationsplus.Config;
-import de.thedevstack.conversationsplus.R;
-import de.thedevstack.conversationsplus.entities.DownloadableFile;
-import de.thedevstack.conversationsplus.entities.Message;
-import de.thedevstack.conversationsplus.entities.Transferable;
-import de.thedevstack.conversationsplus.entities.TransferablePlaceholder;
-import de.thedevstack.conversationsplus.persistance.FileBackend;
-import de.thedevstack.conversationsplus.services.AbstractConnectionManager;
-import de.thedevstack.conversationsplus.services.XmppConnectionService;
-import de.thedevstack.conversationsplus.utils.CryptoHelper;
-import de.thedevstack.conversationsplus.utils.FileUtils;
-import de.thedevstack.conversationsplus.utils.XmppConnectionServiceAccessor;
-
-public class HttpDownloadConnection implements Transferable {
-
- private HttpConnectionManager mHttpConnectionManager;
- private XmppConnectionService mXmppConnectionService;
-
- private URL mUrl;
- private Message message;
- private DownloadableFile file;
- private int mStatus = Transferable.STATUS_UNKNOWN;
- private boolean acceptedAutomatically = false;
- private int mProgress = 0;
- private boolean canceled = false;
-
- public HttpDownloadConnection(HttpConnectionManager manager) {
- this.mHttpConnectionManager = manager;
- this.mXmppConnectionService = XmppConnectionServiceAccessor.xmppConnectionService;
- }
-
- @Override
- public boolean start() {
- if (mXmppConnectionService.hasInternetConnection()) {
- if (this.mStatus == STATUS_OFFER_CHECK_FILESIZE) {
- checkFileSize(true);
- } else {
- new Thread(new FileDownloader(true)).start();
- }
- return true;
- } else {
- return false;
- }
- }
-
- public void init(Message message) {
- init(message, false);
- }
-
- public void init(Message message, boolean interactive) {
- this.message = message;
- this.message.setTransferable(this);
- try {
- String url = (null != message && null != message.getFileParams()) ? message.getFileParams().getUrl() : null;
- if (null == url) {
- /*
- * If this code is reached and the URL is null something went wrong.
- * Try again to extract the file parameters from the message.
- */
- MessageUtil.extractFileParamsFromBody(message);
- url = (null != message.getFileParams()) ? message.getFileParams().getUrl() : null;
- if (null == url) {
- message.setTreatAsDownloadable(Message.Decision.NEVER); // TODO find sth better
- this.cancel();
- return;
- }
- }
- mUrl = new URL(url);
- final String sUrlFilename = mUrl.getPath().substring(mUrl.getPath().lastIndexOf('/') + 1).toLowerCase();
- final String lastPart = FileUtils.getLastExtension(sUrlFilename);
-
- if (!lastPart.isEmpty() && ("pgp".equals(lastPart) || "gpg".equals(lastPart))) {
- this.message.setEncryption(Message.ENCRYPTION_PGP);
- } else if (message.getEncryption() != Message.ENCRYPTION_OTR
- && message.getEncryption() != Message.ENCRYPTION_AXOLOTL) {
- this.message.setEncryption(Message.ENCRYPTION_NONE);
- }
-
- String extension;
- String originalFilename;
- if (!lastPart.isEmpty() && VALID_CRYPTO_EXTENSIONS.contains(lastPart)) {
- extension = FileUtils.getSecondToLastExtension(sUrlFilename);
- originalFilename = sUrlFilename.replace("." + lastPart, "");
- } else {
- extension = lastPart;
- originalFilename = sUrlFilename;
- }
- message.setRelativeFilePath(message.getUuid() + "." + extension);
- this.file = FileBackend.getFile(message, false);
-
- FileParams fileParams = message.getFileParams();
- if (null == fileParams) {
- fileParams = new FileParams();
- message.setFileParams(fileParams);
- }
- fileParams.setOriginalFilename(originalFilename);
-
- if ((this.message.getEncryption() == Message.ENCRYPTION_OTR
- || this.message.getEncryption() == Message.ENCRYPTION_AXOLOTL)
- && this.file.getKey() == null) {
- this.message.setEncryption(Message.ENCRYPTION_NONE);
- }
- checkFileSize(interactive);
- } catch (MalformedURLException e) {
- this.cancel();
- }
- }
-
- private void checkFileSize(boolean interactive) {
- new Thread(new FileSizeChecker(interactive)).start();
- }
-
- @Override
- public void cancel() {
- this.canceled = true;
- mHttpConnectionManager.finishConnection(this);
- if (message.isFileOrImage()) {
- message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED));
- } else {
- message.setTransferable(null);
- }
- mXmppConnectionService.updateConversationUi();
- }
-
- private void finish() {
- FileBackend.updateMediaScanner(file, mXmppConnectionService);
- message.setTransferable(null);
- MessageUtil.setAndSaveFileStatus(this.message, FileStatus.DOWNLOADED);
- mHttpConnectionManager.finishConnection(this);
- if (message.getEncryption() == Message.ENCRYPTION_PGP) {
- message.getConversation().getAccount().getPgpDecryptionService().add(message);
- }
- mXmppConnectionService.updateConversationUi();
- if (acceptedAutomatically) {
- mXmppConnectionService.getNotificationService().push(message);
- }
- }
-
- private void changeStatus(int status) {
- this.mStatus = status;
- mXmppConnectionService.updateConversationUi();
- }
-
- private void showToastForException(Exception e) {
- e.printStackTrace();
- if (e instanceof java.net.UnknownHostException) {
- mXmppConnectionService.showErrorToastInUi(R.string.download_failed_server_not_found);
- } else if (e instanceof java.net.ConnectException) {
- mXmppConnectionService.showErrorToastInUi(R.string.download_failed_could_not_connect);
- } else if (!(e instanceof CancellationException)) {
- mXmppConnectionService.showErrorToastInUi(R.string.download_failed_file_not_found);
- }
- }
-
- private class FileSizeChecker implements Runnable {
-
- private boolean interactive = false;
-
- public FileSizeChecker(boolean interactive) {
- this.interactive = interactive;
- }
-
- @Override
- public void run() {
- long size;
- try {
- size = retrieveFileSize();
- } catch (SSLHandshakeException e) {
- changeStatus(STATUS_OFFER_CHECK_FILESIZE);
- HttpDownloadConnection.this.acceptedAutomatically = false;
- HttpDownloadConnection.this.mXmppConnectionService.getNotificationService().push(message);
- return;
- } catch (RemoteFileNotFoundException e) {
- message.setNoDownloadable(); // TODO Set remote file status to not-available
- cancel();
- return;
- } catch (IOException e) {
- Log.d(Config.LOGTAG, "io exception in http file size checker: " + e.getMessage());
- if (interactive) {
- showToastForException(e);
- }
- cancel();
- return;
- }
- file.setExpectedSize(size);
- if (mHttpConnectionManager.hasStoragePermission()
- && size != -1
- && size <= ConversationsPlusPreferences.autoAcceptFileSize()
- && mXmppConnectionService.isDownloadAllowedInConnection()) {
- HttpDownloadConnection.this.acceptedAutomatically = true;
- new Thread(new FileDownloader(interactive)).start();
- } else {
- changeStatus(STATUS_OFFER);
- HttpDownloadConnection.this.acceptedAutomatically = false;
- HttpDownloadConnection.this.mXmppConnectionService.getNotificationService().push(message);
- }
- }
-
- private long retrieveFileSize() throws IOException {
- try {
- Logging.d(Config.LOGTAG, "retrieve file size. interactive:" + String.valueOf(interactive));
- changeStatus(STATUS_CHECKING);
- HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection();
- connection.setRequestMethod("HEAD");
- Logging.d(Config.LOGTAG, "url: " + connection.getURL().toString());
- Logging.d(Config.LOGTAG, "connection: " + connection.toString());
- connection.setRequestProperty("User-Agent", ConversationsPlusApplication.getNameAndVersion());
- // https://code.google.com/p/android/issues/detail?id=24672
- connection.setRequestProperty("Accept-Encoding", "");
- if (connection instanceof HttpsURLConnection) {
- mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive);
- }
- connection.connect();
- if (connection.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) {
- Logging.d(Config.LOGTAG, "remote file not found");
- throw new RemoteFileNotFoundException();
- }
- String contentLength = connection.getHeaderField("Content-Length");
- connection.disconnect();
- if (contentLength == null) {
- return -1;
- }
- return Long.parseLong(contentLength, 10);
- } catch (RemoteFileNotFoundException e) {
- throw e;
- } catch (IOException e) {
- return -1;
- } catch (NumberFormatException e) {
- return -1;
- }
- }
-
- }
-
- private class FileDownloader implements Runnable {
-
- private boolean interactive = false;
-
- private OutputStream os;
-
- public FileDownloader(boolean interactive) {
- this.interactive = interactive;
- }
-
- @Override
- public void run() {
- try {
- changeStatus(STATUS_DOWNLOADING);
- download();
- updateImageBounds();
- finish();
- } catch (SSLHandshakeException e) {
- changeStatus(STATUS_OFFER);
- } catch (Exception e) {
- if (interactive) {
- showToastForException(e);
- }
- cancel();
- }
- }
-
- private void download() throws SSLHandshakeException, IOException {
- InputStream is = null;
- PowerManager.WakeLock wakeLock = ConversationsPlusApplication.createPartialWakeLock("http_download_"+message.getUuid());
- try {
- wakeLock.acquire();
- HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection();
- if (connection instanceof HttpsURLConnection) {
- mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive);
- }
- connection.setRequestProperty("User-Agent", ConversationsPlusApplication.getNameAndVersion());
- final boolean tryResume = file.exists() && file.getKey() == null;
- if (tryResume) {
- Logging.d(Config.LOGTAG, "http download trying resume");
- long size = file.getSize();
- connection.setRequestProperty("Range", "bytes="+size+"-");
- }
- connection.connect();
- is = new BufferedInputStream(connection.getInputStream());
- boolean serverResumed = "bytes".equals(connection.getHeaderField("Accept-Ranges"));
- long transmitted = 0;
- long expected = file.getExpectedSize();
- if (tryResume && serverResumed) {
- Logging.d(Config.LOGTAG, "server resumed");
- transmitted = file.getSize();
- updateProgress((int) ((((double) transmitted) / expected) * 100));
- os = AbstractConnectionManager.createAppendedOutputStream(file);
- } else {
- file.getParentFile().mkdirs();
- file.createNewFile();
- os = AbstractConnectionManager.createOutputStream(file, true);
- }
- int count = -1;
- byte[] buffer = new byte[1024];
- while ((count = is.read(buffer)) != -1) {
- transmitted += count;
- os.write(buffer, 0, count);
- updateProgress((int) ((((double) transmitted) / expected) * 100));
- if (canceled) {
- throw new CancellationException();
- }
- }
- } catch (CancellationException | IOException e) {
- throw e;
- } finally {
- if (os != null) {
- try {
- os.flush();
- } catch (final IOException ignored) {
-
- }
- }
- StreamUtil.close(os);
- StreamUtil.close(is);
- wakeLock.release();
- }
- }
-
- private void updateImageBounds() {
- message.setType(Message.TYPE_FILE);
- MessageUtil.updateFileParams(message, mUrl);
- mXmppConnectionService.updateMessage(message);
- }
-
- }
-
- public void updateProgress(int i) {
- this.mProgress = i;
- mXmppConnectionService.updateConversationUi();
- }
-
- @Override
- public int getStatus() {
- return this.mStatus;
- }
-
- @Override
- public long getFileSize() {
- if (this.file != null) {
- return this.file.getExpectedSize();
- } else {
- return 0;
- }
- }
-
- @Override
- public int getProgress() {
- return this.mProgress;
- }
-}
diff --git a/src/main/java/de/thedevstack/conversationsplus/http/HttpHeadRetrievedListener.java b/src/main/java/de/thedevstack/conversationsplus/http/HttpHeadRetrievedListener.java
new file mode 100644
index 00000000..0db4f71b
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/http/HttpHeadRetrievedListener.java
@@ -0,0 +1,10 @@
+package de.thedevstack.conversationsplus.http;
+
+import de.thedevstack.conversationsplus.entities.Message;
+
+/**
+ *
+ */
+public interface HttpHeadRetrievedListener {
+ void onFileSizeRetrieved(long size, Message message);
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/http/ProgressListener.java b/src/main/java/de/thedevstack/conversationsplus/http/ProgressListener.java
new file mode 100644
index 00000000..b834eae1
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/http/ProgressListener.java
@@ -0,0 +1,14 @@
+package de.thedevstack.conversationsplus.http;
+
+/**
+ *
+ */
+public interface ProgressListener {
+ /**
+ * React on transferred bytes.
+ * @param bytesRead the number of bytes transferred in the current chunk
+ * @param contentLength the total number of bytes to be transferred
+ * @param done whether there are bytes left or not
+ */
+ void update(long bytesRead, long contentLength, boolean done);
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/parser/MessageParser.java b/src/main/java/de/thedevstack/conversationsplus/parser/MessageParser.java
index 53fbb107..4b6ed240 100644
--- a/src/main/java/de/thedevstack/conversationsplus/parser/MessageParser.java
+++ b/src/main/java/de/thedevstack/conversationsplus/parser/MessageParser.java
@@ -5,6 +5,8 @@ import android.util.Pair;
import de.thedevstack.conversationsplus.entities.FileParams;
import de.thedevstack.conversationsplus.enums.FileStatus;
+import de.thedevstack.conversationsplus.services.filetransfer.http.download.AutomaticFileDownload;
+import de.thedevstack.conversationsplus.services.filetransfer.http.download.HttpRetrieveHead;
import de.thedevstack.conversationsplus.utils.MessageUtil;
import de.thedevstack.conversationsplus.xmpp.httpuploadim.HttpUploadHint;
import de.tzur.conversations.Settings;
@@ -29,7 +31,6 @@ import de.thedevstack.conversationsplus.entities.Contact;
import de.thedevstack.conversationsplus.entities.Conversation;
import de.thedevstack.conversationsplus.entities.Message;
import de.thedevstack.conversationsplus.entities.MucOptions;
-import de.thedevstack.conversationsplus.http.HttpConnectionManager;
import de.thedevstack.conversationsplus.services.AvatarService;
import de.thedevstack.conversationsplus.services.MessageArchiveService;
import de.thedevstack.conversationsplus.services.XmppConnectionService;
@@ -466,21 +467,23 @@ public class MessageParser extends AbstractParser implements
conversation.endOtrIfNeeded();
}
+ MessageUtil.extractFileParamsFromBody(message);
+ FileParams fileParams = message.getFileParams();
+ if (message.treatAsDownloadable() != Message.Decision.NEVER && message.treatAsDownloadable() != Message.Decision.NOT_DECIDED) {
+ if (null != fileParams) {
+ fileParams.setFileStatus(FileStatus.NEEDS_DOWNLOAD);
+ }
+ }
if (message.getEncryption() == Message.ENCRYPTION_NONE || !ConversationsPlusPreferences.dontSaveEncrypted()) {
mXmppConnectionService.databaseBackend.createMessage(message);
}
- MessageUtil.extractFileParamsFromBody(message);
- FileParams fileParams = message.getFileParams();
- if (message.treatAsDownloadable() != Message.Decision.NEVER && message.treatAsDownloadable() != Message.Decision.NOT_DECIDED) {
- if (null != fileParams) {
- fileParams.setFileStatus(FileStatus.NEEDS_DOWNLOAD);
- }
- }
if (message.trusted()
&& message.treatAsDownloadable() != Message.Decision.NEVER
&& ConversationsPlusPreferences.autoAcceptFileSize() > 0
&& (message.isHttpUploaded() || ConversationsPlusPreferences.autoDownloadFileLink())) {
- HttpConnectionManager.createNewDownloadConnection(message);
+ HttpRetrieveHead hrh = new HttpRetrieveHead(message);
+ hrh.setListener(new AutomaticFileDownload(true));
+ hrh.retrieveAndSetContentTypeAndLength();
} else {
if (query == null) {
mXmppConnectionService.getNotificationService().push(message);
diff --git a/src/main/java/de/thedevstack/conversationsplus/services/XmppConnectionService.java b/src/main/java/de/thedevstack/conversationsplus/services/XmppConnectionService.java
index ee0ee14a..6a9b5c13 100644
--- a/src/main/java/de/thedevstack/conversationsplus/services/XmppConnectionService.java
+++ b/src/main/java/de/thedevstack/conversationsplus/services/XmppConnectionService.java
@@ -2387,7 +2387,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
public void sendReadMarker(final Conversation conversation) {
final Message markable = conversation.getLatestMarkableMessage();
- Logging.d("markRead", "XmppConnectionService.sendReadMarker (" + conversation.getName() + ")");
+ //Logging.d("markRead", "XmppConnectionService.sendReadMarker (" + conversation.getName() + ")");
if (this.markRead(conversation)) {
updateConversationUi();
}
diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferEntity.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferEntity.java
index e1b40fa6..06e697db 100644
--- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferEntity.java
+++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferEntity.java
@@ -49,10 +49,16 @@ public class FileTransferEntity implements Transferable {
* This initialization includes loading the file and associating this transferable to the message.
* @param message the message in which the file to transfer is contained.
*/
- public FileTransferEntity(Message message) {
+ public FileTransferEntity(Message message, boolean initFile) {
this.message = message;
this.message.setTransferable(this);
- this.file = FileBackend.getFile(message, false);
+ if (initFile) {
+ this.initFile();
+ }
+ }
+
+ protected final void initFile() {
+ this.file = FileBackend.getFile(this.message, false);
}
/**
@@ -68,11 +74,11 @@ public class FileTransferEntity implements Transferable {
/**
* Returns the global transferable status.
*
- * @return {@value STATUS_FAILED} if #isFailed returns <code>true</code>, {@value STATUS_UPLOADING} otherwise
+ * @return {@value STATUS_FAILED} if #isFailed returns <code>true</code>, {@value STATUS_UNKNOWN} otherwise
*/
@Override
public int getStatus() {
- int status = (isFailed()) ? STATUS_FAILED : STATUS_UPLOADING;
+ int status = (isFailed()) ? STATUS_FAILED : STATUS_UNKNOWN;
return status;
}
@@ -80,7 +86,6 @@ public class FileTransferEntity implements Transferable {
* Returns the expected file size of the underlying file.
* @return the expected file size or 0 if no file is associated.
*/
- @Override
public long getFileSize() {
return file == null ? 0 : file.getExpectedSize();
}
diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferManager.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferManager.java
index 2f9a819b..8d971bb2 100644
--- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferManager.java
+++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferManager.java
@@ -9,7 +9,7 @@ import de.thedevstack.android.logcat.Logging;
import de.thedevstack.conversationsplus.Config;
import de.thedevstack.conversationsplus.entities.Message;
import de.thedevstack.conversationsplus.services.FileTransferService;
-import de.thedevstack.conversationsplus.services.filetransfer.http.upload.HttpFileTransferEntity;
+import de.thedevstack.conversationsplus.services.filetransfer.http.upload.HttpUploadFileTransferEntity;
import de.thedevstack.conversationsplus.utils.MessageUtil;
/**
@@ -73,7 +73,7 @@ public class FileTransferManager implements FileTransferStatusListener {
* @return <code>true</code> if the file transfer was successful, <code>false</code> otherwise
*/
public boolean transferFile(Message message, boolean delay) {
- Logging.d(Config.LOGTAG, "send file message");
+ Logging.d(Config.LOGTAG, "transfer file message");
boolean transferSuccessfullyStarted = false;
for (WeightedTransferService wts : this.transferServices) {
try {
@@ -91,13 +91,13 @@ public class FileTransferManager implements FileTransferStatusListener {
}
/**
- * Checks whether a message can be sent using this service or not.
+ * Checks whether a message can be sent or received using this service or not.
*
* @param message the message to be checked
* @return <code>true</code> if the message can be processed, <code>false</code> otherwise
*/
public boolean accept(Message message) {
- return message.needsUploading();
+ return message.needsUploading() || MessageUtil.needsDownload(message);
}
@Override
@@ -106,7 +106,7 @@ public class FileTransferManager implements FileTransferStatusListener {
if (null == wts) {
return;
}
- boolean delayed = (entity instanceof HttpFileTransferEntity) && ((HttpFileTransferEntity) entity).isDelayed();
+ boolean delayed = (entity instanceof HttpUploadFileTransferEntity) && ((HttpUploadFileTransferEntity) entity).isDelayed();
if (failureReason.isRecoverable()) {
wts.fileTransferService.transferFile(entity.getMessage(), delayed);
} else {
diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/delete/DeleteTokenReceived.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/delete/DeleteTokenReceived.java
index 83a250de..d61a5c6a 100644
--- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/delete/DeleteTokenReceived.java
+++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/delete/DeleteTokenReceived.java
@@ -42,7 +42,7 @@ public class DeleteTokenReceived implements OnIqPacketReceived {
String url = this.remoteFile.getPath();
String deleteToken = DeleteSlotPacketParser.parseDeleteToken(packet);
Logging.d("filetransfer.http.delete", "Got delete token '" + deleteToken + "' for remote file '" + remoteFile.getPath() + "'");
- OkHttpClient client = HttpClient.getClient(true);
+ OkHttpClient client = HttpClient.getOkHttpClient(true);
Request request = new Request.Builder()
.url(url)
.addHeader(HEADER_NAME_DELETE_TOKEN, deleteToken)
diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/download/AutomaticFileDownload.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/download/AutomaticFileDownload.java
new file mode 100644
index 00000000..5885e2e1
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/download/AutomaticFileDownload.java
@@ -0,0 +1,61 @@
+package de.thedevstack.conversationsplus.services.filetransfer.http.download;
+
+import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.ConversationsPlusApplication;
+import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
+import de.thedevstack.conversationsplus.entities.Message;
+import de.thedevstack.conversationsplus.http.HttpHeadRetrievedListener;
+import de.thedevstack.conversationsplus.services.filetransfer.FileTransferManager;
+import de.thedevstack.conversationsplus.utils.XmppConnectionServiceAccessor;
+
+/**
+ *
+ */
+public class AutomaticFileDownload implements HttpHeadRetrievedListener {
+ private boolean notify;
+
+ public AutomaticFileDownload(boolean notify) {
+ this.notify = notify;
+ }
+
+ @Override
+ public void onFileSizeRetrieved(long size, Message message) {
+ if (!this.transferFile(message)) {
+
+ }
+ if (this.notify) {
+ XmppConnectionServiceAccessor.xmppConnectionService.getNotificationService().push(message);
+ }
+ }
+
+ /**
+ * Transfers a file for the corresponding message.
+ *
+ * @param message the message containing the file to transfer
+ * @return <code>true</code> if the file transfer was successful, <code>false</code> otherwise
+ */
+ public boolean transferFile(Message message) {
+ if (this.accept(message)) {
+ Logging.d("http-download", "Starting automatic download");
+ FileTransferManager ftm = FileTransferManager.getInstance();
+ if (ftm.accept(message)) {
+ return ftm.transferFile(message);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Checks whether a message can be sent using this service or not.
+ *
+ * @param message the message to be checked
+ * @return <code>true</code> if the message can be processed, <code>false</code> otherwise
+ */
+ public boolean accept(Message message) {
+ long size = message.getFileParams().getSize();
+ return ConversationsPlusApplication.hasStoragePermission()
+ && size > -1
+ && size <= ConversationsPlusPreferences.autoAcceptFileSize()
+ && XmppConnectionServiceAccessor.xmppConnectionService.isDownloadAllowedInConnection();
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/download/HttpDownloadFileTransferEntity.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/download/HttpDownloadFileTransferEntity.java
new file mode 100644
index 00000000..fcd3904b
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/download/HttpDownloadFileTransferEntity.java
@@ -0,0 +1,53 @@
+package de.thedevstack.conversationsplus.services.filetransfer.http.download;
+
+import android.os.PowerManager;
+
+import de.thedevstack.conversationsplus.entities.FileParams;
+import de.thedevstack.conversationsplus.entities.Message;
+import de.thedevstack.conversationsplus.http.ProgressListener;
+import de.thedevstack.conversationsplus.services.filetransfer.FileTransferEntity;
+import de.thedevstack.conversationsplus.utils.UiUpdateHelper;
+
+/**
+ *
+ */
+public class HttpDownloadFileTransferEntity extends FileTransferEntity implements ProgressListener {
+ public PowerManager.WakeLock wakeLock;
+ /**
+ * Initializes the FileTransferEntity based on the associated message.
+ * This initialization includes loading the file and associating this transferable to the message.
+ *
+ * @param message the message in which the file to transfer is contained.
+ */
+ public HttpDownloadFileTransferEntity(Message message) {
+ super(message, false);
+ FileParams fileParams = message.getFileParams();
+ this.initFile();
+ this.getFile().setExpectedSize(fileParams.getSize());
+ }
+
+ /**
+ * Returns the global transferable status.
+ *
+ * @return {@value STATUS_FAILED} if #isFailed returns <code>true</code>, {@value STATUS_DOWNLOADING} otherwise
+ */
+ @Override
+ public int getStatus() {
+ int status = (isFailed()) ? STATUS_FAILED : STATUS_DOWNLOADING;
+ return status;
+ }
+
+ @Override
+ public void update(long bytesRead, long contentLength, boolean done) {
+ if (0 >= getFile().getExpectedSize()) {
+ getFile().setExpectedSize(contentLength);
+ getMessage().getFileParams().setSize(contentLength);
+ }
+ if (done) {
+ this.transferred();
+ } else {
+ this.updateProgress(bytesRead);
+ }
+ UiUpdateHelper.updateConversationUi();
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/download/HttpDownloadFileTransferService.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/download/HttpDownloadFileTransferService.java
new file mode 100644
index 00000000..c7fcc430
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/download/HttpDownloadFileTransferService.java
@@ -0,0 +1,65 @@
+package de.thedevstack.conversationsplus.services.filetransfer.http.download;
+
+import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.ConversationsPlusApplication;
+import de.thedevstack.conversationsplus.entities.FileParams;
+import de.thedevstack.conversationsplus.entities.Message;
+import de.thedevstack.conversationsplus.enums.FileStatus;
+import de.thedevstack.conversationsplus.http.HttpClient;
+import de.thedevstack.conversationsplus.services.filetransfer.AbstractFileTransferService;
+import de.thedevstack.conversationsplus.utils.MessageUtil;
+import de.thedevstack.conversationsplus.utils.XmppConnectionServiceAccessor;
+import okhttp3.Call;
+
+/**
+ *
+ */
+public class HttpDownloadFileTransferService extends AbstractFileTransferService {
+ /**
+ * Transfers a file for the corresponding message.
+ *
+ * @param message the message containing the file to transfer
+ * @return <code>true</code> if the file transfer was successful, <code>false</code> otherwise
+ */
+ @Override
+ public boolean transferFile(Message message) {
+ return this.transferFile(message, false);
+ }
+
+ /**
+ * Transfers a file for the corresponding message.
+ *
+ * @param message the message containing the file to transfer
+ * @param delay whether the message is delayed or not
+ * @return <code>true</code> if the file transfer was successful, <code>false</code> otherwise
+ */
+ @Override
+ public boolean transferFile(Message message, boolean delay) {
+ Logging.d("http-download", "Get file from remote host");
+ final HttpDownloadFileTransferEntity entity = new HttpDownloadFileTransferEntity(message);
+ if (message.getFileParams().getFileStatus() == FileStatus.NEEDS_DOWNLOAD
+ && ConversationsPlusApplication.hasStoragePermission()
+ && XmppConnectionServiceAccessor.xmppConnectionService.isDownloadAllowedInConnection()) {
+ FileParams fileParams = message.getFileParams();
+ MessageUtil.setAndSaveFileStatus(message, FileStatus.DOWNLOADING);
+ entity.wakeLock = ConversationsPlusApplication.createPartialWakeLock("http_download_" + entity.getMessage().getUuid());
+ entity.wakeLock.acquire();
+ Call call = HttpClient.openCancelableAndProgressListenedCall(fileParams.getUrl(), entity, false);
+ call.enqueue(new HttpFileDownloadCallback(entity));
+ }
+
+ return true;
+ }
+
+ /**
+ * Checks whether a message can be sent using this service or not.
+ *
+ * @param message the message to be checked
+ * @return <code>true</code> if the message can be processed, <code>false</code> otherwise
+ */
+ @Override
+ public boolean accept(Message message) {
+ return MessageUtil.needsDownload(message)
+ && message.hasFileAttached() && (null == message.getFileParams() || message.getFileParams().isRemoteAvailable());
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/download/HttpFileDownloadCallback.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/download/HttpFileDownloadCallback.java
new file mode 100644
index 00000000..2c31754c
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/download/HttpFileDownloadCallback.java
@@ -0,0 +1,57 @@
+package de.thedevstack.conversationsplus.services.filetransfer.http.download;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.entities.DownloadableFile;
+import de.thedevstack.conversationsplus.enums.FileStatus;
+import de.thedevstack.conversationsplus.persistance.FileBackend;
+import de.thedevstack.conversationsplus.services.AbstractConnectionManager;
+import de.thedevstack.conversationsplus.utils.MessageUtil;
+import de.thedevstack.conversationsplus.utils.StreamUtil;
+import de.thedevstack.conversationsplus.utils.XmppConnectionServiceAccessor;
+import okhttp3.Call;
+import okhttp3.Callback;
+import okhttp3.Response;
+
+/**
+ *
+ */
+public class HttpFileDownloadCallback implements Callback {
+ private HttpDownloadFileTransferEntity entity;
+
+ public HttpFileDownloadCallback(HttpDownloadFileTransferEntity entity) {
+ this.entity = entity;
+ }
+
+
+ @Override
+ public void onFailure(Call call, IOException e) {
+ changeStatus(FileStatus.DOWNLOAD_FAILED);
+ }
+
+ @Override
+ public void onResponse(Call call, Response response) throws IOException {
+ if (response.isSuccessful()) {
+ Logging.d("http-download", "Receiving file from remote host");
+ DownloadableFile file = this.entity.getFile();
+ OutputStream os = AbstractConnectionManager.createOutputStream(file, true);
+ os.write(response.body().bytes());
+ StreamUtil.close(os);
+ FileBackend.updateMediaScanner(file, XmppConnectionServiceAccessor.xmppConnectionService);
+ this.entity.transferred();
+ changeStatus(FileStatus.DOWNLOADED);
+ } else {
+ Logging.e("http-download", "Failed to retrieve file from remote host. HTTP response: " + response.code() + ", " + response.body().string());
+ changeStatus(FileStatus.DOWNLOAD_FAILED);
+ }
+ if (entity.wakeLock.isHeld()) {
+ entity.wakeLock.release();
+ }
+ }
+
+ private void changeStatus(FileStatus status) {
+ MessageUtil.setAndSaveFileStatus(this.entity.getMessage(), status);
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/download/HttpRetrieveHead.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/download/HttpRetrieveHead.java
new file mode 100644
index 00000000..8d23d9c0
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/download/HttpRetrieveHead.java
@@ -0,0 +1,112 @@
+package de.thedevstack.conversationsplus.services.filetransfer.http.download;
+
+import java.io.IOException;
+
+import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.entities.FileParams;
+import de.thedevstack.conversationsplus.entities.Message;
+import de.thedevstack.conversationsplus.enums.FileStatus;
+import de.thedevstack.conversationsplus.http.Http;
+import de.thedevstack.conversationsplus.http.HttpClient;
+import de.thedevstack.conversationsplus.http.HttpHeadRetrievedListener;
+import de.thedevstack.conversationsplus.persistance.DatabaseBackend;
+import de.thedevstack.conversationsplus.utils.MessageUtil;
+import de.thedevstack.conversationsplus.utils.UiUpdateHelper;
+import okhttp3.Call;
+import okhttp3.Callback;
+import okhttp3.MediaType;
+import okhttp3.Response;
+
+/**
+ *
+ */
+public class HttpRetrieveHead implements Http, Callback {
+ private static final String LOGTAG = "http-retrieve-head";
+
+ private Message message;
+ private String url;
+
+ private HttpHeadRetrievedListener listener;
+
+ public HttpRetrieveHead(Message message) {
+ this.message = message;
+ this.url = (null != message && null != message.getFileParams()) ? message.getFileParams().getUrl() : null;
+ if (null == this.url) {
+ /*
+ * If this code is reached and the URL is null something went wrong.
+ * Try again to extract the file parameters from the message.
+ */
+ MessageUtil.extractFileParamsFromBody(message);
+ this.url = (null != message.getFileParams()) ? message.getFileParams().getUrl() : null;
+ if (null == this.url) {
+ message.setTreatAsDownloadable(Message.Decision.NEVER); // TODO find sth better
+ if (null != message.getFileParams()) {
+ MessageUtil.setAndSaveFileStatus(message, FileStatus.NOT_FOUND);
+ }
+ }
+ }
+ }
+
+ public void retrieveAndSetContentTypeAndLength() {
+ if (null != this.url) {
+ Logging.d(LOGTAG, "retrieve file size and mime type.");
+
+ try {
+ MessageUtil.setAndSaveFileStatus(message, FileStatus.CHECKING_FILE_SIZE);
+ HttpClient.retrieveHead(this.url, this);
+ } catch (IOException e) {
+ Logging.e(LOGTAG, "Error while trying to call '" + url + "'.", e);
+ }
+ }
+ }
+
+ private static long parseContentLength(String contentLength) {
+ long length = -1;
+ if (null != contentLength) {
+ try {
+ length = Long.parseLong(contentLength, 10);
+ } catch (NumberFormatException e) {
+ }
+ }
+ return length;
+ }
+
+ @Override
+ public void onFailure(Call call, IOException e) {
+ Logging.e(LOGTAG, "Error while trying to call '" + call.request().url() + "'.", e);
+ }
+
+ @Override
+ public void onResponse(Call call, Response response) throws IOException {
+ Logging.d(LOGTAG, "Response for retrieving file size and mime type received.");
+ FileParams fileParams = message.getFileParams();
+ if (response.isSuccessful()) {
+ MediaType mediaType = response.body().contentType();
+ String contentType = null != mediaType ? (mediaType.type() + "/" + mediaType.subtype()) : response.header(MIME_REQUEST_PROPERTY_NAME);
+ String contentLength = response.header(HEADER_NAME_CONTENT_LENGTH);
+
+ long size = parseContentLength(contentLength);
+ fileParams.setSize(size);
+ fileParams.setMimeType(contentType);
+ if (0 < size) {
+ fileParams.setFileStatus(FileStatus.NEEDS_DOWNLOAD);
+ }
+ DatabaseBackend.getInstance().updateMessage(message);
+ UiUpdateHelper.updateConversationUi();
+ if (null != this.listener) {
+ this.listener.onFileSizeRetrieved(size, this.message);
+ }
+ } else {
+ if (response.code() == HTTP_NOT_FOUND) {
+ Logging.d(LOGTAG, "remote file '" + response.request().url() + "' not found.");
+ MessageUtil.setAndSaveFileStatus(message, FileStatus.NOT_FOUND);
+ } else {
+ Logging.d(LOGTAG, "remote file '" + response.request().url() + "' not loaded - response code: " + response.code() + ".");
+ }
+ }
+ }
+
+ public void setListener(HttpHeadRetrievedListener listener) {
+ this.listener = listener;
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpFileUploader.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpFileUploader.java
index 8cae599c..73c18f15 100644
--- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpFileUploader.java
+++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpFileUploader.java
@@ -2,23 +2,32 @@ package de.thedevstack.conversationsplus.services.filetransfer.http.upload;
import android.os.PowerManager;
+import org.apache.http.conn.ssl.StrictHostnameVerifier;
+
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.UnknownHostException;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
import java.util.Scanner;
+import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.X509TrustManager;
import de.thedevstack.android.logcat.Logging;
import de.thedevstack.conversationsplus.ConversationsPlusApplication;
import de.thedevstack.conversationsplus.entities.DownloadableFile;
import de.thedevstack.conversationsplus.entities.Message;
-import de.thedevstack.conversationsplus.http.HttpConnectionManager;
import de.thedevstack.conversationsplus.persistance.FileBackend;
import de.thedevstack.conversationsplus.services.filetransfer.FileTransferFailureReason;
+import de.thedevstack.conversationsplus.utils.CryptoHelper;
+import de.thedevstack.conversationsplus.utils.SSLSocketHelper;
import de.thedevstack.conversationsplus.utils.StreamUtil;
import de.thedevstack.conversationsplus.utils.UiUpdateHelper;
import de.thedevstack.conversationsplus.utils.XmppConnectionServiceAccessor;
@@ -29,9 +38,9 @@ public class HttpFileUploader implements Runnable {
private static final String MIME_REQUEST_PROPERTY_NAME = "Content-Type";
private static final String USER_AGENT_REQUEST_PROPERTY_NAME = "User-Agent";
private static final int BUFFER_LENGTH = 4096;
- private final HttpFileTransferEntity entity;
+ private final HttpUploadFileTransferEntity entity;
- public HttpFileUploader(HttpFileTransferEntity entity) {
+ public HttpFileUploader(HttpUploadFileTransferEntity entity) {
this.entity = entity;
}
@@ -55,7 +64,7 @@ public class HttpFileUploader implements Runnable {
connection = (HttpURLConnection) putUrl.openConnection();
if (connection instanceof HttpsURLConnection) {
- HttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, true);
+ setupTrustManager((HttpsURLConnection) connection, true);
}
connection.setRequestMethod(HTTP_METHOD);
connection.setFixedLengthStreamingMode((int) file.getExpectedSize());
@@ -147,4 +156,37 @@ public class HttpFileUploader implements Runnable {
}
return httpResponseMessage;
}
+
+ public static void setupTrustManager(final HttpsURLConnection connection, final boolean interactive) {
+ final X509TrustManager trustManager;
+ final HostnameVerifier hostnameVerifier;
+ if (interactive) {
+ trustManager = ConversationsPlusApplication.getMemorizingTrustManager();
+ hostnameVerifier = ConversationsPlusApplication.getMemorizingTrustManager().wrapHostnameVerifier(
+ new StrictHostnameVerifier());
+ } else {
+ trustManager = ConversationsPlusApplication.getMemorizingTrustManager()
+ .getNonInteractive();
+ hostnameVerifier = ConversationsPlusApplication.getMemorizingTrustManager()
+ .wrapHostnameVerifierNonInteractive(
+ new StrictHostnameVerifier());
+ }
+ try {
+ final SSLContext sc = SSLSocketHelper.getSSLContext();
+ sc.init(null, new X509TrustManager[]{trustManager},
+ ConversationsPlusApplication.getSecureRandom());
+
+ final SSLSocketFactory sf = sc.getSocketFactory();
+ final String[] cipherSuites = CryptoHelper.getOrderedCipherSuites(
+ sf.getSupportedCipherSuites());
+ if (cipherSuites.length > 0) {
+ sc.getDefaultSSLParameters().setCipherSuites(cipherSuites);
+
+ }
+
+ connection.setSSLSocketFactory(sf);
+ connection.setHostnameVerifier(hostnameVerifier);
+ } catch (final KeyManagementException | NoSuchAlgorithmException ignored) {
+ }
+ }
} \ No newline at end of file
diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpFileTransferEntity.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpUploadFileTransferEntity.java
index 985dafcb..54186983 100644
--- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpFileTransferEntity.java
+++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpUploadFileTransferEntity.java
@@ -18,15 +18,15 @@ import de.thedevstack.conversationsplus.xmpp.filetransfer.http.upload.HttpUpload
/**
*
*/
-public class HttpFileTransferEntity extends FileTransferEntity {
+public class HttpUploadFileTransferEntity extends FileTransferEntity {
private HttpUploadSlot slot;
private final byte[] key;
private final boolean delayed;
- public HttpFileTransferEntity(Message message, boolean delayed) {
- super(message);
+ public HttpUploadFileTransferEntity(Message message, boolean delayed) {
+ super(message, true);
this.getMessage().setHttpUploaded(true);
- this.getMessage().setNoDownloadable(); // TODO Set rmeote file status to uploaded
+ this.getMessage().setNoDownloadable();
FileParams fileParams = this.getMessage().getFileParams();
if (null == fileParams) {
fileParams = new FileParams();
@@ -45,6 +45,17 @@ public class HttpFileTransferEntity extends FileTransferEntity {
this.delayed = delayed;
}
+ /**
+ * Returns the global transferable status.
+ *
+ * @return {@value STATUS_FAILED} if #isFailed returns <code>true</code>, {@value STATUS_UPLOADING} otherwise
+ */
+ @Override
+ public int getStatus() {
+ int status = (isFailed()) ? STATUS_FAILED : STATUS_UPLOADING;
+ return status;
+ }
+
public void setSlot(HttpUploadSlot slot) {
this.slot = slot;
}
diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpUploadFileTransferService.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpUploadFileTransferService.java
index f0bb438d..c2e92a9e 100644
--- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpUploadFileTransferService.java
+++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpUploadFileTransferService.java
@@ -9,7 +9,6 @@ import de.thedevstack.android.logcat.Logging;
import de.thedevstack.conversationsplus.entities.Account;
import de.thedevstack.conversationsplus.entities.DownloadableFile;
import de.thedevstack.conversationsplus.entities.Message;
-import de.thedevstack.conversationsplus.persistance.FileBackend;
import de.thedevstack.conversationsplus.services.AbstractConnectionManager;
import de.thedevstack.conversationsplus.services.FileTransferService;
import de.thedevstack.conversationsplus.services.filetransfer.AbstractFileTransferService;
@@ -53,7 +52,7 @@ public class HttpUploadFileTransferService extends AbstractFileTransferService i
Logging.d("httpupload", "Starting to upload file");
boolean started = false;
try {
- final HttpFileTransferEntity entity = new HttpFileTransferEntity(message, delay);
+ final HttpUploadFileTransferEntity entity = new HttpUploadFileTransferEntity(message, delay);
this.addStatusListenerToEntity(entity);
entity.startAttempt();
Account account = message.getConversation().getAccount();
@@ -99,6 +98,7 @@ public class HttpUploadFileTransferService extends AbstractFileTransferService i
&& null != message.getConversation()
&& null != message.getConversation().getAccount()
&& null != message.getFileParams()
+ && message.needsUploading()
&& AccountUtil.isHttpUploadAvailable(message.getConversation().getAccount(), message.getFileParams().getSize());
}
}
diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpUploadSlotRequestReceived.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpUploadSlotRequestReceived.java
index 2687878d..022aedcf 100644
--- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpUploadSlotRequestReceived.java
+++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpUploadSlotRequestReceived.java
@@ -13,9 +13,9 @@ import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket;
*
*/
public class HttpUploadSlotRequestReceived implements OnIqPacketReceived {
- private final HttpFileTransferEntity entity;
+ private final HttpUploadFileTransferEntity entity;
- public HttpUploadSlotRequestReceived(HttpFileTransferEntity entity) {
+ public HttpUploadSlotRequestReceived(HttpUploadFileTransferEntity entity) {
this.entity = entity;
}
diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpUploadedFileEncryptionUiCallback.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpUploadedFileEncryptionUiCallback.java
index f363a675..dc36851f 100644
--- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpUploadedFileEncryptionUiCallback.java
+++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpUploadedFileEncryptionUiCallback.java
@@ -11,9 +11,9 @@ import de.thedevstack.conversationsplus.utils.XmppConnectionServiceAccessor;
*
*/
public class HttpUploadedFileEncryptionUiCallback implements UiCallback<Message> {
- private final HttpFileTransferEntity entity;
+ private final HttpUploadFileTransferEntity entity;
- public HttpUploadedFileEncryptionUiCallback(HttpFileTransferEntity entity) {
+ public HttpUploadedFileEncryptionUiCallback(HttpUploadFileTransferEntity entity) {
this.entity = entity;
}
diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/jingle/JingleFileTransferService.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/jingle/JingleFileTransferService.java
index 5d8ddd4e..ca708fe6 100644
--- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/jingle/JingleFileTransferService.java
+++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/jingle/JingleFileTransferService.java
@@ -47,6 +47,7 @@ public class JingleFileTransferService extends AbstractFileTransferService imple
*/
@Override
public boolean accept(Message message) {
- return message.fixCounterpart(); // No clue why - but this seems to be the check for jingle file transfer
+ return message.needsUploading()
+ && message.fixCounterpart(); // No clue why - but this seems to be the check for jingle file transfer
}
}
diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/ConversationActivity.java b/src/main/java/de/thedevstack/conversationsplus/ui/ConversationActivity.java
index bca0851a..ce1eb439 100644
--- a/src/main/java/de/thedevstack/conversationsplus/ui/ConversationActivity.java
+++ b/src/main/java/de/thedevstack/conversationsplus/ui/ConversationActivity.java
@@ -44,6 +44,7 @@ import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.services.filetransfer.FileTransferManager;
import de.thedevstack.conversationsplus.utils.AccountUtil;
import de.timroes.android.listview.EnhancedListView;
@@ -59,8 +60,6 @@ import de.thedevstack.conversationsplus.entities.Contact;
import de.thedevstack.conversationsplus.entities.Conversation;
import de.thedevstack.conversationsplus.entities.Message;
import de.thedevstack.conversationsplus.entities.Transferable;
-import de.thedevstack.conversationsplus.http.HttpConnectionManager;
-import de.thedevstack.conversationsplus.http.HttpDownloadConnection;
import de.thedevstack.conversationsplus.persistance.FileBackend;
import de.thedevstack.conversationsplus.services.XmppConnectionService;
import de.thedevstack.conversationsplus.services.XmppConnectionService.OnAccountUpdate;
@@ -624,8 +623,12 @@ public class ConversationActivity extends XmppActivity
Toast.makeText(this, R.string.not_connected_try_again, Toast.LENGTH_SHORT).show();
}
} else if (message.treatAsDownloadable() != Message.Decision.NEVER) {
- HttpDownloadConnection downloadConnection = HttpConnectionManager.createNewDownloadConnection(message, true);
- if (null == downloadConnection) {
+ FileTransferManager ftm = FileTransferManager.getInstance();
+ boolean created = false;
+ if (ftm.accept(message)) {
+ created = ftm.transferFile(message);
+ }
+ if (!created) {
Toast.makeText(this, R.string.file_not_on_remote_host, Toast.LENGTH_LONG).show();
}
}
diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/ConversationFragment.java b/src/main/java/de/thedevstack/conversationsplus/ui/ConversationFragment.java
index 157e6dc3..0f32e762 100644
--- a/src/main/java/de/thedevstack/conversationsplus/ui/ConversationFragment.java
+++ b/src/main/java/de/thedevstack/conversationsplus/ui/ConversationFragment.java
@@ -43,8 +43,7 @@ import java.util.Collections;
import java.util.List;
import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
-import de.thedevstack.conversationsplus.http.HttpConnectionManager;
-import de.thedevstack.conversationsplus.http.HttpDownloadConnection;
+import de.thedevstack.conversationsplus.services.filetransfer.FileTransferManager;
import de.thedevstack.conversationsplus.services.filetransfer.http.delete.DeleteRemoteFileService;
import de.thedevstack.conversationsplus.ui.dialogs.SimpleConfirmDialog;
import de.thedevstack.conversationsplus.ui.dialogs.MessageDetailsDialog;
@@ -559,8 +558,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
}
if (m.hasFileOnRemoteHost()
|| GeoHelper.isGeoUri(m.getBody())
- || m.treatAsDownloadable() == Message.Decision.MUST
- || (t != null && t instanceof HttpDownloadConnection)) {
+ || m.treatAsDownloadable() == Message.Decision.MUST) {
copyUrl.setVisible(true);
}
if ((m.getType() == Message.TYPE_TEXT && t == null && m.treatAsDownloadable() != Message.Decision.NEVER)
@@ -687,8 +685,12 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
}
private void downloadFile(Message message) {
- HttpDownloadConnection downloadConnection = HttpConnectionManager.createNewDownloadConnection(message, true);
- if (null == downloadConnection) {
+ FileTransferManager ftm = FileTransferManager.getInstance();
+ boolean created = false;
+ if (ftm.accept(message)) {
+ created = ftm.transferFile(message);
+ }
+ if (!created) {
Toast.makeText(activity, R.string.file_not_on_remote_host, Toast.LENGTH_LONG).show();
}
}
diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/XmppActivity.java b/src/main/java/de/thedevstack/conversationsplus/ui/XmppActivity.java
index 726facac..6785dd31 100644
--- a/src/main/java/de/thedevstack/conversationsplus/ui/XmppActivity.java
+++ b/src/main/java/de/thedevstack/conversationsplus/ui/XmppActivity.java
@@ -17,28 +17,22 @@ import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.IntentSender.SendIntentException;
import android.content.ServiceConnection;
-import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Point;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.nfc.NfcEvent;
-import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.PowerManager;
import android.os.SystemClock;
-import android.preference.PreferenceManager;
import android.text.InputType;
import android.util.DisplayMetrics;
import android.view.Menu;
@@ -62,17 +56,13 @@ import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import net.java.otr4j.session.SessionID;
-import java.io.FileNotFoundException;
-import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
-import java.util.concurrent.RejectedExecutionException;
import de.thedevstack.android.logcat.Logging;
import de.thedevstack.conversationsplus.ConversationsPlusColors;
import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
-import de.thedevstack.conversationsplus.utils.ImageUtil;
import de.thedevstack.conversationsplus.Config;
import de.thedevstack.conversationsplus.R;
import de.thedevstack.conversationsplus.crypto.axolotl.XmppAxolotlSession;
@@ -104,7 +94,6 @@ public abstract class XmppActivity extends Activity {
public boolean xmppConnectionServiceBound = false;
protected boolean registeredListeners = false;
- private DisplayMetrics metrics;
protected int mTheme;
private long mLastUiRefresh = 0;
@@ -346,7 +335,6 @@ public abstract class XmppActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- metrics = getResources().getDisplayMetrics();
ExceptionHelper.init(getApplicationContext());
this.mTheme = findTheme();
setTheme(this.mTheme);
@@ -1062,112 +1050,4 @@ public abstract class XmppActivity extends Activity {
}
}
}
-
- class BitmapWorkerTask extends AsyncTask<Message, Void, Bitmap> {
- private final WeakReference<ImageView> imageViewReference;
- private final boolean setSize;
- private Message message = null;
-
- public BitmapWorkerTask(ImageView imageView, boolean setSize) {
- imageViewReference = new WeakReference<>(imageView);
- this.setSize = setSize;
- }
-
- @Override
- protected Bitmap doInBackground(Message... params) {
- message = params[0];
- try {
- return ImageUtil.getThumbnail(message, (int) (metrics.density * 288), false);
- } catch (FileNotFoundException e) {
- return null;
- }
- }
-
- @Override
- protected void onPostExecute(Bitmap bitmap) {
- if (bitmap != null) {
- final ImageView imageView = imageViewReference.get();
- if (imageView != null) {
- imageView.setImageBitmap(bitmap);
- imageView.setBackgroundColor(0x00000000);
- if (setSize) {
- imageView.setLayoutParams(new LinearLayout.LayoutParams(
- bitmap.getWidth(), bitmap.getHeight()));
- }
- }
- }
- }
- }
-
- public void loadBitmap(Message message, ImageView imageView, boolean setSize) {
- Bitmap bm;
- try {
- bm = ImageUtil.getThumbnail(message,(int) (metrics.density * 288), true);
- } catch (FileNotFoundException e) {
- bm = null;
- }
-
- if (bm != null) {
- imageView.setImageBitmap(bm);
- imageView.setBackgroundColor(0x00000000);
- if (setSize) {
- imageView.setLayoutParams(new LinearLayout.LayoutParams(
- bm.getWidth(), bm.getHeight()));
- }
- } else {
- if (cancelPotentialWork(message, imageView)) {
- imageView.setBackgroundColor(0xff333333);
- imageView.setImageDrawable(null);
- final BitmapWorkerTask task = new BitmapWorkerTask(imageView, setSize);
- final AsyncDrawable asyncDrawable = new AsyncDrawable(
- getResources(), null, task);
- imageView.setImageDrawable(asyncDrawable);
- try {
- task.execute(message);
- } catch (final RejectedExecutionException ignored) {
- }
- }
- }
- }
-
- public static boolean cancelPotentialWork(Message message,
- ImageView imageView) {
- final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
-
- if (bitmapWorkerTask != null) {
- final Message oldMessage = bitmapWorkerTask.message;
- if (oldMessage == null || message != oldMessage) {
- bitmapWorkerTask.cancel(true);
- } else {
- return false;
- }
- }
- return true;
- }
-
- private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
- if (imageView != null) {
- final Drawable drawable = imageView.getDrawable();
- if (drawable instanceof AsyncDrawable) {
- final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
- return asyncDrawable.getBitmapWorkerTask();
- }
- }
- return null;
- }
-
- static class AsyncDrawable extends BitmapDrawable {
- private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
-
- public AsyncDrawable(Resources res, Bitmap bitmap,
- BitmapWorkerTask bitmapWorkerTask) {
- super(res, bitmap);
- bitmapWorkerTaskReference = new WeakReference<>(
- bitmapWorkerTask);
- }
-
- public BitmapWorkerTask getBitmapWorkerTask() {
- return bitmapWorkerTaskReference.get();
- }
- }
}
diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/adapter/ConversationAdapter.java b/src/main/java/de/thedevstack/conversationsplus/ui/adapter/ConversationAdapter.java
index e0e672e3..df7fc922 100644
--- a/src/main/java/de/thedevstack/conversationsplus/ui/adapter/ConversationAdapter.java
+++ b/src/main/java/de/thedevstack/conversationsplus/ui/adapter/ConversationAdapter.java
@@ -23,6 +23,7 @@ import java.util.concurrent.RejectedExecutionException;
import de.thedevstack.conversationsplus.ConversationsPlusColors;
import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
import de.thedevstack.conversationsplus.ui.listeners.ShowResourcesListDialogListener;
+import de.thedevstack.conversationsplus.utils.ImageUtil;
import de.tzur.conversations.Settings;
import de.thedevstack.conversationsplus.R;
import de.thedevstack.conversationsplus.entities.Account;
@@ -100,7 +101,7 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> {
|| message.getTransferable().getStatus() != Transferable.STATUS_DELETED)) {
mLastMessage.setVisibility(View.GONE);
imagePreview.setVisibility(View.VISIBLE);
- activity.loadBitmap(message, imagePreview, false);
+ ImageUtil.loadBitmap(message, imagePreview, mLastMessage, false);
} else {
Pair<String,Boolean> preview = UIHelper.getMessagePreview(activity,message);
mLastMessage.setVisibility(View.VISIBLE);
diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/adapter/MessageAdapter.java b/src/main/java/de/thedevstack/conversationsplus/ui/adapter/MessageAdapter.java
index 79ab3bc3..e49575f6 100644
--- a/src/main/java/de/thedevstack/conversationsplus/ui/adapter/MessageAdapter.java
+++ b/src/main/java/de/thedevstack/conversationsplus/ui/adapter/MessageAdapter.java
@@ -13,9 +13,7 @@ import android.net.Uri;
import android.os.AsyncTask;
import android.text.Spannable;
import android.text.SpannableString;
-import android.text.Spanned;
import android.text.style.ForegroundColorSpan;
-import android.text.style.RelativeSizeSpan;
import android.text.style.StyleSpan;
import android.text.util.Linkify;
import android.util.Patterns;
@@ -48,13 +46,14 @@ import de.thedevstack.conversationsplus.entities.FileParams;
import de.thedevstack.conversationsplus.entities.Message;
import de.thedevstack.conversationsplus.entities.Transferable;
import de.thedevstack.conversationsplus.enums.FileStatus;
-import de.thedevstack.conversationsplus.http.HttpConnectionManager;
import de.thedevstack.conversationsplus.persistance.FileBackend;
import de.thedevstack.conversationsplus.providers.ConversationsPlusFileProvider;
import de.thedevstack.conversationsplus.services.AvatarService;
+import de.thedevstack.conversationsplus.services.filetransfer.http.download.AutomaticFileDownload;
import de.thedevstack.conversationsplus.ui.ConversationActivity;
import de.thedevstack.conversationsplus.utils.CryptoHelper;
import de.thedevstack.conversationsplus.utils.GeoHelper;
+import de.thedevstack.conversationsplus.utils.ImageUtil;
import de.thedevstack.conversationsplus.utils.MessageUtil;
import de.thedevstack.conversationsplus.utils.UIHelper;
import de.thedevstack.conversationsplus.utils.ui.TextViewUtil;
@@ -119,7 +118,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
}
}
- private void displayStatus(ViewHolder viewHolder, Message message, int type, boolean darkBackground) {
+ private void displayStatus(ViewHolder viewHolder, Message message, int type) {
String filesize = null;
String info = null;
boolean error = false;
@@ -177,12 +176,12 @@ public class MessageAdapter extends ArrayAdapter<Message> {
if (error && type == SENT) {
viewHolder.time.setTextColor(ConversationsPlusColors.warning());
} else {
- viewHolder.time.setTextColor(this.getMessageTextColor(darkBackground,false));
+ viewHolder.time.setTextColor(this.getMessageTextColor(viewHolder.darkBackground,false));
}
if (message.getEncryption() == Message.ENCRYPTION_NONE) {
viewHolder.indicator.setVisibility(View.GONE);
} else {
- viewHolder.indicator.setImageResource(darkBackground ? R.drawable.ic_lock_white_18dp : R.drawable.ic_lock_black_18dp);
+ viewHolder.indicator.setImageResource(viewHolder.darkBackground ? R.drawable.ic_lock_white_18dp : R.drawable.ic_lock_black_18dp);
viewHolder.indicator.setVisibility(View.VISIBLE);
if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) {
XmppAxolotlSession.Trust trust = message.getConversation()
@@ -194,7 +193,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
viewHolder.indicator.setAlpha(1.0f);
} else {
viewHolder.indicator.clearColorFilter();
- if (darkBackground) {
+ if (viewHolder.darkBackground) {
viewHolder.indicator.setAlpha(0.7f);
} else {
viewHolder.indicator.setAlpha(0.57f);
@@ -202,7 +201,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
}
} else {
viewHolder.indicator.clearColorFilter();
- if (darkBackground) {
+ if (viewHolder.darkBackground) {
viewHolder.indicator.setAlpha(0.7f);
} else {
viewHolder.indicator.setAlpha(0.57f);
@@ -236,7 +235,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
if (message.hasFileAttached() && null != message.getFileParams() && null != viewHolder.remoteFileStatus) {
FileStatus fileStatus = message.getFileParams().getFileStatus();
- if (fileStatus == FileStatus.DELETE_FAILED || fileStatus == FileStatus.DELETED || fileStatus == FileStatus.DELETING) {
+ if (fileStatus == FileStatus.DELETE_FAILED || fileStatus == FileStatus.DELETED || fileStatus == FileStatus.DELETING || fileStatus == FileStatus.NOT_FOUND) {
viewHolder.remoteFileStatus.setVisibility(View.VISIBLE);
switch (fileStatus) {
case DELETE_FAILED:
@@ -249,24 +248,28 @@ public class MessageAdapter extends ArrayAdapter<Message> {
case DELETING:
viewHolder.remoteFileStatus.setText(R.string.remote_filestatus_delete_inprogress);
break;
+ case NOT_FOUND:
+ TextViewUtil.setColor(viewHolder.remoteFileStatus, R.color.error);
+ viewHolder.remoteFileStatus.setText(R.string.remote_filestatus_not_found);
+ break;
}
}
}
}
- private void displayInfoMessage(ViewHolder viewHolder, String text, boolean darkBackground) {
+ private void displayInfoMessage(ViewHolder viewHolder, String text) {
if (viewHolder.download_button != null) {
viewHolder.download_button.setVisibility(View.GONE);
}
viewHolder.image.setVisibility(View.GONE);
viewHolder.messageBody.setVisibility(View.VISIBLE);
viewHolder.messageBody.setText(text);
- viewHolder.messageBody.setTextColor(getMessageTextColor(darkBackground, false));
+ viewHolder.messageBody.setTextColor(getMessageTextColor(viewHolder.darkBackground, false));
viewHolder.messageBody.setTypeface(null, Typeface.ITALIC);
viewHolder.messageBody.setTextIsSelectable(false);
}
- private void displayDecryptionFailed(ViewHolder viewHolder, boolean darkBackground) {
+ private void displayDecryptionFailed(ViewHolder viewHolder) {
if (viewHolder.download_button != null) {
viewHolder.download_button.setVisibility(View.GONE);
}
@@ -274,12 +277,12 @@ public class MessageAdapter extends ArrayAdapter<Message> {
viewHolder.messageBody.setVisibility(View.VISIBLE);
viewHolder.messageBody.setText(getContext().getString(
R.string.decryption_failed));
- viewHolder.messageBody.setTextColor(getMessageTextColor(darkBackground, false));
+ viewHolder.messageBody.setTextColor(getMessageTextColor(viewHolder.darkBackground, false));
viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
viewHolder.messageBody.setTextIsSelectable(false);
}
- private void displayTextMessage(final ViewHolder viewHolder, final Message message, boolean darkBackground) {
+ private void displayTextMessage(final ViewHolder viewHolder, final Message message) {
if (viewHolder.download_button != null) {
viewHolder.download_button.setVisibility(View.GONE);
}
@@ -295,12 +298,6 @@ public class MessageAdapter extends ArrayAdapter<Message> {
body = message.getBody();
}
final SpannableString formattedBody = new SpannableString(body);
- int i = body.indexOf(Message.MERGE_SEPARATOR);
- while(i >= 0) {
- final int end = i + Message.MERGE_SEPARATOR.length();
- formattedBody.setSpan(new RelativeSizeSpan(0.3f),i,end,Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- i = body.indexOf(Message.MERGE_SEPARATOR,end);
- }
if (message.getType() != Message.TYPE_PRIVATE) {
if (message.hasMeCommand()) {
final Spannable span = new SpannableString(formattedBody);
@@ -326,7 +323,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
}
final Spannable span = new SpannableString(privateMarker + " "
+ formattedBody);
- span.setSpan(new ForegroundColorSpan(getMessageTextColor(darkBackground,false)), 0, privateMarker
+ span.setSpan(new ForegroundColorSpan(getMessageTextColor(viewHolder.darkBackground,false)), 0, privateMarker
.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
span.setSpan(new StyleSpan(Typeface.BOLD), 0,
privateMarker.length(),
@@ -366,7 +363,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
viewHolder.messageBody.setText("");
viewHolder.messageBody.setTextIsSelectable(false);
}
- viewHolder.messageBody.setTextColor(this.getMessageTextColor(darkBackground, true));
+ viewHolder.messageBody.setTextColor(this.getMessageTextColor(viewHolder.darkBackground, true));
viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
}
@@ -403,10 +400,15 @@ public class MessageAdapter extends ArrayAdapter<Message> {
viewHolder.messageBody.setAutoLinkMask(oldAutoLinkMask);
}
- private void displayDownloadableMessage(ViewHolder viewHolder, final Message message, int resId) {
+ private void displayDownloadableMessage(ViewHolder viewHolder, final Message message) {
viewHolder.image.setVisibility(View.GONE);
FileParams fileParams = message.getFileParams();
String btnText;
+ int resId = R.string.download_x_file;
+ if ((message.getTransferable() != null && message.getTransferable().getStatus() == Transferable.STATUS_OFFER_CHECK_FILESIZE)
+ || (null != fileParams && -1 == fileParams.getSize())) {
+ resId = R.string.check_x_filesize;
+ }
if (null != fileParams) {
this.displayFileInfoForFileMessage(message, viewHolder);
btnText = activity.getString(resId, "");
@@ -465,26 +467,8 @@ public class MessageAdapter extends ArrayAdapter<Message> {
if (viewHolder.download_button != null) {
viewHolder.download_button.setVisibility(View.GONE);
}
- viewHolder.messageBody.setVisibility(View.GONE);
- viewHolder.image.setVisibility(View.VISIBLE);
- //TODO: Check what value add the following lines have (compared with setting height/width in XmppActivity.loadBitmap from thumbnail after thumbnail is created)
- /*
- FileParams params = message.getFileParams();
- double target = metrics.density * 288;
- int scalledW;
- int scalledH;
- if (params.width <= params.height) {
- scalledW = (int) (params.width / ((double) params.height / target));
- scalledH = (int) target;
- } else {
- scalledW = (int) target;
- scalledH = (int) (params.height / ((double) params.width / target));
- }
- LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(scalledW, scalledH);
- layoutParams.setMargins(0, (int) (metrics.density * 4), 0, (int) (metrics.density * 4));
- viewHolder.image.setLayoutParams(layoutParams);*/
- //TODO Why should this be calculated by hand???
- activity.loadBitmap(message, viewHolder.image, true);
+
+ ImageUtil.loadBitmap(message, viewHolder.image, viewHolder.messageBody, true);
viewHolder.image.setOnClickListener(new OnClickListener() {
@Override
@@ -511,19 +495,35 @@ public class MessageAdapter extends ArrayAdapter<Message> {
}
private void displayFileMessage(final Message message, ViewHolder viewHolder) {
- final FileParams fileParams = message.getFileParams();
- String mimeType = (null != fileParams) ? fileParams.getMimeType() : null;
- if ((message.getType() == Message.TYPE_IMAGE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED)
- || (fileParams != null && fileParams.getWidth() > 0)
- || (null != mimeType && mimeType.startsWith("image/"))) {
+ Transferable transferable = message.getTransferable();
+ if (null != transferable) {
+ switch (transferable.getStatus()) {
+ case Transferable.STATUS_OFFER:
+ case Transferable.STATUS_OFFER_CHECK_FILESIZE:
+ displayDownloadableMessage(viewHolder, message);
+ break;
+ case Transferable.STATUS_UPLOADING:
+ displayFileMessage(message, viewHolder);
+ break;
+ case Transferable.STATUS_DELETED:
+ case Transferable.STATUS_CHECKING:
+ case Transferable.STATUS_FAILED:
+ case Transferable.STATUS_DOWNLOADING:
+ case Transferable.STATUS_UNKNOWN:
+ displayInfoMessage(viewHolder, UIHelper.getMessagePreview(activity, message).first);
+ break;
+ }
+ } else if (FileStatus.CHECKING_FILE_SIZE == message.getFileParams().getFileStatus()) {
+ displayInfoMessage(viewHolder, activity.getString(R.string.checking_remote_filesize));
+ } else if (MessageUtil.isAttachedFileAnImage(message) && FileStatus.DOWNLOADED == message.getFileParams().getFileStatus()) {
displayImageMessage(viewHolder, message);
- } else if ((message.getType() == Message.TYPE_FILE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED)
- && null != fileParams && (FileStatus.NEEDS_DOWNLOAD != fileParams.getFileStatus() || 0 == fileParams.getWidth())) {
+ } else if ((MessageUtil.isTypeFileAndDecrypted(message) || FileStatus.DOWNLOADED == message.getFileParams().getFileStatus())
+ && !MessageUtil.needsDownload(message)) {
displayOpenableMessage(viewHolder, message);
- } else if (Message.Decision.NEVER == message.treatAsDownloadable()) {
- displayTextMessage(viewHolder, message, getItemViewType(message) == RECEIVED && !message.isValidInSession());
+ } else if (Message.Decision.NEVER == message.treatAsDownloadable() || !MessageUtil.mayFileRemoteAvailable(message)) {
+ displayTextMessage(viewHolder, message);
} else {
- displayDownloadableMessage(viewHolder, message, R.string.download_x_file);
+ displayDownloadableMessage(viewHolder, message);
}
}
@@ -660,48 +660,28 @@ public class MessageAdapter extends ArrayAdapter<Message> {
this.displayAvatar(message, type, viewHolder);
- boolean darkBackground = (type == RECEIVED && !isInValidSession);
- this.displayStatus(viewHolder, message, type, darkBackground);
+ viewHolder.darkBackground = (type == RECEIVED && !isInValidSession);
+ this.displayStatus(viewHolder, message, type);
- final Transferable transferable = message.getTransferable();
- if (null != transferable) {
- switch (transferable.getStatus()) {
- case Transferable.STATUS_OFFER:
- displayDownloadableMessage(viewHolder, message, R.string.download_x_file);
- break;
- case Transferable.STATUS_OFFER_CHECK_FILESIZE:
- displayDownloadableMessage(viewHolder, message, R.string.check_x_filesize);
- break;
- case Transferable.STATUS_UPLOADING:
- displayFileMessage(message, viewHolder);
- break;
- case Transferable.STATUS_DELETED:
- case Transferable.STATUS_CHECKING:
- case Transferable.STATUS_FAILED:
- case Transferable.STATUS_UNKNOWN:
- displayInfoMessage(viewHolder, UIHelper.getMessagePreview(activity, message).first,darkBackground);
- break;
- }
- } else if (message.hasFileAttached()) {
- if (message.trusted()
+ if (null != message.getTransferable() || message.hasFileAttached()) {
+ if (!(message.trusted()
&& MessageUtil.needsDownload(message)
&& ConversationsPlusPreferences.autoAcceptFileSize() > 0
- && (message.isHttpUploaded() || ConversationsPlusPreferences.autoDownloadFileLink())) {
- HttpConnectionManager.createNewDownloadConnection(message);
- } else {
- displayFileMessage(message, viewHolder);
+ && message.isHttpUploaded() || ConversationsPlusPreferences.autoDownloadFileLink())) {
+ new AutomaticFileDownload(false).transferFile(message);
}
+ displayFileMessage(message, viewHolder);
} else if (GeoHelper.isGeoUri(message.getBody())) {
displayLocationMessage(viewHolder, message);
} else if (message.getEncryption() == Message.ENCRYPTION_PGP) {
if (activity.hasPgp()) {
if (account.getPgpDecryptionService().isRunning()) {
- displayInfoMessage(viewHolder, activity.getString(R.string.message_decrypting), darkBackground);
+ displayInfoMessage(viewHolder, activity.getString(R.string.message_decrypting));
} else {
- displayInfoMessage(viewHolder, activity.getString(R.string.pgp_message), darkBackground);
+ displayInfoMessage(viewHolder, activity.getString(R.string.pgp_message));
}
} else {
- displayInfoMessage(viewHolder,activity.getString(R.string.install_openkeychain),darkBackground);
+ displayInfoMessage(viewHolder, activity.getString(R.string.install_openkeychain));
if (viewHolder != null) {
viewHolder.message_box
.setOnClickListener(new OnClickListener() {
@@ -714,9 +694,9 @@ public class MessageAdapter extends ArrayAdapter<Message> {
}
}
} else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
- displayDecryptionFailed(viewHolder,darkBackground);
+ displayDecryptionFailed(viewHolder);
} else {
- displayTextMessage(viewHolder, message, darkBackground);
+ displayTextMessage(viewHolder, message);
}
if (type == RECEIVED) {
@@ -812,6 +792,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
protected TextView status_message;
protected TextView encryption;
public TextView remoteFileStatus;
+ protected boolean darkBackground;
}
class BitmapWorkerTask extends AsyncTask<Message, Void, Bitmap> {
diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/FileUtils.java b/src/main/java/de/thedevstack/conversationsplus/utils/FileUtils.java
index 77c313f7..95bd60dc 100644
--- a/src/main/java/de/thedevstack/conversationsplus/utils/FileUtils.java
+++ b/src/main/java/de/thedevstack/conversationsplus/utils/FileUtils.java
@@ -175,7 +175,7 @@ public final class FileUtils {
return null;
}
- String filename = path.substring(path.lastIndexOf('/') + 1).toLowerCase();
+ String filename = FileUtils.getFilenameFromPath(path).toLowerCase();
final String lastPart = FileUtils.getLastExtension(filename);
@@ -254,6 +254,19 @@ public final class FileUtils {
return true;
}
+ public static boolean isImage(String mimeType) {
+ return null != mimeType && mimeType.startsWith("image/");
+ }
+
+ public static String getFilenameFromPath(String path) {
+ String filename = null;
+ if (null != path && !path.isEmpty()) {
+ filename = path.substring(path.lastIndexOf('/') + 1);
+ }
+
+ return filename;
+ }
+
private FileUtils() {
// Utility class - do not instantiate
}
diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/ImageUtil.java b/src/main/java/de/thedevstack/conversationsplus/utils/ImageUtil.java
index 0b8ace95..eee18cc9 100644
--- a/src/main/java/de/thedevstack/conversationsplus/utils/ImageUtil.java
+++ b/src/main/java/de/thedevstack/conversationsplus/utils/ImageUtil.java
@@ -1,25 +1,38 @@
package de.thedevstack.conversationsplus.utils;
+import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.RectF;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
import android.media.ExifInterface;
import android.net.Uri;
+import android.os.AsyncTask;
+import android.util.DisplayMetrics;
import android.util.LruCache;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
+import java.lang.ref.WeakReference;
+import java.util.concurrent.RejectedExecutionException;
import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.ConversationsPlusApplication;
import de.thedevstack.conversationsplus.exceptions.ImageResizeException;
import de.thedevstack.conversationsplus.Config;
import de.thedevstack.conversationsplus.R;
import de.thedevstack.conversationsplus.entities.Message;
import de.thedevstack.conversationsplus.persistance.FileBackend;
+import de.thedevstack.conversationsplus.utils.ui.ViewUtil;
/**
* This util provides
@@ -373,6 +386,129 @@ public final class ImageUtil {
return inSampleSize;
}
+ static class BitmapWorkerTask extends AsyncTask<Message, Void, Bitmap> {
+ private final WeakReference<ImageView> imageViewReference;
+ private final WeakReference<TextView> alternativeViewReference;
+ private final boolean setSize;
+ private Message message = null;
+
+ public BitmapWorkerTask(ImageView imageView, TextView alternativeView, boolean setSize) {
+ imageViewReference = new WeakReference<>(imageView);
+ alternativeViewReference = new WeakReference<>(alternativeView);
+ this.setSize = setSize;
+ }
+
+ @Override
+ protected Bitmap doInBackground(Message... params) {
+ message = params[0];
+ try {
+ DisplayMetrics metrics = ConversationsPlusApplication.getAppContext().getResources().getDisplayMetrics();
+ return ImageUtil.getThumbnail(message, (int) (metrics.density * 288), false);
+ } catch (FileNotFoundException e) {
+ return null;
+ }
+ }
+
+ @Override
+ protected void onPostExecute(Bitmap bitmap) {
+ final ImageView imageView = imageViewReference.get();
+ final TextView alternativeView = alternativeViewReference.get();
+ if (bitmap != null) {
+ if (imageView != null) {
+ imageView.setImageBitmap(bitmap);
+ imageView.setBackgroundColor(0x00000000);
+ if (setSize) {
+ imageView.setLayoutParams(new LinearLayout.LayoutParams(
+ bitmap.getWidth(), bitmap.getHeight()));
+ }
+ ViewUtil.gone(alternativeView);
+ ViewUtil.visible(imageView);
+ }
+ } else {
+ ViewUtil.visible(alternativeView);
+ alternativeView.setText(message.getBody()); // TODO Should be the same as MessageAdapter.displayText... or display preview of ConversationAdapter
+ ViewUtil.gone(imageView);
+ }
+ }
+ }
+
+ public static void loadBitmap(Message message, ImageView imageView, TextView alternativeView, boolean setSize) {
+ Bitmap bm;
+ try {
+ DisplayMetrics metrics = ConversationsPlusApplication.getAppContext().getResources().getDisplayMetrics();
+ bm = ImageUtil.getThumbnail(message,(int) (metrics.density * 288), true);
+ } catch (FileNotFoundException e) {
+ bm = null;
+ }
+
+ if (bm != null) {
+ imageView.setImageBitmap(bm);
+ imageView.setBackgroundColor(0x00000000);
+ if (setSize) {
+ imageView.setLayoutParams(new LinearLayout.LayoutParams(
+ bm.getWidth(), bm.getHeight()));
+ }
+
+ ViewUtil.gone(alternativeView);
+ ViewUtil.visible(imageView);
+ } else {
+ if (cancelPotentialWork(message, imageView)) {
+ imageView.setBackgroundColor(0xff333333);
+ imageView.setImageDrawable(null);
+ final BitmapWorkerTask task = new BitmapWorkerTask(imageView, alternativeView, setSize);
+ final AsyncDrawable asyncDrawable = new AsyncDrawable(ConversationsPlusApplication.getAppContext().getResources(), null, task);
+ imageView.setImageDrawable(asyncDrawable);
+ ViewUtil.gone(alternativeView);
+ ViewUtil.visible(imageView);
+ try {
+ task.execute(message);
+ } catch (final RejectedExecutionException ignored) {
+ }
+ }
+ }
+ }
+
+ public static boolean cancelPotentialWork(Message message,
+ ImageView imageView) {
+ final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
+
+ if (bitmapWorkerTask != null) {
+ final Message oldMessage = bitmapWorkerTask.message;
+ if (oldMessage == null || message != oldMessage) {
+ bitmapWorkerTask.cancel(true);
+ } else {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
+ if (imageView != null) {
+ final Drawable drawable = imageView.getDrawable();
+ if (drawable instanceof AsyncDrawable) {
+ final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
+ return asyncDrawable.getBitmapWorkerTask();
+ }
+ }
+ return null;
+ }
+
+ static class AsyncDrawable extends BitmapDrawable {
+ private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
+
+ public AsyncDrawable(Resources res, Bitmap bitmap,
+ BitmapWorkerTask bitmapWorkerTask) {
+ super(res, bitmap);
+ bitmapWorkerTaskReference = new WeakReference<>(
+ bitmapWorkerTask);
+ }
+
+ public BitmapWorkerTask getBitmapWorkerTask() {
+ return bitmapWorkerTaskReference.get();
+ }
+ }
+
private ImageUtil() {
// Static helper class
}
diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/MessageUtil.java b/src/main/java/de/thedevstack/conversationsplus/utils/MessageUtil.java
index acd73575..e0d627b9 100644
--- a/src/main/java/de/thedevstack/conversationsplus/utils/MessageUtil.java
+++ b/src/main/java/de/thedevstack/conversationsplus/utils/MessageUtil.java
@@ -27,6 +27,14 @@ public final class MessageUtil {
if (null == message) {
return;
}
+
+ // Ensure that for every message the fileParams are set after calling this method
+ FileParams fileParams = message.getFileParams();
+ if (null == fileParams) {
+ fileParams = new FileParams();
+ message.setFileParams(fileParams);
+ }
+
String body = message.getBody();
/**
* there are a few cases where spaces result in an unwanted behavior, e.g.
@@ -37,24 +45,27 @@ public final class MessageUtil {
return;
}
- FileParams fileParams = message.getFileParams();
- if (null == fileParams) {
- fileParams = new FileParams();
- message.setFileParams(fileParams);
- }
-
try {
URL url = new URL(body);
if (!url.getProtocol().equalsIgnoreCase("http") && !url.getProtocol().equalsIgnoreCase("https")) {
message.setTreatAsDownloadable(Message.Decision.NEVER);
return;
}
+ String extension = FileUtils.getRelevantExtension(url);
if (message.isHttpUploaded()) {
fileParams.setUrl(url.toString());
- message.setTreatAsDownloadable(Message.Decision.MUST);
+ if (null != extension
+ && (Transferable.WELL_KNOWN_EXTENSIONS.contains(extension.toLowerCase()) || Transferable.VALID_IMAGE_EXTENSIONS.contains(extension.toLowerCase()))) {
+ message.setTreatAsDownloadable(Message.Decision.MUST);
+ } else {
+ message.setTreatAsDownloadable(Message.Decision.NEVER);
+ fileParams.setFileStatus(FileStatus.UNDEFINED);
+ }
+
+ extractFilename(message, url.toString());
return;
}
- String extension = FileUtils.getRelevantExtension(url);
+
if (extension == null) {
message.setTreatAsDownloadable(Message.Decision.NEVER);
return;
@@ -65,23 +76,100 @@ public final class MessageUtil {
if (MimeUtils.guessMimeTypeFromExtension(extension) != null) {
message.setTreatAsDownloadable(Message.Decision.MUST);
fileParams.setKeyAndIv(ivAndKey);
- fileParams.setUrl(url.toString());
} else {
message.setTreatAsDownloadable(Message.Decision.NEVER);
}
} else if (Transferable.VALID_IMAGE_EXTENSIONS.contains(extension)
|| Transferable.WELL_KNOWN_EXTENSIONS.contains(extension)) {
message.setTreatAsDownloadable(Message.Decision.SHOULD);
- fileParams.setUrl(url.toString());
} else {
message.setTreatAsDownloadable(Message.Decision.NEVER);
}
+ if (message.treatAsDownloadable() == Message.Decision.MUST
+ || message.treatAsDownloadable() == Message.Decision.SHOULD) {
+ fileParams.setUrl(url.toString());
+ extractFilename(message, url.toString());
+ }
} catch (MalformedURLException e) {
message.setTreatAsDownloadable(Message.Decision.NEVER);
}
}
+ private static void extractFilename(Message message, String url) {
+ String originalFilename = FileUtils.getFilenameFromPath(url);
+ final String lowerCaseFilename = originalFilename.toLowerCase();
+ final String lastPart = FileUtils.getLastExtension(lowerCaseFilename);
+
+ detectAndSetEncryption(lastPart, message);
+
+ String filenameExtension;
+ if (!lastPart.isEmpty() && Transferable.VALID_CRYPTO_EXTENSIONS.contains(lastPart)) {
+ filenameExtension = FileUtils.getSecondToLastExtension(lowerCaseFilename);
+ originalFilename = originalFilename.replace("." + lastPart, "");
+ } else {
+ filenameExtension = lastPart;
+ }
+ message.setRelativeFilePath(message.getUuid() + "." + filenameExtension);
+
+ message.getFileParams().setOriginalFilename(originalFilename);
+ }
+
+ private static void detectAndSetEncryption(String lastPart, Message message) {
+ if (!lastPart.isEmpty() && ("pgp".equals(lastPart) || "gpg".equals(lastPart))) {
+ message.setEncryption(Message.ENCRYPTION_PGP);
+ } else if (message.getEncryption() != Message.ENCRYPTION_OTR
+ && message.getEncryption() != Message.ENCRYPTION_AXOLOTL) {
+ message.setEncryption(Message.ENCRYPTION_NONE);
+ } else if ((message.getEncryption() == Message.ENCRYPTION_OTR
+ || message.getEncryption() == Message.ENCRYPTION_AXOLOTL)
+ && message.getFileParams() != null && message.getFileParams().getKey() == null) {
+ // If an encryption is set for the message, but no key given -> decryption not possible
+ message.setEncryption(Message.ENCRYPTION_NONE);
+ }
+ }
+
+ /**
+ * Checks if an attached file is an image or not.
+ * Prerequisite for calling this method: The check if a file is attached is done before.
+ * @param message
+ * @return
+ */
+ public static boolean isAttachedFileAnImage(Message message) {
+ final FileParams fileParams = message.getFileParams();
+ String mimeType = (null != fileParams) ? fileParams.getMimeType() : null;
+ return message.getType() == Message.TYPE_IMAGE
+ || (fileParams != null && fileParams.getWidth() > 0)
+ || (null != mimeType && mimeType.startsWith("image/"));
+ }
+
+ public static boolean isTypeFileAndDecrypted(Message message) {
+ return message.getType() == Message.TYPE_FILE
+ && message.getEncryption() != Message.ENCRYPTION_PGP
+ && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED;
+ }
+
+ public static boolean isDecrypted(Message message) {
+ return message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED;
+ }
+
+ /**
+ * Checks if the file status of an message is <b>NOT</b> one of:
+ * <ul>
+ * <li>FileStatus#NOT_FOUND</li>
+ * <li>FileStatus#DELETED</li>
+ * </ul>
+ * The file status does not guarantee that a file is really available or not.
+ * @param message
+ * @return
+ */
+ public static boolean mayFileRemoteAvailable(Message message) {
+ FileStatus fileStatus = (null != message.getFileParams()) ? message.getFileParams().getFileStatus() : null;
+ return null != fileStatus
+ && FileStatus.NOT_FOUND != fileStatus
+ && FileStatus.DELETED != fileStatus;
+ }
+
public static boolean needsDownload(Message message) {
FileStatus fileStatus = (null != message.getFileParams()) ? message.getFileParams().getFileStatus() : null;
return (null == fileStatus
diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/UIHelper.java b/src/main/java/de/thedevstack/conversationsplus/utils/UIHelper.java
index a2ed6d81..e3680b81 100644
--- a/src/main/java/de/thedevstack/conversationsplus/utils/UIHelper.java
+++ b/src/main/java/de/thedevstack/conversationsplus/utils/UIHelper.java
@@ -291,31 +291,28 @@ public class UIHelper {
}
public static String getHumanReadableFileSize(long size) {
- String filesize;
- if (size > (1.5 * 1024 * 1024)) {
- filesize = size / (1024 * 1024)+ " MiB";
- } else if (size > 0) {
- filesize = size / 1024 + " KiB";
- } else {
- filesize = null;
- }
+ return getHumanReadableDetailedFileSize(size, 1);
+ }
- return filesize;
+ public static String getHumanReadableDetailedFileSize(long filesize) {
+ String size = getHumanReadableDetailedFileSize(filesize, 2);
+ return null == size ? "?" : size;
}
- public static String getHumanReadableDetailedFileSize(long filesize) {
+ public static String getHumanReadableDetailedFileSize(long filesize, int precision) {
if (0 > filesize) {
- return "?";
+ return null;
}
double size = Double.valueOf(filesize);
String[] sizes = {" bytes", " Kb", " Mb", " Gb", " Tb"};
int i = 0;
- while (1023 < size) {
+ while (1023 < size && i < sizes.length - 1) {
size /= 1024d;
++i;
}
+
BigDecimal readableSize = new BigDecimal(size);
- readableSize = readableSize.setScale(2, BigDecimal.ROUND_HALF_UP);
- return readableSize.doubleValue() + sizes[i];
+ readableSize = readableSize.setScale(precision, BigDecimal.ROUND_HALF_UP);
+ return ((0 == i) ? String.valueOf(readableSize.intValue()) : String.valueOf(readableSize.doubleValue())) + sizes[i];
}
}
diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/ui/TextViewUtil.java b/src/main/java/de/thedevstack/conversationsplus/utils/ui/TextViewUtil.java
index 27a269f2..faa9a5ed 100644
--- a/src/main/java/de/thedevstack/conversationsplus/utils/ui/TextViewUtil.java
+++ b/src/main/java/de/thedevstack/conversationsplus/utils/ui/TextViewUtil.java
@@ -4,6 +4,8 @@ import android.support.annotation.StringRes;
import android.view.View;
import android.widget.TextView;
+import de.thedevstack.conversationsplus.ConversationsPlusColors;
+
/**
*
*/
@@ -61,7 +63,7 @@ public final class TextViewUtil extends ViewUtil {
public static void setColorEnabledAndTextResId(TextView tv, Integer color, Boolean enabled, @StringRes Integer resid) {
if (null != color) {
- tv.setTextColor(color);
+ tv.setTextColor(ConversationsPlusColors.byId(color));
}
if (enabled != null) {
diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/ui/ViewUtil.java b/src/main/java/de/thedevstack/conversationsplus/utils/ui/ViewUtil.java
index 77422587..170a6401 100644
--- a/src/main/java/de/thedevstack/conversationsplus/utils/ui/ViewUtil.java
+++ b/src/main/java/de/thedevstack/conversationsplus/utils/ui/ViewUtil.java
@@ -10,30 +10,35 @@ import android.view.View;
public class ViewUtil {
public static <T extends View> T visible(View parentView, @IdRes int textViewId) {
- T tv = (T) parentView.findViewById(textViewId);
- if (null != tv) {
- tv.setVisibility(View.VISIBLE);
- }
-
- return tv;
+ return ViewUtil.visible((T) parentView.findViewById(textViewId));
}
public static <T extends View> T invisible(View parentView, @IdRes int textViewId) {
- T tv = (T) parentView.findViewById(textViewId);
- if (null != tv) {
- tv.setVisibility(View.INVISIBLE);
- }
-
- return tv;
+ return ViewUtil.invisible((T) parentView.findViewById(textViewId));
}
public static <T extends View> T gone(View parentView, @IdRes int textViewId) {
- T tv = (T) parentView.findViewById(textViewId);
- if (null != tv) {
- tv.setVisibility(View.GONE);
+ return ViewUtil.gone((T) parentView.findViewById(textViewId));
+ }
+
+ public static <T extends View> T gone(T view) {
+ if (null != view) {
+ view.setVisibility(View.GONE);
}
+ return view;
+ }
- return tv;
+ public static <T extends View> T visible(T view) {
+ if (null != view) {
+ view.setVisibility(View.VISIBLE);
+ }
+ return view;
}
+ public static <T extends View> T invisible(T view) {
+ if (null != view) {
+ view.setVisibility(View.INVISIBLE);
+ }
+ return view;
+ }
}
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleConnection.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleConnection.java
index ca823926..aa85430b 100644
--- a/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleConnection.java
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleConnection.java
@@ -1007,7 +1007,7 @@ public class JingleConnection implements Transferable {
return this.mStatus;
}
- @Override
+
public long getFileSize() {
if (this.file != null) {
return this.file.getExpectedSize();
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index 251db18e..eb1fefb8 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -684,4 +684,6 @@
<string name="cplus_open">Open</string>
<string name="cplus_remote_file_delete_service_unavailable">Remote File Deletion Service currently unavailable. Please try again later.</string>
<string name="cplus_remote_file_delete_failed">Failed to delete remote file.</string>
+ <string name="remote_filestatus_not_found">Remote file not found.</string>
+ <string name="checking_remote_filesize">Checking file size on remote host.</string>
</resources>
diff --git a/src/test/java/de/thedevstack/conversationsplus/utils/UIHelperTest.java b/src/test/java/de/thedevstack/conversationsplus/utils/UIHelperTest.java
new file mode 100644
index 00000000..2409413d
--- /dev/null
+++ b/src/test/java/de/thedevstack/conversationsplus/utils/UIHelperTest.java
@@ -0,0 +1,29 @@
+package de.thedevstack.conversationsplus.utils;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+/**
+ *
+ */
+public class UIHelperTest {
+ @Test
+ public void testGetHumanReadableDetailedFileSizeBytes() {
+ String result = UIHelper.getHumanReadableDetailedFileSize(1);
+ assertEquals("1 bytes", result);
+ result = UIHelper.getHumanReadableDetailedFileSize(120);
+ assertEquals("120 bytes", result);
+ result = UIHelper.getHumanReadableDetailedFileSize(1023);
+ assertEquals("1023 bytes", result);
+ }
+
+ @Test
+ public void testGetHumanReadableDetailedFileSizeKilobytes() {
+ String result = UIHelper.getHumanReadableDetailedFileSize(1024);
+ assertEquals("1 kb", result);
+ result = UIHelper.getHumanReadableDetailedFileSize(120);
+ assertEquals("120 bytes", result);
+ result = UIHelper.getHumanReadableDetailedFileSize(1023);
+ assertEquals("1023 bytes", result);
+ }
+}